目录
服务搭建
1. 注册中心 --Eureka Server
2. 服务提供方 --Service Provider
3. 服务消费方 --Service Consumer
服务消费 Feign 与断路器 Hystrix
特别注意
- Summary
- Reference
- Source Code
本文首发于我的个人博客, Spring Cloud 负载均衡初体验 , 欢迎访问!
使用 Spring Cloud Netflix 组件 Eureka 和 Ribbon 构建单注册中心的负载均衡服务.
Spring Cloud 是基于 Spring 的微服务技术栈, 可以这么概括吧, 里面包含了很多例如服务发现注册, 配置中心, 消息总线, 负载均衡, 断路器, 数据监控等组件, 可以通过 Spring Boot 的形式进行集成和使用.
目前, 项目中有这么个需求, Spring Boot 做一个 web 服务, 然后调用 TensorFlow 模型得到结果. 但是由于 TensorFlow GPU 版, 不支持多线程多引擎, 所以只能采用多进程的方式去进行调度, 所以需要做一个负载均衡. 负载均衡的话, 可以分为客户端和服务端负载均衡. 我目前还没能领悟到有什么不同, 毕竟整体的架构都是一样的, 如下如图. 其中客户端均衡负载的代表是 Spring Cloud 的 Ribbon, 服务端负载均衡代表是 Nginx.
由于项目的压力并不大, 日平均请求约 5000 左右, 因此就采用 Spring Cloud 中的组件进行客户端负载均衡. 主要用到的就是 Spring Cloud 和 Eureka. 很多博客中会也看到 Ribbon 的身影. 其实他们都是 Spring Cloud Netflix 中的组件, 用来构建微服务. 本文所讲的例子, 也可以看作是一个微服务, 把原来一个的算法服务拆成了若干个小服务. 之前讲到的 Spring Cloud 的各种组件也都是为了使得这些独立的服务能够更好的管理和协作.
回到负载均衡, 一个使用 Spring Boot 搭建的客户端负载均衡服务, 其实只需要 Rureka 这一个组件就够了.
Eureka 是 Spring Cloud Netflix 当中的一个重要的组件, 用于服务的注册和发现. Eureka 采用了 C-S 的设计架构. 具体如下图, Eureka Server 作为一个注册中心, 担任服务中台的角色, 余下的其他服务都是 Eureka 的 Client. 所有的服务都需要注册到 Eureka Server 当中去, 由它进行统一管理和发现. Eureka Server 作为管理中心, 自然, 除了注册和发现服务外, 还有监控等其他辅助管理的功能.
具体从负载均衡的角度来讲:
Eureka Server-- 提供服务注册和发现
Service Provider-- 服务提供方, 一般有多个服务参与调度
Service Consumer-- 服务消费方, 从 Eureka 获取注册服务列表, 从而能够消费服务, 也就是请求的直接入口.
服务搭建
下面主要实战一下负载均衡服务搭建.
正如上面所说, 一套完整的负载均衡服务, 至少需要三个服务.
1. 注册中心 --Eureka Server
直接通过 IDEA 创建一个包含 Eureka Server 的 Spring Boot 项目, 直接引入所需的 dependency. 主要是 spring-cloud-dependencies 和 spring-cloud-starter-netflix-eureka-server.
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-dependencies</artifactId>
- <version>${spring-cloud.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
在启动类上添加注解 @EnableEurekaServer.
- @EnableEurekaServer
- @SpringBootApplication
- public class EurekaServerApplication {
- public static void main(String[] args) {
- SpringApplication.run(EurekaServerApplication.class, args);
- }
- }
在 YAML 中配置 eureka.
- server:
- port: 8080
- eureka:.
- instance:
- prefer-ip-address: true
- client:
- # 表示是否将自己注册到 Eureka Server, 默认为 true
- register-with-eureka: false
- # 表示是否从 Eureka Server 获取注册信息, 默认为 true
- fetch-registry: false
- service-url:
- # 默认注册的域, 其他服务都往这个 url 上注册
- defaultZone: http://localhost:${server.port}/eureka/
由于目前配置的是单节点的注册中心, 因此 register-with-eureka 和 fetch-registry 都设为 false, 不需要把自己注册到服务中心, 不需要获取注册信息.
启动工程后, 访问: http://localhost:8080/, 就能看到一个图形化的管理界面, 目前没有注册任何服务.
2. 服务提供方 --Service Provider
在 YAML 中配置 Eureka.
- eureka:
- instance:
- prefer-ip-address: true # 以 IP 的形式注册
- # 默认是 hostname 开头的, 修改成 ip 开头
- instance-id: \${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
- client:
- serviceUrl:
- defaultZone: http://localhost:8080/eureka/ # 注册到之前的服务中心
- server:
- port: 8084
- spring:
- application:
- name: services-provider # 应用名称
默认, Eureka 是通过 hostname 来注册到 Eureka Server 上的, 由于后面可能涉及到多节点的配置, hostname 可能不如 ip 方便管理, 所以将 prefer-ip-address 设为 true, 通过 ip 注册, 并修改 instance-id 格式为:${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}. 注意 ip-address 为短线连接.
然后写一个简单的 Web 服务做测试.
- @RestController
- public class ServicesController {
- @RequestMapping("/")
- public String home() {
- return "Hello world";
- }
- }
然后运行工程.
3. 服务消费方 --Service Consumer
新建项目同 2, 然后配置 Eureka:
- eureka:
- instance:
- prefer-ip-address: true
- instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
- client:
- serviceUrl:
- defaultZone: http://localhost:8080/eureka/
- server:
- port: 8085
- spring:
- application:
- name: services-consumer
和之前一样, 注意区分端口号和 name 就可以了.
再在启动类, 配置 RestTemplate 用于负载均衡的服务分发. 这是服务消费的一种基本方式, 下面还会介绍第二种 Feign.
- @SpringBootApplication
- public class ServicesConsumerApplication {
- @Bean
- @LoadBalanced
- public RestTemplate restTemplate() {
- return new RestTemplate();
- }
- public static void main(String[] args) {
- SpringApplication.run(ServicesConsumerApplication.class, args);
- }
- }
在 Controller 中的调用
- @RestController
- public class ConsumerController {
- // services-provider 为服务 provider 的 application.name, 负载均衡要求多个服务的 name 相同
- private final String servicesUrl = "http://services-provider/";
- private final RestTemplate restTemplate;
- public ConsumerController(RestTemplate restTemplate) {
- this.restTemplate = restTemplate;
- }
- @RequestMapping("/")
- public String home() {
- return restTemplate.getForObject(servicesUrl,String.class);
- }
- }
然后运行工程, 可以发现两个服务都已经成功注册到注册中心.
有的博客中可能提到需要添加,@EnableDiscoveryClient 或 @EnableEurekaClient, 而实际上, 官方文档已经明确, 只要 spring-cloud-starter-netflix-eureka-client 在 classpath 中, 应用会自动注册到 Eureka Server 中去.
- By having spring-cloud-starter-netflix-eureka-clienton the classpath, your application automatically registers with the Eureka Server. Configuration is required to locate the Eureka server.
- https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.2.0.M1/
直接调用 localhost:8085, 就显示 hello world, 说明成功! 如果不相信的话, 可以起两个 provider 服务, 然后分别输出不同的字符串来验证负载均衡是否成功.
2019-08-13 15:08:18.689 INFO 14100 --- [nio-8085-exec-4] c.n.l.DynamicServerListLoadBalancer: DynamicServerListLoadBalancer for client services-provider initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=services-provider,current list of Servers=[192.168.140.1:8084]}
简化后的日志如上, 可以看到, 注解 @LoadBalanced 的负载均衡功能是通过 DynamicServerListLoadBalancer 来实现, 这是一种默认的负载均衡策略. 获取到服务列表后, 通过 Round Robin 策略 (服务按序轮询从 1 开始, 直到 N) 进行服务分发.
至此, 本地的负载均衡服务就搭建完成了.
服务消费 Feign 与断路器 Hystrix
如果是初体验的话可以忽略这一节. 添加这一节的目的是为了服务的完整性. 在实际开发中, Feign 可能用到的更多, 并且多会配合 Hystrix.
Feign 也是一种服务消费的方式, 采用的是申明式的形式, 使用起来更像是本地调用, 它也是 Spring Cloud Netflix 中的一员.
Hystrix, 简而言之就是一种断路器, 负载均衡中如果有服务出现问题不可达后, 通过配置 Hystrix, 可以实现服务降级和断路的作用, 这个功能 Eureka 默认是不提供的. 因此, 之前说了, 为了负载均衡的完整性, 需要添上它, 否则, Ribbon 依然会将请求分发到有问题的服务上去.
那么要使用他们, 首先需要添加依赖
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
在启动类上加上 @EnableFeignClients.
调用的时候需要实现接口.
- @FeignClient(value = "services-provider")
- public interface IRestRemote {
- @GetMapping("/")
- String home();
- }
最后在 Controller 中注入接口, 直接调用就可以了.
- @RestController
- public class ConsumerController {
- private final IRestRemote remote;
- public ConsumerController(IRestRemote remote) {
- this.remote = remote;
- }
- @RequestMapping("/")
- public String home() {
- return remote.home();
- }
相对于 RestTemplate,Fegin 使用起来更简单, 不需要去构造参数请求了, 并且 Feign 底层集成了 Ribbon, 也不需要显示添加 @LoadBalanced 注解了. 同时 Feign 也可以直接使用下面要讲的 Hystrix.
Hystrix 的功能很多, 远不止断路器一种, 需要详细了解可看别的博客. 这里就讲一下 Feign 如何集成 Hystrix 工作的.
下面先描述一下具体场景. 假如现在有两个负载均衡服务, 其中有一个挂了或出现异常了, 如果没有断路器, 进行服务分发的时候, 仍然会分配到. 如果开启 Hystrix, 首先会进行服务降级(FallBack), 也就是出现问题, 执行默认的方法, 并在满足一定的条件下, 熔断该服务.
在原来 @FeignClient 的基础上添加 fallback 参数, 并实现降级服务.
- @FeignClient(value = "services-provider",fallback = RestRemoteHystrix.class)
- public interface IRestRemote {
- @GetMapping("/")
- String home();
- }
- @Component
- public class RestRemoteHystrix implements IRestRemote {
- @override
- public String home() {
- return "default";
- }
- }
最后, 在配置文件中开启 Feign 的 Hystrix 开关.
- feign:
- hystrix:
- enabled: true
下面就可以测试了, 沿着之前的例子, 分别开启两个服务, 并输出不同的文本, 当关闭一个服务后, 再请求会发现, 分配到关闭的那个服务时, 会显示 "default", 请求多次后发现, 该服务不可用, 说明断路器配置成功! 欲知更多, 可以阅读参考文献 5-6.
特别注意
1. 多节点的配置
如果不是单机, 则需要修改部分字段, 具体如下注释:
- eureka:
- instance:
- prefer-ip-address: true # 以 IP 的形式注册
- # 主机的 ip 地址(同样考虑集群环境, 一个节点可能会配置多个网段 ip, 这里可指定具体的)
- ip-address:
- # 主机的 http 外网通信端口, 该端口和 server.post 不同, 比如外网需要访问某个集群节点, 直接是无法访问 server.post 的, 而是需要映射到另一端口.
- # 这个字段就是配置映射到外网的端口号
- non-secure-port:
- # 默认是 hostname 开头的, 修改成 ip 开头
- instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
- client:
- serviceUrl:
- # 这里不是 localhost 了, 而是注册中心的 ip:port
- defaultZone: http://ip:8080/eureka/
- server:
- port: 8084
- spring:
- application:
- name: services-provider # 应用名称
2. 高可用注册中心
本文上述搭建的是单个注册中心的例子, 基本已经满足我目前的项目需求. 但是在阅读别的博客中, 发现真正的微服务架构, 为了实现高可用, 一般会有多个注册中心, 并且相互注册形成. 这里先简单做个记录.
3. 更复杂, 自定义的负载均衡规则.
目前, 其实只是引入了 spring cloud 和 Eureka 的依赖就实现了简单的负载均衡. 但仔细看 DynamicServerListLoadBalancer 类的位置, 是在 Ribbon 下的. 虽然没有显式去添加 Ribbon 的依赖包, 但是实际上已经被包含进去了. 那么 Ribbon 是什么?
Ribbon is a client-side load balancer that gives you a lot of control over the behavior of HTTP and TCP clients. Feign already uses Ribbon, so, if you use @FeignClient, this section also applies.
Ribbon 是 Spring Cloud 中的一个组件, 主要是做客户端负载均衡的. 不关注底层细节, 可能上文搭建的服务它无关, 实际上还是用到了. 那么, 如果需要自定义复杂均衡的规则, 则需要通过配置 Ribbon 来实现.
Summary
本文主要是 "拿来主义" 的思想, 直接用了 Spring Cloud 中的几个组件来组建一个负载均衡服务. 为了能够更好地进行服务治理, 还需按部就班先学习基本的原理概念, 例如 CAP 理论等, 在逐步学习 Cloud 中的组件, 有一定的全局观.
注册中心 Eureka 本身满足的就是 AP, 在生产环境中, 为了保证服务的高可用, 势必要有至少两个的注册中心.
Reference
springcloud(二): 注册中心 Eureka
springcloud(三): 服务提供与调用
Spring Cloud Netflix
SpringCloud - 负载均衡器 Ribbon
Spring Cloud(四): 服务容错保护 Hystrix[Finchley 版]
- Setup a Circuit Breaker with Hystrix, Feign Client and Spring Boot
- Source Code
来源: https://www.cnblogs.com/Sinte-Beuve/p/11569789.html