【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默认为我们提供了很多的负载均衡算法,例如轮询、随机等。我们也可以自定负载均衡算法。

springcloud默认的负载均衡组件是_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集群中了。

当我们访问时当然也能访问成功。

springcloud默认的负载均衡组件是_spring_02

3. 使用Ribbon实现负载均衡

要实现负载均衡,首先要有多个订单服务提供者。

首先启动两个SiyiServiceProviderApplication实例,一个8081,一个8082。

设置配置文件:

springcloud默认的负载均衡组件是_负载均衡_03


springcloud默认的负载均衡组件是_spring_04


springcloud默认的负载均衡组件是_SpringCloud_05


当然这样看不出来哪里负载均衡了,如果时两个订单服务用的数据库不一样,我们就可以轻易发现每次刷新都会改变使用的那个订单服务,比如第一次是8081,那么刷新后就是8082。

直接说也不是个事,我们跟着源码追踪一番就知道了。

4. 源码追踪

为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。

显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor

在如下代码打断点:

springcloud默认的负载均衡组件是_负载均衡_06


一路源码跟踪:RestTemplate.getForObject --> RestTemplate.execute --> RestTemplate.doExecute:

springcloud默认的负载均衡组件是_ide_07


点击进入AbstractClientHttpRequest.execute --> AbstractBufferingClientHttpRequest.executeInternal --> InterceptingClientHttpRequest.executeInternal --> InterceptingClientHttpRequest.execute:

springcloud默认的负载均衡组件是_spring_08


继续跟入:LoadBalancerInterceptor.intercept方法

springcloud默认的负载均衡组件是_Ribbon_09


继续跟入execute方法:发现获取了8082端口的服务

springcloud默认的负载均衡组件是_负载均衡_10


再跟下一次,发现获取的是8081:

springcloud默认的负载均衡组件是_ide_11

5. 指定负载均衡策略

Ribbon默认的负载均衡策略是简单的轮询,我们可以测试一下:

编写测试类,在刚才的源码中我们看到拦截中是使用RibbonLoadBalanceClient来进行负载均衡的,其中有一个choose方法,找到choose方法的接口方法,是这样介绍的:

springcloud默认的负载均衡组件是_Ribbon_12


现在这个就是负载均衡获取实例的方法。

我们注入这个类的对象,然后对其测试:

@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());
        }
    }
}

springcloud默认的负载均衡组件是_负载均衡_13


符合了我们的预期推测,确实是轮询方式。跟踪源码,我们发现:

springcloud默认的负载均衡组件是_负载均衡_14


我们看看这个rule是谁:

springcloud默认的负载均衡组件是_Ribbon_15


这里的rule默认值是一个RoundRobinRule,看类的介绍:

springcloud默认的负载均衡组件是_SpringCloud_16


这不就是轮询的意思嘛。我们注意到,这个类其实是实现了接口IRule的,查看一下:

springcloud默认的负载均衡组件是_Ribbon_17


定义负载均衡的规则接口。我们看看以下实现有哪些

springcloud默认的负载均衡组件是_Ribbon_18


SpringBoot也帮我们提供了修改负载均衡规则的配置入口,只需在配置文件中做如下配置:

service-provider: # 服务提供方的服务id
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

格式是:{服务名称}.ribbon.NFLoadBalancerRuleClassName,值就是IRule的实现类。

再次测试,我们发现结果就不再是轮询了,而是随机的

springcloud默认的负载均衡组件是_负载均衡_19