上一篇博客(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. 关于Junit4 和 Junit5.4

    1.    Junit5.4 主要是用于Maven框架 , 对普通类的测试是不可以的. 如这个, junit4可以, junit5.4不可以. 2. Junit不可以使用 static 静态方法. 关 ...

  2. python yaml文件数据按原有的数据顺序dump

    yml文件的更新后工具类: import os import yaml class YamlUtils(): def __init__(self,folder_name='config'): self ...

  3. “大地主”IPv6的地址实验配置

    上一篇文章,我们简单的介绍了一下IPv6协议的邻居发现BD和简单的基础配置,这里我们通过实验观察一下 IPv6邻居发现中会发送的报文,顺便熟悉一下,新的地址配置 根据拓扑图配置地址 这里原理和IPv4 ...

  4. 8点了解Java服务端单元测试

    一. 前言 单元测试并不只是为了验证你当前所写的代码是否存在问题,更为重要的是它可以很大程度的保障日后因业务变更.修复Bug或重构等引起的代码变更而导致(或新增)的风险. 同时将单元测试提前到编写正式 ...

  5. 【HttpRunner v3.x】笔记—7. 测试用例-teststeps-RunTestCase

    以前我在写接口自动化用例的时候,为了保证用例的独立性,需要在setUp里调用各种满足用例的一些前置条件,其中就不乏调用了其他测试用例中的方法. 而httprunner也是支持了这一项很重要的特性,通过 ...

  6. Monkey and Banana(dp,求最长的下降子序列)

    A group of researchers are designing an experiment to test the IQ of a monkey. They will hang a bana ...

  7. Robot Framework自动化测试框架核心指南-如何做好自动化测试平台框架的设计

    自动化测试如果需要能高效快速的支撑软件项目的测试,项目的快速迭代以及上线,除了以上我们介绍的需要许多的Lib来支持以及需要高效的去编写自动化测试案例外,还需要一个好的自动化测试框架平台来支撑我们的自动 ...

  8. 编程体系结构(01):Java编程基础

    一.数据类型 1.基础类型 整型:byte .short .int .long 浮点型:float.double 字节型:char 2.包装类型 Byte,Short,Integer,Long Flo ...

  9. 如何手动合并ts视频文件

    手动合并ts视频文件一不需要编程,二不需要下载什么特殊软件,用一些最普通的软件即可. 工具: 1.Chrome浏览器 2.迅雷 3.CMD命令行工具 步骤 一.打开迅雷,先确保没有别的在下载. 二.播 ...

  10. 使用java爬虫从雪球网下载股票数据

    雪球网也是采用Ajax方式展示数据,我依然采用开发者工具查看其访问地址和返回数据. 访问使用到的库是jsoup,解析返回的json用的类库是jackson,二者的依赖是: <!-- jsoup ...