一、先看下流程图
备注:红色后面都为拦截器的逻辑,主要是加载配置文件【LoadBalancerAutoConfiguration】,对发送http请求的RestTemplate进行包装拦截,逻辑拦在拦截器里面。
二、LoadBalancerAutoConfiguration
负载均衡用到配置类:LoadBalancerAutoConfiguration
LoadBalancerAutoConfiguration主要做2个事情。
1、生成Bean: LoadBalancerInterceptor 拦截器
2、生成Bean: RestTemplateCustomizer 这个是RestTemplate的包装对象,里面拿到上面生成的拦截器,
然后把拦截器添加到RestTemplate里面去。restTemplate每次执行方法请求时,都会调用【intercept方法】执行拦截,
在intercept方法里面,通过【服务名】获取了该服务对应的【负载均衡器ILoadBalancer】的实例对象,
然后调用该实例的chooseServer方法获取一个具体【服务实例】,然后发送http请求服务提供者。
三、LoadBalancerClient
在Ribbon中有个非常重要的组件LoadBalancerClient,它是【负载均衡的客户端】,通过LoadBalancerClient的方法,
我们只需要传一个【服务名】,那么LoadBalancerClient就会根据【服务名】返回对应的【负载均衡器】
而LoadBalancerClient是一个接口,真正的实现类正是RibbonLoadBalancerClient,逻辑都在choos方法里面,
先通过【服务Id】拿到ILoadBalancer,这个也叫【负载均衡器】
下面看下ILoadBalancer【负载均衡器】:ILoadBalancer的实现类有ZoneAwareLoadBalancer,
主要包含以下2个内容
1、里面的allServerList列表已经缓存了所有的服务列表,
2、里面还有个属性IRule,这个就是真正负载均衡算法的核心
当负载均衡器调用chooseServer方法时,会通过负载均衡算法IRule,返回哪一个具体的Server,这个就是我们最终要调用的服务Ip+端口。
ILoadBalancer接口定义了Ribbon中核心的两项内容,【服务获取】与【服务选择】,
可以说ILoadBalancer是Ribbon中最重要的一个组件,它起到了承上启下的作用,既要连接 Eureka获取服务地址,
又要调用IRule利用负载均衡算法选择服务
服务获取
Ribbon在选择之前需要获取服务列表,而Ribbon本身不具有服务发现的功能,
所以需要借助Eureka来解决获取服务列表的问题, 其实在获取实例的过程中,
1、先他得到一个EurekaClient的实例,然后借助EurekaClient的服务发现功能,来获取服务的实例列表。
2、在获取了实例信息后,判断服务的状态如果为UP,那么最终将它加入serverList中。
在获取得到serverList后,会进行缓存操作。首先进入BaseLoadBalancer的setServerList方法,
里面会往下面2个缓存赛数据,将拉取的serverList赋值给缓存列表allServerList。
protected volatile List<Server> allServerList = Collections.synchronizedList(new ArrayList<Server>());
protected volatile List<Server> upServerList = Collections.synchronizedList(new ArrayList<Server>());
Ribbon从Eureka中得到了服务列表,缓存在本地List后,存在一个问题,如何保证在调用服务的时候服务仍然处于可用状态,
也就是说应该如何解决缓存列表脏读问题?
在默认负载均衡器ZoneAwareLoadBalancer#setupPingTask方法中,里面创建了一个定时任务,使用ping的方式判断服务是否可用。
怎么ping的呢,不是判断网络通不通,这个ping只是他的方法这么叫,实际上还是通过查询EurekaServer返回服务的状态来判断。
因为本地的serverList为缓存值,可能与eureka中不同,所以从eureka中去查询该实例的状态,
如果eureka里面显示该实例状态为UP,就返回true,说明服务可用。
通过调用EurekaServer的接口,获取到服务的状态列表后,进行循环,如果状态改变,加入到changedServers中,
并且把所有可用服务加入newUpList,最终更新upServerList中缓存值
除了使用ping的方式去检测服务是否在线外,Ribbon还使用了别的方式来更新服务列表。使用了定时调度线程池调用了PollingServerListUpdater#start方法,来进行更新服务操作
Ribbon为了解决服务列表的脏读现象,采用了两种手段:
1、更新列表 (定时任务)
2、ping机制 (定时任务)
更新机制和ping机制功能基本重合,并且在ping的时候不能执行更新,在更新的时候不能运行ping,所以很难检测到ping失败的情况。
负载均衡算法
Ribbon默认使用的是ZoneAvoidanceRule,也叫【区域亲和】负载均衡算法,优先调用一个zone区间中的服务,
并使用轮询算法。
当然,也可以由我们自己实现IRule接口,重写其中的choose方法来实现自己的负载均衡算法,然后通过@Bean的方式注入到spring容器中。
当然也可以将不同的服务应用不同的IRule策略,这里需要注意的是,Spring cloud的官方文档中提醒我们,
如果多个微服务要调用不同的IRule,那么创建出IRule的配置类不能放在ComponentScan的目录下面,
这样所有的微服务都会使用这一个策略。
总结
综上所述,在Ribbon的负载均衡中,大致可以分为以下几步:
1、拦截请求,通过请求中的url地址,截取服务名称
2、通过LoadBalancerClient获取ILoadBalancer
3、使用Eureka获取服务列表
4、通过IRule负载均衡策略选择具体服务
5、ILoadBalancer通过IPing及定时更新机制来维护服务列表
6、重构该url地址,最终调用HttpURLConnection发起请求
了解了整个调用流程后,我们更容易明白为什么Ribbon叫做客户端的负载均衡。与nginx服务端负载均衡不同,
1、nginx在使用反向代理具体服务的时候,调用端不知道都有哪些服务。
2、Ribbon在调用之前,已经知道有哪些服务可用,直接通过本地负载均衡策略调用即可。
而在实际使用过程中,也可以根据需要,结合两种方式真正实现高可用。