客户端配置延迟加载和启用饥饿加载

该博文为Ribbon与Eureka整合分析系列文章中的第四篇,主要介绍客户端所需配置,默认情况下,如何在创建客户端时,才加载配置,以及如何在启动时,加载客户端配置(即饥饿加载)。

文章目录

  • 客户端配置延迟加载和启用饥饿加载
  • 一、客户端配置加载
  • 二、客户端饥饿加载
  • 总结

一、客户端配置加载

最近在调试和研究Ribbon源码的时候,发现,客户端所需配置,默认情况下,并不是随着容器启动而加载,而是在使用时(请求经过LoadBalancerInterceptor拦截器处理),才进行加载。之所以能达到这个效果,是借助于Spring的AnnotationConfigApplicationContext类。该类的注释信息如下:

Standalone application context, accepting annotated classes as input - in particular {@link Configuration @Configuration}-annotated classes, but also plain {@link org.springframework.stereotype.Component @Component} types and JSR-330 compliant classes using {@code javax.inject} annotations. Allows for registering classes one by one using {@link #register(Class…)} as well as for classpath scanning using {@link #scan(String…)}.

In case of multiple {@code @Configuration} classes, @{@link Bean} methods defined in later classes will override those defined in earlier classes. This can be leveraged to deliberately override certain bean definitions via an extra {@code @Configuration} class.

即通过AnnotationConfigApplicationContext,达到Bean注册效果。另外,通过其构造方法上注释,需要先调用register方法,然后调用refresh方法。

/*** Create a new AnnotationConfigApplicationContext that needs to be populated* through {@link #register} calls and then manually {@linkplain #refresh refreshed}.*/public AnnotationConfigApplicationContext() {this.reader = new AnnotatedBeanDefinitionReader(this);this.scanner = new ClassPathBeanDefinitionScanner(this);}

如下代码,是Ribbon根据客户端serviceId,创建AnnotationConfigApplicationContext的方法。其中register,都是从configurations属性获取需要注册的内容,且分为两部分,key为客户端serviceId的注册以及以default开头的注册。

protected AnnotationConfigApplicationContext createContext(String name) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();if (this.configurations.containsKey(name)) {for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {context.register(configuration);}}for (Map.Entry<String, C> entry : this.configurations.entrySet()) {if (entry.getKey().startsWith("default.")) {for (Class<?> configuration : entry.getValue().getConfiguration()) {context.register(configuration);}}}context.register(PropertyPlaceholderAutoConfiguration.class,this.defaultConfigType);context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,Collections.<String, Object> singletonMap(this.propertyName, name)));if (this.parent != null) {// Uses Environment from parent as well as beanscontext.setParent(this.parent);}context.setDisplayName(generateDisplayName(name));context.refresh();return context;}

那么,configurations这个属性值,是如何生成的呢?这里涉及到ImportBeanDefinitionRegistrar接口,该接口注释信息如下

Interface to be implemented by types that register additional bean definitions when processing @{@link Configuration} classes. Useful when operating at the bean definition level (as opposed to {@code @Bean} method/instance level) is desired or necessary.

Along with {@code @Configuration} and {@link ImportSelector}, classes of this type may be provided to the @{@link Import} annotation (or may also be returned from an {@code ImportSelector}).

An {@link ImportBeanDefinitionRegistrar} may implement any of the following {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective methods will be called prior to {@link #registerBeanDefinitions}:

RIbbon针对于该接口,提供RibbonClientConfigurationRegistrar实现类,用于处理RibbonClients以及RibbonClient注解。然后将收集到的数据信息,注册为RibbonClientSpecification bean对象。

@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {Map<String, Object> attrs = metadata.getAnnotationAttributes(RibbonClients.class.getName(), true);if (attrs != null && attrs.containsKey("value")) {AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");for (AnnotationAttributes client : clients) {registerClientConfiguration(registry, getClientName(client),client.get("configuration"));}}if (attrs != null && attrs.containsKey("defaultConfiguration")) {String name;if (metadata.hasEnclosingClass()) {name = "default." + metadata.getEnclosingClassName();} else {name = "default." + metadata.getClassName();}registerClientConfiguration(registry, name,attrs.get("defaultConfiguration"));}Map<String, Object> client = metadata.getAnnotationAttributes(RibbonClient.class.getName(), true);String name = getClientName(client);if (name != null) {registerClientConfiguration(registry, name, client.get("configuration"));}}private void registerClientConfiguration(BeanDefinitionRegistry registry,Object name, Object configuration) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RibbonClientSpecification.class);builder.addConstructorArgValue(name);builder.addConstructorArgValue(configuration);registry.registerBeanDefinition(name + ".RibbonClientSpecification",builder.getBeanDefinition());}

在解析这里,基于RibbonClients注解的defaultConfiguration属性,增加default前缀进行标识,其余的通过value(或name属性,对应于客户端serviceId),作为RibbonClientSpecification的name属性。

经过上述分析,默认情况下,创建客户端配置时,会通过RibbonAutoConfiguration和RibbonEurekaAutoConfiguration两个配置类,完成客户端注册操作。

在上述getInstance处,在AnnotationConfigApplicationContext创建完成之后,能根据指定类型,从客户端上下文环境中,获取指定的配置bean。

二、客户端饥饿加载

默认情况下,客户端所需配置,是在第一次请求到来之后,才进行创建。这种情况下,会增加第一次请求处理时长,从而增加请求处理超时的风险。为此,Ribbon增加RibbonEagerLoadProperties配置类,用于指定是否需要在容器启动时加载指定客户端所需配置信息。默认是关闭的。

@ConfigurationProperties(prefix = "ribbon.eager-load")
public class RibbonEagerLoadProperties {private boolean enabled = false;private List<String> clients;public boolean isEnabled() {return enabled;}public void setEnabled(boolean enabled) {this.enabled = enabled;}public List<String> getClients() {return clients;}public void setClients(List<String> clients) {this.clients = clients;}
}

该配置类,在RibbonAutoConfiguration配置类中使用。当ribbon.eager-load.enabled设置为true,将开启如下配置。而RibbonApplicationContextInitializery为Spring事件监听处理类,用于监听ApplicationReadyEvent事件。

@Bean@ConditionalOnProperty(value = "ribbon.eager-load.enabled")public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {return new RibbonApplicationContextInitializer(springClientFactory(),ribbonEagerLoadProperties.getClients());}

在接收到ApplicationReadyEvent事件后,调用init方法,先创建客户端所需上下文环境。

protected void initialize() {if (clientNames != null) {for (String clientName : clientNames) {this.springClientFactory.getContext(clientName);}}}

到此,Ribbon的懒加载配置项如下:

ribbon.eager-load.enabled=true
ribbon.eager-load.clients=service_id1,service_id2

总结

客户端配置加载

通过RibbonClientConfigurationRegistrar收集RibbonClients和RibbonClient注解信息,并将其配置为RibbonClientSpecification Bean。然后基于AnnotationConfigApplicationContext,分别针对于客户端,构建其上下文环境信息,然后在获取IRule等配置。

客户端饥饿加载

默认情况下,客户端所需配置,是在接收到请求之后,才会进行创建。这种模式,会增加第一个请求处理时长,从而增加第一个请求处理超时的风险,Ribbon增加饥饿加载配置,通过ribbon.eager-load.enabled=true,开启饥饿加载,同时通过ribbon.eager-load.clients配置项指定,那些客户端需要饥饿加载。

Ribbon与Eureka整合分析(四)、客户端配置延迟加载和启用饥饿加载相关推荐

  1. Spring Cloud Alibaba - 09 Ribbon 饥饿加载及其他配置参数解读

    文章目录 解决Ribbon 第一次调用耗时高的配置 超时时间相关参数 并发参数 重试 源码 解决Ribbon 第一次调用耗时高的配置 开启饥饿加载 # ribbon 饥饿加载 解决第一次耗时多的问题 ...

  2. Spring Cloud实战小贴士:Ribbon的饥饿加载(eager-load)模式

    我们在使用Spring Cloud的Ribbon或Feign来实现服务调用的时候,如果我们的机器或网络环境等原因不是很好的话,有时候会发现这样一个问题:我们服务消费方调用服务提供方接口的时候,第一次请 ...

  3. Ribbon负载均衡策略、懒加载及饥饿加载

    目录 一.负载均衡概述 二.负载均衡策略 三.懒加载及饥饿加载 一.负载均衡概述 在业务初期,我们一般会先使用单台服务器对外提供服务.随着业务流量越来越大,单台服务器无论如何优化,无论采用多好的硬件, ...

  4. Ribbon负载均衡 饥饿加载

    需要两份或多份相同的性质的服务的模块,地址与端口不同,服务模块名称相同,访问者通过名称进行访问 让访问者进行负载均衡的选择 在Eureka中发送这个路径我使用的是名字,而不是ip,这里面的负载均衡就是 ...

  5. nginx 配置静态文件目录_nginx缓存静态资源,只需几个配置提升10倍页面加载速度...

    nginx缓存静态资源,只需几个配置提升10倍页面加载速度 首先我们看图说话 这是在没有缓存的情况下,这个页面发送了很多静态资源的请求: 可以看到,静态资源占用了整个页面加载用时的90%以上,而且这个 ...

  6. Ribbon开启饥饿加载

    Ribbon开启饥饿加载 Ribbon默认开启的是懒加载,这时第一次访问的时候相比之后会非常慢,以我的结果为例,懒汉模式下第一次请求耗时500ms,第二次却只有20ms,这样就可以提高体验感. app ...

  7. 【Android NDK 开发】Android.mk 配置动态库 ( Android Studio 配置动态库 | 动态库加载版本限制 | 本章仅做参考推荐使用 CMake 配置动态库 )

    文章目录 I . Android Studio 中使用 Android.mk 配置动态库 总结 II . 第三方动态库来源 III . 配置 Android.mk 构建脚本路径 IV . 预编译 第三 ...

  8. 项目遇到的问题总结(四):单页面首屏加载慢解决方案

    项目遇到的问题总结(四):单页面首屏加载慢解决方案 参考文章: (1)项目遇到的问题总结(四):单页面首屏加载慢解决方案 (2)https://www.cnblogs.com/myfirstboke/ ...

  9. GIS开发之二维地下管线综合管理系统(Arcgis)第四节 使用arcgis api for js 加载天地图

    GIS开发之二维地下管线综合管理系统(Arcgis)第四节 使用arcgis api for js 加载天地图 核心js文件 调用方式 调用结果 核心js文件 #通过定义加载天地图js文件,引用并组织 ...

最新文章

  1. 马斯克Neuralink联合创始人宣布离职,此前尚未推出上市产品
  2. python简单编程例子-中文方便就用中文编程!Python图形界面开发实例
  3. [转帖]IP地址、子网掩码、网络号、主机号、网络地址、主机地址以及ip段/数字-如192.168.0.1/24是什么意思?...
  4. Java程序员必须知道的Java10特性
  5. c .net ajax,Asp.net mvc 2中使用Ajax的三种方式
  6. 口算练习题(洛谷P1957题题解,Java语言描述)
  7. iptv管理系统php制作,双子星IPTV管理系统搭建教程
  8. Java中sleep,wait的区别
  9. hadoop组件---数据仓库---hive简介
  10. 第 2 届河北省大学生程序设计竞赛(河北省赛)-Problem C. icebound 的账单-题解
  11. Python和R之间转换的基本指南:使用Python或R知识来有效学习另一种语言的简单方法。
  12. 七代处理器装win7_Intel7代处理器 win10重装win7后无法安装显卡声卡驱动的解决方案 | A小可私人狗窝...
  13. CSS/HTML制作电影网站中的电影卡片
  14. Kal设置启动时输出日志,不显示logo
  15. 解决Android v4、v7包导入标红问题import android.support.v4.app.ActivityCompat;import android.support.v7.app
  16. 快上车,老司机带你实现后台录像功能
  17. J9数字论:什么是DAO模式?DAO发展过程的阻碍
  18. RSS的基本使用 - rsslibj
  19. 序列3:序列方法实操上
  20. html5 photo gallery,jquery图片查看器 jquery-photo-gallery

热门文章

  1. VsCode+OpenOCD 开发stm32系列
  2. 不等距双杆模型_电磁感应之双杆模型ppt课件
  3. 麦肯锡三部曲_《学会提问:麦肯锡工作法》—读书笔记导图分享
  4. 当case when then else end 语句遇上sum或count等统计函数
  5. rgb sw 线主板接口在哪_自带RGB风扇,支持神光同步的九州风神小堡垒120I水冷散热器...
  6. [nlp] sentiment analysis(情感分析)
  7. vue-i18n的使用,前端实现中英文切换
  8. PDF生成插件--TcPDF
  9. ERROR: node with name rabbit already running的解决方法
  10. MySQL删除数据后,释放磁盘空间