【Spring Cloud】Ribbon实现负载均衡
- 1. Ribbon简介
- 2. Ribbon的使用
- 2.1 导入Ribbon依赖
- 2.2 配置application.yml
- 2.3 向http中植入Ribbon
- 2.4 通过服务名称来访问服务集群
- 3. 使用Ribbon实现负载均衡
- 4. 源码追踪
- 5. 指定负载均衡策略
1. Ribbon简介
Ribbon时Netflix发布的负载均衡器,它有助于控制HTTP和TCP客户端的行为。为Ribbon配置服务提供者地址列表后,Ribbon就可基于某种负载均衡算法,自动地帮助服务消费者去请求。Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机等。我们也可以自定负载均衡算法。
在之前的案例中,我们启动了一个service-provider,然后通过DiscoveryClient来获取服务实例信息,然后获取ip和端口来访问。
但是实际环境中,我们往往会开启很多个service-provider的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?
一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。
不过Eureka中已经帮我们集成了负载均衡组件:Ribbon,简单修改代码即可使用。
2. Ribbon的使用
我们在前面文章中,将订单服务注册到 Eureka,然后消费方可以通过 http 请求去获取订单的信息,但是这是最原始的 http 调用,没有任何 Ribbon 的东西在里面,接下来我们要在消费方植入 Ribbon。
2.1 导入Ribbon依赖
<!--eureka Client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--ribbon负载均衡依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2.2 配置application.yml
server:
port: 8080
spring:
application:
name: service-consumer
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka01:10086/eureka/,http://eureka02:10087/eureka/
我们搭建了一个 Eureka 集群,那么就用这个集群,这个消费方我们设置不注册到该集群中。
2.3 向http中植入Ribbon
之前的 消费方是使用 RestTemplate 来发送 http 请求,调用订单服务的,但是没有负载均衡,所以现在我们要让这个 http 调用自带负载均衡。
修改RestTemplate配置
@SpringBootApplication
@EnableDiscoveryClient
public class SiyiServiceConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SiyiServiceConsumerApplication.class, args);
}
@Bean
@LoadBalanced //开启负载均衡 '@LoadBalanced'注解表示使用Ribbon实现客户端负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
在方法上添加一个 @LoadBalanced 注解即可开启 Ribbon 负载均衡。这样就可以通过微服务的名字从 Eureka 中找到对应的服务并访问了。
2.4 通过服务名称来访问服务集群
开启 Ribbon 负载均衡后,就可以通过微服务的名字从 Eureka 中找到对应的服务。
@Controller
@RequestMapping("consumer/user")
public class UserController {
@Autowired
private RestTemplate restTemplate;
// @Autowired
// private DiscoveryClient discoveryClient; //包含了拉取的所有服务信息
@GetMapping
@ResponseBody
public String queryUserById(@RequestParam("id")Long id){
// List<ServiceInstance> instances = discoveryClient.getInstances("service-provider");
// ServiceInstance serviceInstance = instances.get(0);
return this.restTemplate.getForObject("http://service-provider/user/"+id, String.class);
}
}
可以看出,注释掉的那部分,是原来的访问方式,订单提供服务是根据discoveryClient获取了第一个ip,然后访问了那一个端口。现在我们改成了微服务名称。这个名称就是 Eureka 管理界面显示的注册进去的名称,也即服务提供方的application.yml配置文件中配置的服务名称:
spring:
application:
name: service-consumer # 对外暴露的服务名称
分别启动eureka10086,eureka10087,以及eureka8080,可以看到服务已经注册到Eureka集群中了。
当我们访问时当然也能访问成功。
3. 使用Ribbon实现负载均衡
要实现负载均衡,首先要有多个订单服务提供者。
首先启动两个SiyiServiceProviderApplication实例,一个8081,一个8082。
设置配置文件:
当然这样看不出来哪里负载均衡了,如果时两个订单服务用的数据库不一样,我们就可以轻易发现每次刷新都会改变使用的那个订单服务,比如第一次是8081,那么刷新后就是8082。
直接说也不是个事,我们跟着源码追踪一番就知道了。
4. 源码追踪
为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。
显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor
在如下代码打断点:
一路源码跟踪:RestTemplate.getForObject --> RestTemplate.execute --> RestTemplate.doExecute:
点击进入AbstractClientHttpRequest.execute --> AbstractBufferingClientHttpRequest.executeInternal --> InterceptingClientHttpRequest.executeInternal --> InterceptingClientHttpRequest.execute:
继续跟入:LoadBalancerInterceptor.intercept方法
继续跟入execute方法:发现获取了8082端口的服务
再跟下一次,发现获取的是8081:
5. 指定负载均衡策略
Ribbon默认的负载均衡策略是简单的轮询,我们可以测试一下:
编写测试类,在刚才的源码中我们看到拦截中是使用RibbonLoadBalanceClient来进行负载均衡的,其中有一个choose方法,找到choose方法的接口方法,是这样介绍的:
现在这个就是负载均衡获取实例的方法。
我们注入这个类的对象,然后对其测试:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SiyiServiceConsumerApplication.class)
public class LoadBalanceTest {
@Autowired
private RibbonLoadBalancerClient client;
@Test
public void testLoadBalance(){
for (int i = 0; i < 100; i++) {
ServiceInstance instance = this.client.choose("service-provider");
System.out.println(instance.getHost() + ":" +instance.getPort());
}
}
}
符合了我们的预期推测,确实是轮询方式。跟踪源码,我们发现:
我们看看这个rule是谁:
这里的rule默认值是一个RoundRobinRule
,看类的介绍:
这不就是轮询的意思嘛。我们注意到,这个类其实是实现了接口IRule的,查看一下:
定义负载均衡的规则接口。我们看看以下实现有哪些
SpringBoot也帮我们提供了修改负载均衡规则的配置入口,只需在配置文件中做如下配置:
service-provider: # 服务提供方的服务id
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
格式是:{服务名称}.ribbon.NFLoadBalancerRuleClassName
,值就是IRule的实现类。
再次测试,我们发现结果就不再是轮询了,而是随机的