Ribbon 是 Netflix 开源的项目,主要用于为提供客户端软件提供负载均衡算法。Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一个基于 HTTP 和 TCP 的客户端负载均衡器。
1. Ribbon 单独使用
创建两个项目:order-service(订单服务,作为服务提供者)、user-center(用户中心,作为服务消费者)。并在 user-center(服务消费者)端使用 Spring Cloud Ribbon 做客户端负载均衡。
1.1 服务提供者
在 order-service/pom.xml 中声明依赖:
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
|
在 order-service/src/main/resources/application.yml 配置两个端口以便启动两个不同实例用于测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| spring: application: name: order-service profiles: active: service1 --- spring: profiles: service1 server: port: 8881 --- spring: profiles: service2 server: port: 8882
|
编写查询用户订单的服务方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderRepository orderRepository; @GetMapping("/find/{uid:[1-9]\\d+}") public List<Order> findByUid(@PathVariable Integer uid) { System.out.println("------------------------------------------------"); System.out.println("------------------ 方法被调用 ------------------"); System.out.println("------------------------------------------------"); return orderRepository.findByUid(uid); } }
|
配置根路径的访问许可,以便客户端负载均衡器能够 PING 通本服务:
1 2 3 4 5 6 7 8 9
| @Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addStatusController("/", HttpStatus.OK); } }
|
编写 Spring Boot 应用启动类:
1 2 3 4 5 6 7 8
| @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class); } }
|
1.2 服务消费者
在 user-center/pom.xml 中声明依赖:
1 2 3 4 5 6 7 8
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency>
|
在 user-center/src/main/resources/application.yml 配置客户端负载均衡:
1 2 3 4 5 6 7 8 9 10 11 12 13
| server: port: 8888 spring: application: name: user-center order-service: ribbon: eureka: enabled: false listOfServers: localhost:8881,localhost:8882 ServerListRefreshInterval: 3000
|
默认情况下,Spring Cloud Ribbon 使用 NoOpPing 作为 IPing 的实现,NoOpPing 实际上并没有 PING 服务器而是一直返回 true。而 PingUrl 则会通过 PING 服务器根路径地址来检查每一台服务器的状态以确认服务是否还在线。默认情况下,Spring Cloud Ribbon 使用 ZoneAvoidanceRule 作为 IRule 的实现,ZoneAvoidanceRule 基于 AZ(可用区)过滤服务器最大程度避免区域服务器故障。由于本地环境测试,这里采用 AvailabilityFilteringRule,它使用 Ribbon 内置的断路器功能来过滤掉处于开路状态(无法 PING 通)的服务器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class RibbonConfiguration { @Autowired private IClientConfig ribbonClientConfig; @Bean public IPing ribbonPing(IClientConfig config) { return new PingUrl(); } @Bean public IRule ribbonRule(IClientConfig config) { return new AvailabilityFilteringRule(); } }
|
编写用户订单查询服务:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @RestController @RequestMapping("/user") public class UserController { @Autowired private RestTemplate restTemplate; @GetMapping("/orders/{uid:[1-9]\\d+}") public List<Order> orders(@PathVariable Integer uid) { return restTemplate.getForObject(String.format("http://order-service/order/find/%d", uid), List.class); } }
|
编写 Spring Boot 应用启动类,使用@LoadBalanced
注解,为 RestTemplate 开启负载均衡的能力:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @SpringBootApplication @RibbonClient(name = "order-service", configuration = RibbonConfiguration.class) public class Application { @Bean @LoadBalanced public RestTemplate proviceRestTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Application.class); } }
|
1.3 启动服务
启动两个服务提供者服务:
1 2
| $ java -jar order-service-0.0.1-SNAPSHOT.jar $ java -Dspring.profiles.active=service2 -jar order-service-0.0.1-SNAPSHOT.jar
|
启动一个服务消费者服务:
1
| $ java -jar user-center-0.0.1-SNAPSHOT.jar
|
你可以尝试多次访问用户订单服务,或尝试途中关闭其中一个服务提供者服务再访问用户订单服务,以便查看其效果。
2. 整合 Eureka
创建三个项目:eureka-server(服务注册中心)、order-service(订单服务,作为服务提供者)、user-center(用户中心,作为服务消费者)。并在 user-center(服务消费者)端使用 Spring Cloud Ribbon 做客户端负载均衡。
2.1 服务注册中心
在 eureka-server/pom.xml 中声明依赖:
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency>
|
eureka-server/src/main/resources/application.yml 配置示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| server: port: 8761 spring: application: name: eureka-server eureka: instance: hostname: localhost server: renewal-percent-threshold: 0.49 client: register-with-eureka: false fetch-registry: false service-url: default-zone: http://${eureka.instance.hostname}:${server.port}/eureka/ logging: level: com.netflix.eureka: 'off' com.netflix.discovery: 'off'
|
Spring Boot 应用启动类:
1 2 3 4 5 6 7 8
| @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class); } }
|
2.2 服务提供者
在 order-service/pom.xml 中声明依赖:
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
|
在 order-service/src/main/resources/application.yml 配置两个端口以便启动两个不同实例用于测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| spring: application: name: order-service profiles: active: service1 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ --- spring: profiles: service1 server: port: 8881 --- spring: profiles: service2 server: port: 8882
|
编写查询用户订单的服务方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderRepository orderRepository; @GetMapping("/find/{uid:[1-9]\\d+}") public List<Order> findByUid(@PathVariable Integer uid) { System.out.println("------------------------------------------------"); System.out.println("------------------ 方法被调用 ------------------"); System.out.println("------------------------------------------------"); return orderRepository.findByUid(uid); } }
|
编写 Spring Boot 应用启动类:
1 2 3 4 5 6 7 8
| @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class); } }
|
2.3 服务消费者
在 user-center/pom.xml 中声明依赖:
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
|
user-center/src/main/resources/application.yml 配置示例:
1 2 3 4 5 6 7 8 9 10
| server: port: 8888 spring: application: name: user-center eureka: client: register-with-eureka: false serviceUrl: defaultZone: http://localhost:8761/eureka/
|
编写用户订单查询服务:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @RestController @RequestMapping("/user") public class UserController { @Autowired private RestTemplate restTemplate; @GetMapping("/orders/{uid:[1-9]\\d+}") public List<Order> orders(@PathVariable Integer uid) { return restTemplate.getForObject(String.format("http://order-service/order/find/%d", uid), List.class); } }
|
编写 Spring Boot 应用启动类,使用@LoadBalanced
注解,为 RestTemplate 开启负载均衡的能力:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @SpringBootApplication @RibbonClient(name = "order-service", configuration = RibbonConfiguration.class) public class Application { @Bean @LoadBalanced public RestTemplate proviceRestTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Application.class); } }
|
2.4 启动服务
启动服务注册中心:
1
| $ java -jar eureka-server-0.0.1-SNAPSHOT.jar
|
启动两个服务提供者服务:
1 2
| $ java -jar order-service-0.0.1-SNAPSHOT.jar $ java -Dspring.profiles.active=service2 -jar order-service-0.0.1-SNAPSHOT.jar
|
启动一个服务消费者服务:
1
| $ java -jar user-center-0.0.1-SNAPSHOT.jar
|
你可以尝试多次访问用户订单服务,或尝试途中关闭其中一个服务提供者服务再访问用户订单服务,以便查看其效果。
示例项目开发环境:Java-8、Maven-3、IntelliJ IDEA-2017、Spring Cloud-Dalston.SR1
完整示例项目链接:spring-cloud-netflix-ribbon-sample、spring-cloud-netflix-ribbon-with-eureka-sample
参考文档文献链接:client-side-load-balancing、spring-cloud-ribbon