SpringCloud系列:深入理解Ribbon之源码解析

举报
云享专家 发表于 2019/08/31 11:46:47 2019/08/31
【摘要】 ,Ribbon的负载均衡,主要通过LoadBalancerClient来实现的,而LoadBalancerClient具体交给了ILoadBalancer来处理,ILoadBalancer通过配置IRule、IPing等信息,并向EurekaClient获取注册列表的信息,并默认10秒一次向EurekaClient发送“ping”,进而检查是否更新服务列表

RibbonNetflix公司开源的一个负载均衡的项目,它属于上述的第二种,是一个客户端负载均衡器,运行在客户端上。它是一个经过了云端测试的IPC库,可以很好地控制HTTPTCP客户端的一些行为。Feign已经默认使用了Ribbon

·      负载均衡

·      容错

·      多协议(HTTPTCPUDP)支持异步和反应模型

·      缓存和批处理

RestTemplateRibbon相结合

RibbonNetflix组件是非常重要的一个组件,在Zuul中使用Ribbon做负载均衡,以及Feign组件的结合等。在Spring Cloud 中,作为开发中,做的最多的可能是将RestTemplateRibbon相结合,你可能会这样写:

@Configuration
public
class RibbonConfig {    
   @Bean
    @LoadBalanced
    RestTemplate restTemplate() { 
       return new RestTemplate();
    }
}

消费另外一个的服务的接口,差不多是这样的:

 

@Servicepublic class RibbonService {  
   @Autowired
    RestTemplate restTemplate;
    public String hi(String name) {    
       return restTemplate.getForObject(
"http://eureka-client/hi?name="+name,String.class);
    }
}

深入理解Ribbon

LoadBalancerClient

Riibon中一个非常重要的组件为LoadBalancerClient,它作为负载均衡的一个客户端。它在spring-cloud-commons包下:LoadBalancerClient是一个接口,它继承ServiceInstanceChooser,它的实现类是RibbonLoadBalancerClient,这三者之间的关系如下图:

1.png

 

其中LoadBalancerClient接口,有如下三个方法,其中excute()为执行请求,reconstructURI()用来重构url


1


2

3

4

5

6

public interface LoadBalancerClient extends ServiceInstanceChooser {

<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

URI reconstructURI(ServiceInstance instance, URI original);

}

ServiceInstanceChooser接口,主要有一个方法,用来根据serviceId来获取ServiceInstance,代码如下:


1


2

3

4

public interface ServiceInstanceChooser {

ServiceInstance choose(String serviceId);

}

LoadBalancerClient的实现类为RibbonLoadBalancerClient,这个类是非常重要的一个类,最终的负载均衡的请求处理,由它来执行。它的部分源码如下:


1


2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

public class RibbonLoadBalancerClient implements LoadBalancerClient {

...//省略代码

@Override

        public ServiceInstance choose(String serviceId) {

               Server server = getServer(serviceId);

               if (server == null) {

                       return null;

               }

               return new RibbonServer(serviceId, server, isSecure(server, serviceId),

                               serverIntrospector(serviceId).getMetadata(server));

        }

protected Server getServer(String serviceId) {

               return getServer(getLoadBalancer(serviceId));

}

protected Server getServer(ILoadBalancer loadBalancer) {

               if (loadBalancer == null) {

                       return null;

               }

               return loadBalancer.chooseServer("default"); // TODO: better handling of key

        }

protected ILoadBalancer getLoadBalancer(String serviceId) {

               return this.clientFactory.getLoadBalancer(serviceId);

        }

        

        ...//省略代码

 

 

RibbonLoadBalancerClient的源码中,其中choose()方法是选择具体服务实例的一个方法。该方法通过getServer()方法去获取实例,经过源码跟踪,最终交给了ILoadBalancer类去选择服务实例。

ILoadBalancerribbon-loadbalancerjar包下,它是定义了实现软件负载均衡的一个接口,它需要一组可供选择的服务注册列表信息,以及根据特定方法去选择服务,它的源码如下


1


2

3

4

5

6

7

8

public interface ILoadBalancer {

public void addServers(List<Server> newServers);

public Server chooseServer(Object key);

public void markServerDown(Server server);

public List<Server> getReachableServers();

public List<Server> getAllServers();

}

其中,addServers()方法是添加一个Server集合;chooseServer()方法是根据key去获取ServermarkServerDown()方法用来标记某个服务下线;getReachableServers()获取可用的Server集合;getAllServers()获取所有的Server集合。

DynamicServerListLoadBalancer

它的继承类为BaseLoadBalancer,它的实现类为DynamicServerListLoadBalancer,这三者之间的关系如下:

2.png

 

 

查看上述三个类的源码,可用发现,配置以下信息,IClientConfigIRuleIPingServerListServerListFilterILoadBalancer,查看BaseLoadBalancer类,它默认的情况下,实现了以下配置:

·      IClientConfig ribbonClientConfig: DefaultClientConfigImpl配置

·      IRule ribbonRule: RoundRobinRule 路由策略

·      IPing ribbonPing: DummyPing

·      ServerList ribbonServerList: ConfigurationBasedServerList

·      ServerListFilter ribbonServerListFilter: ZonePreferenceServerListFilter

·      ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer

IClientConfig 用于对客户端或者负载均衡的配置,它的默认实现类为DefaultClientConfigImpl

IRule用于复杂均衡的策略,它有三个方法,其中choose()是根据key 来获取server,setLoadBalancer()getLoadBalancer()是用来设置和获取ILoadBalancer的,它的源码如下:


1


2

3

4

5

6

7

8

public interface IRule{

public Server choose(Object key);

public void setLoadBalancer(ILoadBalancer lb);

public ILoadBalancer getLoadBalancer();

}

IRule有很多默认的实现类,这些实现类根据不同的算法和逻辑来处理负载均衡。Ribbon实现的IRule有一下。在大多数情况下,这些默认的实现类是可以满足需求的,如果有特性的需求,可以自己实现。

·      BestAvailableRule选择最小请求数

·      ClientConfigEnabledRoundRobinRule轮询

·      RandomRule 随机选择一个server

·      RoundRobinRule 轮询选择server

·      RetryRule 根据轮询的方式重试

·      WeightedResponseTimeRule根据响应时间去分配一个weight weight越低,被选择的可能性就越低

·      ZoneAvoidanceRule根据serverzone区域和可用性来轮询选择

·       3.png

·       


1


2

3

public interface IPing {

public boolean isAlive(Server server);

}

·      IPing是用来想server发生”ping”,来判断该server是否有响应,从而判断该server是否可用。它有一个isAlive()方法,它的源码如下:

·      IPing的实现类有PingUrl、PingConstant、NoOpPing、DummyPing和NIWSDiscoveryPing。它门之间的关系如下:

·       4.png

·       

·      PingUrl 真实的去ping 某个url,判断其是否alive

·      PingConstant 固定返回某服务是否可用,默认返回true,即可用

·      NoOpPing 不去ping,直接返回true,即可用。

·      DummyPing 直接返回true,并实现了initWithNiwsConfig方法。

·      NIWSDiscoveryPing,根据DiscoveryEnabledServerInstanceInfoInstanceStatus去判断,如果为InstanceStatus.UP,则为可用,否则不可用。

ServerList是定义获取所有的server的注册列表信息的接口,它的代码如下:

 


1


2

3

4

5

6

public interface ServerList<T extends Server> {

public List<T> getInitialListOfServers();

public List<T> getUpdatedListOfServers();

}

 

ServerListFilter接口,定于了可根据配置去过滤或者根据特性动态获取符合条件的server列表的方法,代码如下:

 


1


2

3

4

5

public interface ServerListFilter<T extends Server> {

public List<T> getFilteredListOfServers(List<T> servers);

}

 

阅读DynamicServerListLoadBalancer的源码,DynamicServerListLoadBalancer的构造函数中有个initWithNiwsConfig()方法。在改方法中,经过一系列的初始化配置,最终执行了restOfInit()方法。其代码如下:

·       


2

3

public interface IPing {

public boolean isAlive(Server server);

}

·       

在restOfInit()方法上,有一个updateListOfServers()的方法,该方法是用来获取所有的ServerList的。

5.png

 

 

在restOfInit()方法上,有一个updateListOfServers()的方法,该方法是用来获取所有的ServerList的。

6.png

 

进一步跟踪updateListOfServers()方法的源码,最终由serverListImpl.getUpdatedListOfServers()获取所有的服务列表的,代码如下:

7.png

 

 

而serverListImpl是ServerList接口的具体实现类。跟踪代码,ServerList的实现类为DiscoveryEnabledNIWSServerList,在ribbon-eureka.jar的com.netflix.niws.loadbalancer下。其中DiscoveryEnabledNIWSServerList有getInitialListOfServers()和getUpdatedListOfServers()方法,具体代码如下:

8.png

 

继续跟踪源码,obtainServersViaDiscovery(),是根据eurekaClientProvider.get()来回去EurekaClient,再根据EurekaClient来获取注册列表信息,代码如下:

9.png

10.png

 

 

其中eurekaClientProvider的实现类是LegacyEurekaClientProvider,它是一个获取eurekaClient类,通过静态的方法去获取eurekaClient,其代码如下:

11.png

 

EurekaClient的实现类为DiscoveryClient,在之前已经分析了它具有服务注册、获取服务注册列表等的全部功能。

由此可见,负载均衡器是从EurekaClient获取服务信息,并根据IRule去路由,并且根据IPing去判断服务的可用性。

那么现在还有个问题,负载均衡器多久一次去获取一次从Eureka Client获取注册信息呢。

在BaseLoadBalancer类下,BaseLoadBalancer的构造函数,该构造函数开启了一个PingTask任务,代码如下:

12.png

 

setupPingTask()的具体代码逻辑,它开启了ShutdownEnabledTimer执行PingTask任务,在默认情况下pingIntervalSeconds为10,即每10秒钟,想EurekaClient发送一次”ping”。

13.png

 

PingTask源码,即new一个Pinger对象,并执行runPinger()方法。

14.png

 

查看Pinger的runPinger()方法,最终根据pingerStrategy.pingServers(ping, allServers)来获取服务的可用性,如果该返回结果,如之前相同,则不去向EurekaClient获取注册列表,如果不同则通知ServerStatusChangeListener或者changeListeners发生了改变,进行更新或者重新拉取。

15.png

16.png

 

spacer.gif

由此可见,LoadBalancerClient是在初始化的时候,会向Eureka回去服务注册列表,并且向通过10s一次向EurekaClient发送“ping”,来判断服务的可用性,如果服务的可用性发生了改变或者服务数量和之前的不一致,则更新或者重新拉取。LoadBalancerClient有了这些服务注册列表,就可以根据具体的IRule来进行负载均衡。

RestTemplate是如何和Ribbon结合的

最后,回答问题的本质,为什么在RestTemplate加一个@LoadBalance注解就可可以开启负载均衡呢?

17.png

全局搜索ctr+shift+f @LoadBalanced有哪些类用到了LoadBalanced有哪些类用到了, 发现LoadBalancerAutoConfiguration类,即LoadBalancer自动配置类。

18.png

19.png

 

在该类中,首先维护了一个被@LoadBalanced修饰的RestTemplate对象的List,在初始化的过程中,通过调用customizer.customize(restTemplate)方法来给RestTemplate增加拦截器LoadBalancerInterceptor。

而LoadBalancerInterceptor,用于实时拦截,在LoadBalancerInterceptor这里实现来负载均衡。LoadBalancerInterceptor的拦截方法如下:

20.png

 

总结

综上所述,Ribbon的负载均衡,主要通过LoadBalancerClient来实现的,而LoadBalancerClient具体交给了ILoadBalancer来处理,ILoadBalancer通过配置IRule、IPing等信息,并向EurekaClient获取注册列表的信息,并默认10秒一次向EurekaClient发送“ping”,进而检查是否更新服务列表,最后,得到注册列表后,ILoadBalancer根据IRule的策略进行负载均衡。

而RestTemplate 被@LoadBalance注解后,能过用负载均衡,主要是维护了一个被@LoadBalance注解的RestTemplate列表,并给列表中的RestTemplate添加拦截器,进而交给负载均衡器去处理。

 

 

原创作者:方志朋

方志朋简介:SpringCloud中国社区联合创始人,博客访问量突破一千万,爱好开源,热爱分享,活跃于各大社区,保持着非常强的学习驱动力,终身学习践行者,终身学习受益者。目前就职于国内某家知名互联网保险公司,担任DEVOPS工程师,对微服务领域和持续集成领域研究较深,精通微服务框架SpringCloud

 


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200