简介
SpringCloud Ribbon是一个基于HTTP和TCP的客户端的负载均衡工具,它基于Netflix Ribbon实现。通过SpringCloud 的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。
注意这里的两个关键词:
- 客户端:Ribbon需要将注册中心的服务示例拉去到本地进行缓存,并在本地完成负载均衡的动作,默认使用轮询的方式。
- 负载均衡:Ribbon只是进行负载均衡的操作,选取合适的微服务示例来提供给RestTemplate,请求的操作由后者完成。
项目准备
必要准备
引入依赖
如果使用的是eureka client 和 consul client,无须引入依赖。因为在eureka、consul中默认集成了ribbon组件
<!--引入Ribbon依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> <version>2.2.8.RELEASE</version> </dependency>
创建RestTemplate配置类
这样我们只需要在需要调用其他服务的控制器中使用@Autowired
注解注入即可
@Configuration public class BeansConfig { @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
我使用8083、8084、8085同时运行了订单服务来构成订单服务集群。
编写测试接口
Ribbon提供简单的三种方式来获取配置中心的服务示例或者完成负载均衡
DiscoveryClient
DiscoveryClient可以指定服务的名称来获取配置中心上的实例列表,这个对象是没有实现负载均衡的。
@Slf4j @RestController @RequestMapping("/users") public class UserController { /** * 服务注册发现客户端对象 */ @Autowired private DiscoveryClient discoveryClient; @Autowired private RestTemplate restTemplate; /** * 获取用户的订单 */ @GetMapping("/id/{id}/orders") public String getUserOrders(@PathVariable("id") Integer id){ // 获取注册中心指定服务全部的示例 List<ServiceInstance> instances = discoveryClient.getInstances("ORDERS"); instances.forEach(instance -> log.info("DiscoveryClient获取到[ORDERS]服务示例 -> 主机[{}],端口[{}],Uri[{}]", instance.getHost(), instance.getPort(), instance.getUri())); // 这里注意,取得是实例列表里面的第一个实例(可能为空),这里可以手动写一个算法,从instances随机获取一个实例 return restTemplate.getForObject(instances.get(0).getUri+"/orders/uId/"+id, String.class); } }
浏览器访问http://localhost:8082/users/id/2/orders
返回数据:20210726120030X2P8083
多次请求发现还是返回同样的数据,因为我们没有进行负载均衡,在代码中写死了,instances.get(0).getUri
,始终都是向第一个实例发送请求的。
LoadBalancerClient
LoadBalancerClient这是一个负载均衡的客户端对象,使用它提供的choose方法可以使用负载均衡来获取一个服务的实例,这里返回的是单个对象,默认的方式也是沦陷。
@Slf4j @RestController @RequestMapping("/users") public class UserController { /** * 负载均衡客户端对象 */ @Autowired private LoadBalancerClient loadBalancerClient; @Autowired private RestTemplate restTemplate; /** * 获取用户的订单 */ @GetMapping("/id/{id}/orders") public String getUserOrders(@PathVariable("id") Integer id){ // 使用负载均衡获取服务示例对象,默认轮询 ServiceInstance instance = loadBalancerClient.choose("ORDERS"); log.info("LoadBalancerClient获取到[ORDERS]服务示例 -> 主机[{}],端口[{}],Uri[{}]", instance.getHost(), instance.getPort(), instance.getUri()); return restTemplate.getForObject(instance.getUri+"/orders/uId/"+id, String.class); } }
多次在浏览器访问http://localhost:8082/users/id/2/orders
返回数据: 20210726120030X2P8085 20210726120030X2P8084 20210726120030X2P8083
这里可以看到,这样就实现了负载均衡。
@LoadBalanced
@LoadBalanced注解可以对上面的LoadBalancerClient进行再一次的简化,省掉编写loadBalancerClient.choose("ORDERS");
这个语句,将这个对象声明在RestTemplate的生成方法上面可以让生成的对象具有负载均衡的能力。
@Configuration public class BeansConfig { /** * LoadBalanced 为restTemplate对象赋予负载均衡的职责 */ @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
@Slf4j @RestController @RequestMapping("/users") public class UserController { @Autowired private RestTemplate restTemplate; /** * 获取用户的订单 */ @GetMapping("/id/{id}/orders") public String getUserOrders(@PathVariable("id") Integer id){ // ORDERS为注册中心服务的名称,框架会进行处理 return restTemplate.getForObject("http://ORDERS/orders/uId/"+id, String.class); } }
多次在浏览器访问http://localhost:8082/users/id/2/orders
返回数据: 20210726120030X2P8085 20210726120030X2P8084 20210726120030X2P8083
这里可以看到,这样也实现了负载均衡。