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底层分析完成
最新文章
- 图片浏览(CATransition)转场动画
- js中获取URL中指定的查询字符串
- hypervisor与VMware共存方法
- shell 中 &;&;和||的方法
- css实现背景颜色线性渐变
- jquery中append()、prepend()、after()、before()的区别详解
- TETRIS 项目开发笔记
- 从零开始学C++之运算符重载(三):完善String类([]、 +、 += 运算符重载)、>;>;和<;<;运算符重载
- Python之file方法
- SQLite: sql script demo
- Springboot+MyBatis+JPA集成
- single-cell RNA-seq 工具大全
- vue-04-组件
- vue2之 missing param for named route ";xxxx";
- C#将图片存放到SQL SERVER数据库中的方法
- Tomcat 一端口多项目,多端口多项目 server.xml
- db2错误代码大全
- php header函数导出excel表格
- KVM虚拟机安装报错 KVM is not available
- 【转】如何使用visual studio将你的程序打包成安装包
热门文章
- Tensorflow2(一)深度学习基础和tf.keras
- Java基础的基础,花1h看这一篇就够了!
- CSS3实现圆环进度条
- express综合用法
- 巧用HashMap一行代码统计单词出现次数
- vs2015创建项目没法选择.net2.0的问题
- 使用Android的硬件缩放技术优化执行效率
- 【原】“Error getting &#39;android:label&#39; attribute”
- [BUUOJ记录] [BJDCTF2020]The mystery of ip
- 做一名合格的DBA