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容器创建的方法:
1 2 3 4 5 6 7 8 9 10 11 12 | 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底层分析完成
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类