上一篇博客(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源码分析(二)-- 服务列表的获取和负载均衡算法分析的更多相关文章

  1. Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  2. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  3. Tomcat源码分析二:先看看Tomcat的整体架构

    Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...

  4. 十、Spring之BeanFactory源码分析(二)

    Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...

  5. 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>

    目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...

  6. Vue源码分析(二) : Vue实例挂载

    Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...

  7. [源码解析] 并行分布式任务队列 Celery 之 负载均衡

    [源码解析] 并行分布式任务队列 Celery 之 负载均衡 目录 [源码解析] 并行分布式任务队列 Celery 之 负载均衡 0x00 摘要 0x01 负载均衡 1.1 哪几个 queue 1.1 ...

  8. Ribbon源码分析(一)-- RestTemplate 以及自定义负载均衡算法

    如果只是想看ribbon的自定义负载均衡配置,请查看: https://www.cnblogs.com/yangxiaohui227/p/13186004.html 注意: 1.RestTemplat ...

  9. spring源码分析(二)Aop

    创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...

随机推荐

  1. 基于id3算法根据房价数据进行画图预测python

    根据已给的波士顿房价数据,对波斯顿房价进行预测.即,实现给出若干条件(如房间数.社区的低收入阶层的比率和镇上学生与教师数量比例的部分数据),要能说出给出的条件是否能够有效进行预测,如可以做有效预测,则 ...

  2. mysql建数据库的字符集与排序规则说明

    本文转自https://blog.csdn.net/qq_38224812/article/details/80745868,感谢作者,自己留存已备他日使用 1.字符集说明: 一般选择utf8.下面介 ...

  3. Cortex-M4的快速memcpy,根据数据对齐情况自动优化,速度为普通memcpy的1.3到5.2倍

    代码:https://github.com/gamesun/memcpy_fast memcpy_fast与memcpy速度比较 测试方法 memcpy_fast(dest + a, src + b, ...

  4. Android中的Activity四种启动模式(launchMode)

    转载:http://blog.csdn.net/cjjky/article/details/7533110          我们在开发项目的过程中,会涉及到该应用中多个Activity组件之间的跳转 ...

  5. JetBrain破解

    https://blog.csdn.net/u014044812/article/details/78727496 https://jetlicense.nss.im/ https://zhile.i ...

  6. Unity透明地形

    http://answers.unity3d.com/questions/1162779/unity-5-transparent-terrain-shader.html http://answers. ...

  7. java初探(1)之登录总结

    登录总结 前几章总结了登录各个步骤中遇到的问题,现在完成的做一个登录的案例,其难点不在于实现功能,而在于抽象各种功能模块,提高复用性,较低耦合度. 前端页面: 对于前端页面来说,不是后端程序员要考虑的 ...

  8. CSS中的包含块

    1.初始包含块,浏览器viewport大小 2.非根元素,position:relative/static,包含块为最近的块级框,表格单元或行内祖先框的内容区 3.非根元素,position:abso ...

  9. web前端笔记(包含php+laravel)

    概况 熟悉HTML5.CSS3.JavaScript.ES6规范 熟悉JQuery框架 熟悉BootStrap 熟悉Less.Sass 熟悉Vue 熟悉Git postman Bootstrap 布局 ...

  10. oracle数据库创建数据库实例-九五小庞

    oracle数据库创建数据库实例