ribbon是什么?
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服
务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中
列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接

服务端的负载均衡是一个url先经过一个代理服务器(这里是nginx),然后通过这个代理服务器通过算法(轮询,随机,权重等等..)反向代理你的服务,来完成负载均衡。而客户端的负载均衡则是一个请求在客户端的时候已经声明了要调用哪个服务,然后通过具体的负载均衡算法来完成负载均衡。
如何使用
首先,我们还是要引入依赖,但是,eureka已经把ribbon集成到他的依赖里面去了,所以这里不需要再引用ribbon的依赖,如图:

要使用ribbon,只需要一个注解,在RestTemplate上面加入@LoadBalanced注解,这样子就已经有了负载均衡。

为什么加这个注解就可以实现了ribbon的负载均衡了呢?在spring中,使用RestTemplate来构建网络请求,在使用RestTemplate时,可以实现自己的拦截器setInterceptors注入自己的拦截器,而ribbon就是使用了这一技术而实现的。
在ribbon的包下的spring.factories的配置文件中,加入了自动配置类RibbonAutoConfiguration,它主要作用就是实例化拦截器的逻辑,主要实例化了一个RibbonLoadBalancerClient的类。

ribbon的主要逻辑实现是在LoadBalancerAutoConfiguration类中,下面我们分析这个类都做了哪些事情。

首先注入了两个集合(spring支持注入集合),但是注入集合的时候,可以在其上加条件判断。如上图LoadBalancerAutoConfiguration的restTemplates集合的注入,其上加了一个@LoadBalanced的注解,表示:spring在实例化的时候,只有加上了@LoadBalanced注解的bean才会注入到restTemplates集合中(有一个前提,@LoadBalanced注解上必须加上@Qualifier才会生效),这就是我们自己使用ribbon的时候,只需要在RestTemplate上加上@LoadBalanced,ribbon就会生效的原因。这个restTemplates集合的作用是什么呢?它就是用来添加ribbon的拦截器。RetryLoadBalancerInterceptor就是ribbon拦截RestTemplate的拦截器。

这个intercept方法就是用来执行ribbon的逻辑。LoadBalancerInterceptor实现了ClientHttpRequestInterceptor,我们也可以自己实现一个拦截器,实现spring提供的ClientHttpRequestInterceptor这个接口就可以。

继续进入excute方法,在这个方法中有两个非常重要的方法,getLoadBalancer和getServer以及excute方法。getLoadBalancer是用来根据微服务名字获取负载均衡器,如果有就拿,没有则创建;在ribbon中,一个微服务名就会有一个与之对应的负载均衡器为它服务,不同的微服务名所对应的负载均衡器是不一样的,不同的LoadBalancer对应不同的Spring Context对象。getServer是用来通过指定的负载均衡策略返回一个具体的微服务地址。

从getLoadBalancer一直点进去,到最后会创建一个AnnotationConfigApplicationContext的上下文对象,在createContext方法中,首先会对AnnotationConfigApplicationContext做初始化,初始化的时候,用for循环把ribbon的配置类加载到了AnnotationConfigApplicationContext上下文中,然后构建父容器,context.setParent(this.parent)就是构建父子容器,把当前spring容器设置到新建的容器当中去,在子容器中可以拿到父容器中的内容,这样做就达到了隔离的效果。
getLoadBalancer()方法总结:根据微服务名字构建不同的spring容器,隔离配置;构建负载均衡器;去eureka client里面读取配置;定时刷新配置(30s并缓存),定时ping微服务地址是否可用(10s一次)。
负载均衡器的实现
DynamicServerListLoadBalancer就是ribbon的负载均衡器的实现,它主要做了如下三件事情:1)、去eureka client去读配置;2)、定时配置刷新;3)、ping eureka client服务器是否存活。流程如下图所示:

注意:只有在第一次调用某个微服务名字的时候,才会创建容器,后续都是从缓存中获取信息。
如何读取配置的呢?

在DynamicServerListLoadBalancer的构造方法中,进行了初始化以及配置的刷新等操作,在restOfInit中调用了一个叫updateListOfServices()的方法,这个方法就是用来从客户端Eureka Client获取配置信息并根据负载均衡算法,返回一个具体的servers。


serverListImpl是微服务名字的集合
定时配置刷新是如何实现的呢?
在DynamicServerListLoadBalancer中有一个叫UpdateAction的属性,如下图,它会定时去执行updateListOfServices方法,在ServerListUpdater的实现类EurekaNotificationServerListUpdater中会调用这个doUpdate的方法。


Ping的实现
在DynamicServerListLoadBalancer构造方法的第一行,执行父类了super的方法,ping就是在它的父类BaseLoadBalancer中实现的,在BaseLoadBalancer的构造方法中有一个setupPingTask的方法,这个方法就是用来定时发送ping的。



pingIntervalSeconds默认等于10,这个值不可改变,就是每隔10秒中执行一次,主要逻辑在PingTask里面。

在runPinger中,真正执行ping的是在一个pingServers的方法中,其实这个ping并没有真正的区执行ping xxx这样的操作,它是直接返回了一个true,是一个假的ping。

如果和Eureka一起使用,默认实现是NIWSDiscoveryPing,如果是UP状态,则是true,否则是false。

这个ping要不要去执行正真的ping操作是可以配置的。IPing的实现类的PingUrl就是真实去ping的,默认使用NIWSDiscoveryPing,根据注册中心的状态判断。

如果想要修改这个ping的逻辑,有两种方式:自己实现IPing接口或者使用IPing的已经存在的实现。在EurekaRibbonClientConfiguration中,注入了IPing的配置实现,如下图,如果spring容器中没有IPing的bean,则注入默认的实现。如果我们想改这个,则可以自定义一个IPing的实现注入到spring容器中。

网友评论