1. LoadBalancerClient 客户端使用
(1)引入注解
(2)使用客户端进行获取可用的实例,然后根据返回可用的实例进行http调用
2. LoadBalanced 注解拦截使用
(1)在实例 restTemplate 上加入@LoadBalanced注解
(2)直接通过restTemplate进行调用服务
3. LoadBalanced注解的分析
(1)@LoadBalanced注解 引用了@Qualifier,就继承了@Qualifier起到DI标识的作用
(2)测试@Qualifier 的效果,结果返回只有加上@Qualifier的bean ,实践如下:
(3)根据上述情况,猜测:@LoadBalanced,在配置文件进行标识 restTemplate的bean列表使用,下面我们来进行验证
1) 找到对应的 LoadBalance的自动配置类,spring boot starter组件都有自己的配置类,而ribben也是如此
2) 在此看到比较熟悉的身影,证明咱们的猜测是没问题,就是用@LoadBalanced来标识 restTemplate的bean集合
(4)上述的几个点已经从猜测到验证 @LoadBalanced的标识进行DI 注入,下来咱们就进行继续猜测和验证
@LoadBalanced 之后的作用
(5)猜测使用@LoadBalanced 标识 RestTemplate 主要是进行拦截 代理实现负载负载均衡根据服务ID 进行选举可用的实例,就是代替做如下的工作:
(6)验证第(5)点的猜测 ,回到 LoadBalancerAutoConfiguration 的类进行,看具体的bean的初始化怎么封装拦截器
初始化阶段:如下
1)、LoadBalancerInterceptor:
2)、自定义 RestTemplateCustomizer 的初始化
3)、SmartInitializingSingleton 的初始化
往自定义的RestTemplateCustomizer 设置获取标识的 restTmplate
到此相关的初始bean在 LoadBalancerAutoConfiguration 上已经装配完毕,大都是采用一层一层的依赖注入
5. RestTemplate 调用分析
restTemplate 类上调用
(1)执行 doExecute
此处来到了 doExecute ,看到有 ClientHttpRequest request = createRequest(url, method);看源码的同时 如果有新的对象影响主流程,咱们进行进去阅读对应方法
(2)createRequest 的创建分析
这里个方法是用来创建一个请求对象,其中getRequestFactory(),调用的是InterceptingHttpAccessor中的getRequestFactory方法,因为InterceptingHttpAccessor继承了HttpAccessor这个类,重写了getRequestFactory方法。
在上图的 getInterceptors()方法,这个方法中返回的拦截器列表,是从InterceptingHttpAccessor.setInterceptors()方法来设置的,而这个setInterceptors()调用的地方正好是LoadBalancerAutoConfifiguration.LoadBalancerInterceptorConfifig.restTemplateCustomizer
再回到createRequest方法中,getRequestFactory()方法返回的是InterceptingClientHttpRequestFactory,而createRequest方法,最终返回的是 InterceptingClientHttpRequest这个类。
所以分析到这个createRequest 最终返回是 InterceptingClientHttpRequest这个类
调试验证可得 如下图的InterceptingClientHttpRequest 实例:
(3)上述第(2)点 分析了request创建过程和最终得到的实例,下面验证 InterceptingClientHttpRequest调用execute的过程
点击 execute 方法进入如下界面
点击实现类出现工厂模式的创建类,这下该怎么选呢?可以由下面类图可知选 AbstractClientHttpRequest 进入(没有找到对应的 InterceptingClientHttpRequest类,则选择父类)
由上述进入该界面
继续往下进入executeInternal方法,进入之后也面临选择的问题,因为当前类是 AbstractClientHttpRequest,所以选择进入AbstractBufferFeringClientHttpRequest (可以进行断点调试)
走到这里已经进入了 最初 createRequest()的 InterceptingClientHttpRequest类,万里长城终于走到一大半了,小伙伴们
在上述方法上,调用了内部类 InterceptingRequestExecution 的 execute执行方法,最终执行拦截器的intercept
小伙伴们,到这里看到了咱们最开始分析初始化的LoadBalancerInterceptor了 ,所谓可歌可泣呀
拦截调用主要实现了负载均衡器的执行
小总结:到这里查找验证分析LoadBalancerIntercetor拦截调用就结束了。下面是分析这个拦截调用的负载均衡器怎么进行服务列表的获取和定时更新服务列表缓存和定时心跳
(4)LoadBalancerClient 负载均衡客户端分析
上述的第(3)点主要是分析请求拦截器的实现过程,下面分析拦截成功后做的事情。
1)回到 拦截器执行intercept的方法,调用了LoadBalancerClient的execute方法
2)由 execute 进入 LoadBalancerClient 的 execute 方法,可以看到
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId); 获取对应的负载均衡器
Server server = this.getServer(loadBalancer, hint); 再由负载均衡器获取到服务实例
判断Server的值是否为空。这里的Server实际上就是传统的一个服务节点,这个对象存储了服务节
点的一些元数据,比如host、port等
3)分析 getServer()
loadBalancer 的类图:
从整个类的关系图来看,BaseLoadBalancer类实现了基础的负载均衡,而DynamicServerListLoadBalancer和ZoneAwareLoadBalancer则是在负载均衡策略的基础上做了一些功能扩展。
AbstractLoadBalancer实现了ILoadBalancer接口,它定义了服务分组的枚举类/chooseServer(用来选取一个服务实例)/getServerList(获取某一个分组中的所有服务实例)/getLoadBalancerStats用来获得一个LoadBalancerStats对象,这个对象保存了每一个服务的状态信息。
BaseLoadBalancer,它实现了作为负载均衡器的基本功能,比如服务列表维护、服务存活状态监测、负载均衡算法选择Server等。但是它只是完成基本功能,在有些复杂场景中还无法实现,比如动态服务列表、Server过滤、Zone区域意识(服务之间的调用希望尽可能是在同一个区域内进行,减少延迟)。
DynamicServerListLoadBalancer是BaseLoadbalancer的一个子类,它对基础负载均衡提供了扩展,从名字上可以看出,它提供了动态服务列表的特性
ZoneAwareLoadBalancer 它是在DynamicServerListLoadBalancer的基础上,增加了以Zone的形式来配置多个LoadBalancer的功能。
问题:那在getServer方法中, loadBalancer.chooseServer 具体的实现类是哪一个呢?
我们找到 RibbonClientConfifiguration这个类。在这个bean的声明中可以看到,默认情况下采用的是ZoneAwareLoadBalancer。可以看到AutoConfiguration下的bean
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server>
serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping,
ServerListUpdater serverListUpdater) {
return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class,
this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class,
config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList,
serverListFilter, serverListUpdater));
}
继续进入ZoneAwareLoadBalancer 的 chooseServer 方法
往下继续 进入 BaseLoadBalancer的 chooseServer 方法
其中 this.rule.choose(key) 根据规则器获取实例服务,根据IRule的类图可知,有各类的算法,默认采用的是轮询规则算法 ZoneAvoidanceRule
点击 this.irule.choose(key) 的方法,出现工厂模式选择,找不到对应的 ZoneAvoidanceRule, 则找父类
分析到这里,我们已经搞明白了Ribbon是如何运用负载均衡算法来从服务列表中获得一个目标服务进行访问的。但是还有一个疑惑,就是动态服务列表的更新和获取是在哪里实现呢?
(5)服务列表的加载过程
演示该例子,楼主是将服务列表配置在application.properties文件中,意味着在某个时候会加载这个列表,保存在某个位置,那它是在什么时候加载的呢?
1)进入 ZoneAwareLoadBalancer 的 chooseServer方法 调用了 super.chooseServer(key),此时咱们就需要看一下类图,看执行的父类是哪个? 有下面的类图可以看出执行的是 BaseLoadBalancer 的 chooseServer 方法
2)进入 BaseLoadBalancer 的 chooseServer 看到有
3)往下追踪下去,看到获取服务列表,该服务列表方法是 ILoadBalancer 提供的标准(添加服务实例,选择服务实例,获取服务实例列表等等)
4)进入 BaseLoadBalancer的 getAllServers 可以看到 获取的是 allServerList的缓存,所以追踪到这里,就明白了服务列表是放在BaseLoadBalancer上的缓存, BaseLoadBalancer的缓存有更新服务列表和所有服务列表。
但是在这引入了一个思考: 它们是在哪里赋值的?这里只有获取
allServerList 和 upServerList 在BaseLoadBalancer上有定义
5)继续往下看,到底这两个什么的缓存在哪里赋值?
在 BaseLoadBalancer 当前类找了找还是没有找到,然后再想想是不是在它的字类里面赋值呢?
然后楼主猜,测由第(4)点验证默认的负载均衡器是 ZoneAwareLoadBalancer ,所以回到 RibbonClientConfiguration 进入
ZoneAwareLoadBalancer 的构造器看,果然发现了新大陆 , 调用了它的父类的进行 restOfInit()
6)进入 restOfInit 看到两个很很重要的方法 :
enableAndInitLearnNewServersFeature() 定时更新服务实例
updateListOfServers() 获取服务实例
updateListOfServers 怎么获取实例的呢,咱们继续往下看
进入 serverListImpl.getUpdatedListOfServers() 的调用 发现又面临了选择,由最开始楼主使用的是本地配置服务的方式,所以进入ConfigurationBasedServerList 的实现类(如果用nacos eruka等 就进行对应的实例,楼主这边没有引用注册中心的包)
此处就是获取配置文件的配置的服务列表,以 listOfservers为后缀
例如这样的配置
回到 updateListOfServers 的 updateAllServerList() 更新服务列表,下面 setServersList(ls); 就是执行更新服务列表,验证服务实例是否可用,用到ping进行验证,往下就没啥可看了,小伙伴们可以自己看具体的实现
上面已经追踪完了获取服务实例的方法,下面咱们进入定时更新服务列表方法 enableAndInitLearnNewServersFeature()
进入 enableAndInitLearnNewServersFeature 可以看出用 schedule 进行做定时任务参数:
initialDelayMs :延时执行
refreshIntervalMs :更新时间间隔
单位为毫秒
initialDelayMs、refreshIntervalMs初始化赋值 如果没有自定义配置的话,则默认为 initialDelayMs = 1000 (1秒), refreshIntervalMs = 30 * 1000 (30秒)
自定义时间配置方式可以这么配置
7)还有一个比较重要的就是对服务器实例列表进行 ping 校验是否可用
定时ping默认10秒
回到 PingTask任务,执行任务时又看到了比较熟悉的身影 并发锁。到这里已经跟踪完了,下面小伙伴可以自己去看详细的ping 校验是否可用的逻辑
总结:整个链路咱们已经跟踪分析完毕了,下面做个小的总结流程图,以方便小伙伴进行源码的跟踪。源码跟踪时有点绕,希望小伙伴们可以坚持哦,加油!!!!!!!!!!