Spring Cloud netflix ribbon源码分析

2020-10-26 17:41:09 浏览数 (1)

  1. 依赖
代码语言:javascript复制
 <dependency>
	 <groupId>org.springframework.cloud</groupId>
	 <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
	 <version>2.2.3.RELEASE</version>
</dependency>
  1. 配置

假设订单服务有两台,分别分8060、8061的两个服务

代码语言:javascript复制
micro-order-service.ribbon.listOfServers=
  http://localhost:8060/orders,
  http://localhost:8061/orders
  1. 客户端负载均衡处理(user-service代码片段)
代码语言:javascript复制
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping(value = "/orders")
public String orders(){
	// 通过LoadBalancerClient获取一个ServiceInstance实例
	// 其中包含了host、port等信息
       ServiceInstance serviceInstance = loadBalancerClient.choose("micro-order-service");
	return restTemplate.getForObject(String.format("http://%s:%s/orders",
	 serviceInstance.getHost(), serviceInstance.getPort()), String.class);
}
  1. order-service
代码语言:javascript复制
   @Value("${server.port}")
    private Integer port;

    @GetMapping(value = "/orders")
    public String getOrders() {
        System.out.printf("curr service port: %s n", port);
        System.out.println();
        // 伪代码
        return "{"  
                "订单编号: 12345678900000,"  
                "订单编号: 12345678900001,"  
                "}";
    }

查看控制台会发现轮询打印了端口的信息

@LoadBalanced

修改user-service服务的代码如下,会发现带来同样的效果

代码语言:javascript复制
 @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @Bean
 // 这里加上@LoadBalanced
    @LoadBalanced
    public RestTemplate newRestTemplate(RestTemplateBuilder builder){
        return builder.build();
    }

    @GetMapping(value = "/orders")
    public String orders(){
		// 直接将host和port换成micro-order-service
        return restTemplate.getForObject("http://micro-order-service/orders", String.class);
    }
@LoadBalanced做了什么
  1. @Qualifier

进入@LoadBalanced会发现当前类被@Qualifier标注;那么@Qualifier的作用是什么

1.1. 小测试

代码语言:javascript复制
    @Qualifier
    @Bean("user1")
    public User newUser1(){
        return new User("张三");
    }

    @Bean("user2")
    @Qualifier
    public User newUser2(){
        return new User("李四");
    }

    @Autowired
    @Qualifier
    private List<User> users = Collections.emptyList();

 @GetMapping(value = "/users")
    public List users(){
        return users;
    }

当我们访问的 /users的时候会获取张三、李四两个对象;当我们将张三的@Qualifier注解去掉之后,只会获得李四一个对象;由此可以知道@Qualifier,可以作为一个标识,并且只能拿到同样标识了@Qualifier注解的对象。同时@Qualifier还具有分组的作用

代码语言:javascript复制
     @Qualifier("g1")
    @Bean
    public User newTest(){
        return new User("111");
    }

    @Qualifier("g1")
    @Bean
    public User newTest2(){
        return new User("222");
    }

    @Qualifier("g2")
    @Bean
    public User newTest3(){
        return new User("333");
    }

    @Qualifier("g2")
    @Autowired
    List<User> u1;

    @Qualifier("g1")
    @Autowired
    List<User> u2;

 @GetMapping(value = "/test")
    public Map test(){
        Map<String, List<User>> map = new HashMap<>();
        map.put("g2",u1);
        map.put("g1",u2);
        return map;
    }
LoadBalancerAutoConfiguration

按照正常的逻辑来讲,一个http请求肯定需要host,port才可以发送请求,那么很显然我们通过serviceId的方式也可以发送,说明在发送之前肯定是做了拦截操作,LoadBalancerAutoConfiguration在程序启动的时候会被加载。

  1. 通过调试不难发现这里会加载LoadBalancerInterceptor拦截
  1. 然后会将拦截器置入到RestTemplate
发送请求

发送请求的时候我们继续调试会进入到RestTemplatedoExecute方法中,大致如下所示:

  1. 这里有个重要的方法createRequest

继续跟踪会发现这里会去选择一个RequestFactory去创建请求, 有两个实现如下: org.springframework.http.client.support.HttpAccessor, org.springframework.http.client.support.InterceptingHttpAccessor

  1. InterceptingHttpAccessor

InterceptingHttpAccessor 是HttpAccessor的子类,InterceptingHttpAccessor一定会覆盖HttpAccessor的同名方法

  1. getRequestFactory

很显然这里的getInterceptors就是前面LoadBalancerAutoConfiguration中设置的。所以最后这里返回的一定是InterceptingClientHttpRequestFactory

  1. 返回到createReuquest的地方

这里发现并没有我们要找的InterceptingClientHttpRequestFactory类,因此我们进入到抽象类中

  1. InterceptingClientHttpRequestFactory的createRequest

createRequest方法在当前抽象类中没有做处理,是在子类中处理的,其中有两个子类 org.springframework.http.client.InterceptingClientHttpRequestFactory org.springframework.http.client.BufferingClientHttpRequestFactory

  1. 创建InterceptingClientHttpRequest

逐步跟进最后会进入到InterceptingClientHttpRequestFactory类中然后创建的request对象显然是InterceptingClientHttpRequest

  1. 返回到RestTemplate的doExecute方法

这里会有个execute方法,很显然没有找到InterceptingClientHttpRequest类,所以我们继续进入到抽象类:org.springframework.http.client.AbstractClientHttpRequest

  1. AbstractBufferingClientHttpRequest#executeInternal

依然没有找到InterceptingClientHttpRequest,所以需要继续跟进到 org.springframework.http.client.AbstractBufferingClientHttpRequest#executeInternal类中,继续跟踪

  1. 千呼万唤使出来

突破层层包装以后终于找到了我们需要找到的InterceptingClientHttpRequest

  1. InterceptingClientHttpRequest内部类InterceptingRequestExecution#execute

继续跟进,最后会进入到内部类InterceptingRequestExecution的execute方法.然后会判断是否有拦截器,答案是肯定的;而且通过前面LoadBalancerAutoConfiguration的介绍,最终一定会进入到: org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor#intercept

RibbonLoadBalancerClient

这里会进入到RibbonLoadBalancerClient#execute, 这里的serviceId即前面传入的。然后会选择一个负载均衡器。如下图示:

  1. 进入到getLoadBalancer

进入到getLoadBalancer方法逐步跟进最后会进入到 org.springframework.cloud.netflix.ribbon.SpringClientFactory#getInstance方法拿到负载均衡器及所有的实例并返回,这里会根据我们在配置文件中配置的内容: app-service: ribbon: listOfServers: http://localhost:8060,http://localhost:8061

getServer

进入到getServer这里会选择一个具体的实例。: ZoneAwareLoadBalancer是DynamicServerListLoadBalancer的子类所以会进入到子类方法中: com.netflix.loadbalancer.ZoneAwareLoadBalancer#chooseServer

ZoneAwareLoadBalancer

DynamicServerListLoadBalancer中因为没有chooseServer方法,所以会继续往上找到: com.netflix.loadbalancer.BaseLoadBalancer

注意这里的判断条件会判断 ZoneAwareNIWSDiscoveryLoadBalancer.enabled这个配置是否是false(默认为true),或者AvailableZones=1(考虑到跨机房部署),所以这里会执行if条件中的内容

  1. BaseLoadBalancer#chooseServer

这里会根据负债均衡规则获取实例

  1. ZoneAvoidanceRule

RibbonClientConfiguration中默认配置的路由规则为ZoneAvoidanceRule,因为不存在choose方法所以找到他的父类PredicateBasedRule

3. chooseRoundRobinAfterFiltering方法

根据方法名,或者方法上的注释不难发现最后还是采用的轮询算法。

  1. incrementAndGetModulo方法
关于IRule.

IRule是制定负载均衡规则的接口,提供了诸如: 随机、轮询、最可用、响应时间等不同的规则,这里默认采用的是轮询。

关于IPing.

如上图所示如何知道节点是否可用就需要有一个健康检查机制,那么IPing提供了一系列的健康检查机制,甚至我们自己也可以实现自己的健康检查规则:

  1. RibbonClientConfiguration中定义了默认的规则

进入到com.netflix.loadbalancer.DummyPing内部会发现isAlive方法返回true,即为不做检查,默认都是存活的状态

  1. 自定义规则

官方文档指出通过如下方式配置即可实现自定义规则,从源码中也可以诡探一二

  1. RibbonClientConfiguration#ribbonPing

进入到:org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration#ribbonPing,

服务发现和更新
  • DynamicServerListLoadBalancer的构造方法 在调用的过程中,会进入到DynamicServerListLoadBalancer的构造方法中DynamicServerListLoadBalancer(IClientConfig, IRule , IPing , ServerList<T> , ServerListFilter<T>, ServerListUpdater)
  • restOfInit方法

在restOfInit方法中执行服务列表的更新动作,enableAndInitLearnNewServersFeature 方法会去执行PollingServerListUpdater的start方法,如下图所示:

  • com.netflix.loadbalancer.PollingServerListUpdater

最终会进入到com.netflix.loadbalancer.PollingServerListUpdater类中,他这边有个start方法用来检查服务是否需要更新,每隔30s执行一次,并且回调DynamicServerListLoadBalancer#updateListOfServers方法,如下图所示:

  • com.netflix.loadbalancer.DynamicServerListLoadBalancer#updateListOfServers

紧接着会回到DynamicServerListLoadBalancer并且调用updateListOfServers,这里有调用更新服务列表的时候有两个实现,最终会进入到ConfigurationBasedServerList:

  • com.netflix.loadbalancer.ConfigurationBasedServerList#derive

0 人点赞