Ribbon源码分析(二)-- 服务列表的获取和负载均衡算法分析
上一篇博客(https://www.cnblogs.com/yangxiaohui227/p/12614343.html)分享了ribbon如何实现对http://product/info/这个链接重构为http://ip:端口/info/的过程
本次来分析如何通过服务名称获取服务列表以及通过服务列表如何负载均衡获取一个服务的过程

一.服务列表获取过程调试:




小结:通过SpringClientFactory类的获取ILoadBalancer的方法跟踪,发现最终调用了起父类的getInstance的方法,详细流程如下:

接着我们跟进看看是如何通过服务名称获spring容器的:


map集合的结构如下:

下面我们看看spring容器是如何创建的:

我们回顾下,没学springboot之前,我们启动一个spring项目的写法:

由此可见,ribbon中创建spring容器和我们之前创建spring容器的方式都是一样的:
问题:SpringClientFactory这个类是什么时候被初始化的?容器中注册的类是哪些,服务名称存到了环境变量中,那么属性key是啥?
SpringClientFactory初始化时机:
上篇ribbon源码提到springboot的自动装配机制会实例化META-INF\spring.factories文件中配置的类


初始化SpringClientFactory会调用构造方法:

调用了父类的初始化方法:

此时我们再回来看看spring容器创建的方法:
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//省略部分代码
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType); //这里注册的类是:RibbonClientConfiguration.class
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,//ribbon
Collections.<String, Object>singletonMap(this.propertyName, name))); //属性名称为:ribbon.client.name
//省略部分代码
context.refresh();
return context;
}
从上面分析可以看到:每个服务都有一个spring容器,每个容器都会注册一个RibbonClientConfiguration.class配置类,同时将服务名称作为存到环境变量中,以便初始化RibbonClientConfiguration配置类相关的bean时,可以获取相应的环境变量
所以,重点的类就是RibbonClientConfiguration.class:
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {
/**
* Ribbon client default connect timeout.
*/
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
/**
* Ribbon client default read timeout.
*/
public static final int DEFAULT_READ_TIMEOUT = 1000;
/**
* Ribbon client default Gzip Payload flag.
*/
public static final boolean DEFAULT_GZIP_PAYLOAD = true;
@RibbonClientName //该注解的作用可以在环境变量中获取到服务名称,上面提过,创建spring容器时,已经将服务名称存到环境变量中了
private String name = "client";
@Autowired
private PropertiesFactory propertiesFactory;
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
return config;
}
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) { //这里就是我们以后要讲得负载均衡相关的bean了
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) { //这个是用来检测服务列表中的服务还有没活着
if (this.propertiesFactory.isSet(IPing.class, name)) {
return this.propertiesFactory.get(IPing.class, config, name);
}
return new DummyPing();
}
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerList<Server> ribbonServerList(IClientConfig config) { //服务列表相关的类
if (this.propertiesFactory.isSet(ServerList.class, name)) {
return this.propertiesFactory.get(ServerList.class, config, name);
}
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
return serverList;
}
@Bean
@ConditionalOnMissingBean
public ServerListUpdater ribbonServerListUpdater(IClientConfig config) { //这个是更新服务列表相关的bean
return new PollingServerListUpdater(config);
}
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config, //这个bean就是我们要找的bean了,我们上面提到获取服务列表就是从这个类中获取的
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
return this.propertiesFactory.get(ServerListFilter.class, config, name);
}
ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
filter.initWithNiwsConfig(config);
return filter;
}
@Bean
@ConditionalOnMissingBean
public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,
IClientConfig config, RetryHandler retryHandler) {
return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
}
@Bean
@ConditionalOnMissingBean
public RetryHandler retryHandler(IClientConfig config) {
return new DefaultLoadBalancerRetryHandler(config);
}
@Bean
@ConditionalOnMissingBean
public ServerIntrospector serverIntrospector() {
return new DefaultServerIntrospector();
}
@PostConstruct
public void preprocess() {
setRibbonProperty(name, DeploymentContextBasedVipAddresses.key(), name);
}
static class OverrideRestClient extends RestClient {
private IClientConfig config;
private ServerIntrospector serverIntrospector;
protected OverrideRestClient(IClientConfig config,
ServerIntrospector serverIntrospector) {
super();
this.config = config;
this.serverIntrospector = serverIntrospector;
initWithNiwsConfig(this.config);
}
@Override
public URI reconstructURIWithServer(Server server, URI original) {
URI uri = updateToSecureConnectionIfNeeded(original, this.config,
this.serverIntrospector, server);
return super.reconstructURIWithServer(server, uri);
}
@Override
protected Client apacheHttpClientSpecificInitialization() {
ApacheHttpClient4 apache = (ApacheHttpClient4) super.apacheHttpClientSpecificInitialization();
apache.getClientHandler().getHttpClient().getParams().setParameter(
ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);
return apache;
}
}
}

至此,我们知道了如何通过一个服务名称获取服务列表的过程,那么问题来了,服务列表是什么时候存到ZoneAwareLoadBalancer类中的呢?
先看看该类的继承体系:

下面我们跟进ZoneAwareLoadBalancer的初始化过程,看看做了些什么事情:



先看super(clientConfig, rule, ping);





public void runPinger() throws Exception {
if (!pingInProgress.compareAndSet(false, true)) {
return; // Ping in progress - nothing to do
}
Server[] allServers = null;
boolean[] results = null;
Lock allLock = null;
Lock upLock = null;
try {
allLock = allServerLock.readLock();
allLock.lock();
allServers = allServerList.toArray(new Server[allServerList.size()]);
allLock.unlock();
int numCandidates = allServers.length;
results = pingerStrategy.pingServers(ping, allServers); //所有的服务都去ping一下看看有没存活
final List<Server> newUpList = new ArrayList<Server>(); //还活着的服务
final List<Server> changedServers = new ArrayList<Server>(); //有变化服务
for (int i = 0; i < numCandidates; i++) {
boolean isAlive = results[i];
Server svr = allServers[i];
boolean oldIsAlive = svr.isAlive();
svr.setAlive(isAlive);
if (oldIsAlive != isAlive) {
changedServers.add(svr);
logger.debug("LoadBalancer [{}]: Server [{}] status changed to {}",
name, svr.getId(), (isAlive ? "ALIVE" : "DEAD"));
}
if (isAlive) {
newUpList.add(svr);
}
}
upLock = upServerLock.writeLock();
upLock.lock();
upServerList = newUpList; //活着的服务赋值给BaseLoadBalancer 的List<Server> upServerList
upLock.unlock();
notifyServerStatusChangeListener(changedServers); //通知监听器处理有变化的服务
} finally {
pingInProgress.set(false);
}
}
这个Ping服务的作用其实很简单,就是拿所有的服务列表去查询下服务是否还在线,将还在线的服务赋给BaseLoadBalancer的upServerList属性;
但是,我们看到容器中IPing的实现类是DummyPing

看到这个类判断服务是否存活的方法永远都返回true,所以这个Ping的功能默认的实现有跟没有什么区别:




看看是如何获取服务列表的:
DiscoveryEnabledNIWSServerList这个类中有个获取服务列表的方法(我这里是用Eureka作为注册中心)



上面过程总结:

思考: 服务列表保存到了BaseLoadBalancer中,那么,假如一个服务挂了,如何保证BaseLoadBalancer的服务列表会被更新?或者说,如果新增了一个服务,如何保证新增的服务会加到服务列表中
通过上面的讲解,我们发现有一个Ping机制会去检查服务是否还活着,但默认实现判断是否活着的时候,都是返回true,因此该功能基本没什么用,是否存在其他机制呢?
我们回到ZoneAwareLoadBalancer这个类的创建:


我们继续回到ZoneAwareLoadBalancer的创建过程中:


发现调用了PollingServerListUpdater的start方法



至此,Ribbon通过服务名称获取服务列表的底层原理分析完毕

代码跟踪:Server server = getServer(loadBalancer, hint);方法







至此,负载均衡算法获取一个服务的底层已经分析完毕;
思考:如果我想不使用默认的负载均衡算法或者非默认的IPing实现类,如何做?
我们回到spring容器注册的那个配置类RibbonClientConfiguration

因此这个PropertiesFactory类比较重要:
public class PropertiesFactory {
@Autowired
private Environment environment;
private Map<Class, String> classToProperty = new HashMap<>();
public PropertiesFactory() { //这些属性都是可以配置的
classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
classToProperty.put(ServerList.class, "NIWSServerListClassName");
classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
}
public boolean isSet(Class clazz, String name) {
return StringUtils.hasText(getClassName(clazz, name));
}
public String getClassName(Class clazz, String name) {
if (this.classToProperty.containsKey(clazz)) {
String classNameProperty = this.classToProperty.get(clazz);
String className = environment
.getProperty(name + "." + NAMESPACE + "." + classNameProperty);
return className;
}
return null;
}
@SuppressWarnings("unchecked")
public <C> C get(Class<C> clazz, IClientConfig config, String name) { //yml中配置了相应的类,这里会实例化化
String className = getClassName(clazz, name);
if (StringUtils.hasText(className)) {
try {
Class<?> toInstantiate = Class.forName(className);
return (C) SpringClientFactory.instantiateWithConfig(toInstantiate,
config);
}
catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unknown class to load " + className
+ " for class " + clazz + " named " + name);
}
}
return null;
}
}


由此可知,要想配置自定义的bean,语法是服务名.ribbon.xxxx,如:



至此,Ribbon底层分析完成
Ribbon源码分析(二)-- 服务列表的获取和负载均衡算法分析的更多相关文章
- Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题
4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...
- 框架-springmvc源码分析(二)
框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...
- Tomcat源码分析二:先看看Tomcat的整体架构
Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...
- 十、Spring之BeanFactory源码分析(二)
Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...
- 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>
目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...
- Vue源码分析(二) : Vue实例挂载
Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...
- [源码解析] 并行分布式任务队列 Celery 之 负载均衡
[源码解析] 并行分布式任务队列 Celery 之 负载均衡 目录 [源码解析] 并行分布式任务队列 Celery 之 负载均衡 0x00 摘要 0x01 负载均衡 1.1 哪几个 queue 1.1 ...
- Ribbon源码分析(一)-- RestTemplate 以及自定义负载均衡算法
如果只是想看ribbon的自定义负载均衡配置,请查看: https://www.cnblogs.com/yangxiaohui227/p/13186004.html 注意: 1.RestTemplat ...
- spring源码分析(二)Aop
创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...
随机推荐
- Java后台服务慢优化杂谈
Java后台服务慢优化杂谈 前言 你是否遇到过这样的场景,当我们点击页面某个按钮后,页面一直loading,要等待好几分钟才出结果的画面,有时直接502或504,作为一个后台开发,看到自己开发的系统是 ...
- “网络巨轮”BGP的高级装备(增强配置)
引入 如下图在很多时候,BGP建立对等体的时候,要去建立大量的EBGP和IBGP对等体,IBGP还要全连接,这就给我们带来了大量重复的工作,路由表就会变得很庞大,区域内和区域之间就会很难管理,以下这些 ...
- TX-LCN 分布式事务框架
第十章 TX-LCN 分布式事务框架 (Spring Cloud 高级) 一. 什么是分布式事务 分布式事务是指事务的参与者.支持事务的服务器.资源服务器以及事务管理器分别位 于不同的分布式系统的不同 ...
- Javascript数组与函数初识
1 - 数组 1.1 数组的概念 数组可以把一组相关的数据一起存放,并提供方便的访问(获取)方式. 数组是指一组数据的集合,其中的每个数据被称作元素,在数组中可以存放任意类型的元素.数组是一种将一组数 ...
- Git使用感悟
前言 分支介绍 我们现在开发的分支一般是这样的(基于上面那张图片的): master:上线用的 dev:开发用的 featature_xxx:开发用的 test:测试用的 hotfix:修复bug的 ...
- 支付-微信h5
背景 h5支付分两种 1.浏览器 2.app 浏览器里的h5,最终也会跳转到app. 而app里的h5,本质是公众号.在微信里叫公众号,支付宝叫服务窗. 这里主要讲微信h5. 核心原理 最终目标是下单 ...
- 13props 对象
props: { homeData: { type: Object, required: true } }, 父组件传递给子组件是对象homeData或者数组: homeData={name:'zs' ...
- Sender(agumaster_crawler)->RabbitMq->Reciever(agumaster)
发送方: package com.heyang.agumasterCrawler; import java.util.List; import org.springframework.beans.fa ...
- Java线程本质
java当中的线程和操作系统的线程是什么关系? 关于操作系统的线程 linux操作系统的线程控制原语 int pthread create(pthread t *thread, const pthre ...
- 业务级别MySQL
业务级别MySQL 目录 业务级别MySQL 1. 权限管理和备份 1. 用户管理 1. SQLyog可视化操作 2. SQL命令操作 2. MySQL备份 3. 规范数据库设计 1. 为什么需要设计 ...