apollo源码分析 感知_Kitty中的动态线程池支持Nacos,Apollo多配置中心了

目录

  • 回顾昨日
  • nacos 集成
    • Spring Cloud Alibaba 方式
    • Nacos Spring Boot 方式
  • Apollo 集成
  • 自研配置中心对接
  • 无配置中心对接
  • 实现源码分析
    • 兼容 Apollo 和 Nacos NoClassDefFoundError
    • Apollo 自动刷新问题

回顾昨日

上篇文章 《一时技痒,撸了个动态线程池,源码放 Github 了》发出后很多读者私下问我这个能不能用到工作中,用肯定是可以用的,本身来说是对线程池的扩展,然后对接了配置中心和监控。

目前用的话主要存在下面几个问题:

  • 还没发布到 Maven 中央仓库(后续会做),可以自己编译打包发布到私有仓库(临时方案)
  • 耦合了 Nacos,如果你项目中没有用 Nacos 或者用的其他的配置中心怎么办?(本文内容)
  • 只能替换业务线程池,像一些框架中的线程池无法替换(构思中)

本文的重点就是介绍如何对接 Nacos 和 Apollo,因为一开始就支持了 Nacos,但是支持的方式是依赖了 Spring Cloud Alibaba ,如果是没有用 Spring Cloud Alibaba 如何支持,也是需要扩展的。

Nacos 集成

Nacos 集成的话分两种方式,一种是你的项目使用了 Spring Cloud Alibaba ,另一种是只用了 Spring Boot 方式的集成。

Spring Cloud Alibaba 方式

加入依赖:

com.cxytiandikitty-spring-cloud-starter-dynamic-thread-pool

然后在 Nacos 中增加线程池的配置,比如:

kitty.threadpools.executors[0].threadPoolName=TestThreadPoolExecutor
kitty.threadpools.executors[0].corePoolSize=4
kitty.threadpools.executors[0].maximumPoolSize=4
kitty.threadpools.executors[0].queueCapacity=5
kitty.threadpools.executors[0].queueCapacityThreshold=22

然后在项目中的 bootstrap.properties 中配置要使用的 Nacos data-id。

spring.cloud.nacos.config.ext-config[0].data-id=kitty-cloud-thread-pool.properties
spring.cloud.nacos.config.ext-config[0].group=BIZ_GROUP
spring.cloud.nacos.config.ext-config[0].refresh=true

Nacos Spring Boot 方式

如果你的项目只是用了 Nacos 的 Spring Boot Starter,比如下面:

com.alibaba.bootnacos-config-spring-boot-starter

那么集成的步骤跟 Spring Cloud Alibaba 方式一样,唯一不同的就是配置的加载方式。使用@NacosPropertySource 进行加载。

@NacosPropertySource(dataId = NacosConstant.HREAD_POOL, groupId = NacosConstant.BIZ_GROUP, autoRefreshed = true, type = ConfigType.PROPERTIES)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

然后需要在 bootstrap.properties 中关闭 Spring Cloud Alibaba Nacos Config 的自动配置。

spring.cloud.nacos.config.enabled=false

Apollo 集成

Apollo 的使用我们都是用它的 client,依赖如下:

  com.ctrip.framework.apolloapollo-client1.4.0

集成 Thread-Pool 还是老的步骤,先添加 Maven 依赖:

com.cxytiandikitty-spring-cloud-starter-dynamic-thread-pool

然后配置线程池配置的 namespace:

apollo.bootstrap.namespaces=thread-pool-config

Properties 不用加后缀,如果是 yaml 文件那么需要加上后缀:

apollo.bootstrap.namespaces=thread-pool-config.yaml

如果你项目中用到了多个 namespace 的话,需要在线程池的 namespace 中指定,主要是监听配置修改需要用到。

kitty.threadpools.apolloNamespace=thread-pool-config.yaml

自研配置中心对接

如果你们项目使用的是自研的配置中心那该怎么使用动态线程池呢?

最好的方式是跟 Nacos 一样,将配置跟 Spring 进行集成,封装成 PropertySource。

Apollo 中集成 Spring 代码参考:https://github.com/ctripcorp/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesProcessor.java[1]

因为配置类是用的@ConfigurationProperties,这样就相当于无缝集成了。

如果没和 Spring 进行集成,那也是有办法的,可以在项目启动后获取你们的配置,然后修改

DynamicThreadPoolProperties 配置类,再初始化线程池即可,具体步骤跟下面的无配置中心对接一致。DynamicThreadPoolManager 提供了 createThreadPoolExecutor()来创建线程池。

无配置中心对接

如果你的项目中没有使用配置中心怎么办?还是可以照样使用动态线程池的。

直接将线程池的配置信息放在项目的 application 配置文件中即可,但是这样的缺点就是无法动态修改配置信息了。

如果想有动态修改配置的能力,可以稍微扩展下,这边我提供下思路。

编写一个 Rest API,参数就是整个线程池配置的内容,可以是 Properties 文件也可以是 Yaml 文件格式。

这个 API 的逻辑就是注入我们的 DynamicThreadPoolProperties,调用 refresh()刷新 Properties 文件,调用 refreshYaml()刷新 Yaml 文件。

然后注入 DynamicThreadPoolManager,调用 refreshThreadPoolExecutor()刷新线程池参数。

实现源码分析

首先,我们要实现的需求是同时适配 Nacos 和 Apollo 两个主流的配置中心,一般有两种做法。

第一种:将跟 Nacos 和 Apollo 相关的代码独立成一个模块,使用者按需引入。

第二种:还是一个项目,内部做兼容。

我这边采取的是第二种,因为代码量不多,没必要拆分成两个。

需要在 pom 中同时增加两个配置中心的依赖,需要设置成可选(optional=true)。

com.alibaba.cloudspring-cloud-alibaba-nacos-configtrue
com.ctrip.framework.apolloapollo-client1.4.0true

然后内部将监听配置动态调整线程池参数的逻辑分开,ApolloConfigUpdateListener 和 NacosConfigUpdateListener。

在自动装配 Bean 的时候按需装配对应的 Listener。

@ImportAutoConfiguration(DynamicThreadPoolProperties.class)
@Configuration
public class DynamicThreadPoolAutoConfiguration {
@Bean
@ConditionalOnClass(value = com.alibaba.nacos.api.config.ConfigService.class)
public NacosConfigUpdateListener nacosConfigUpdateListener() {
return new NacosConfigUpdateListener();
}
@Bean
@ConditionalOnClass(value = com.ctrip.framework.apollo.ConfigService.class)
public ApolloConfigUpdateListener apolloConfigUpdateListener() {
return new ApolloConfigUpdateListener();
}

}

兼容 Apollo 和 Nacos NoClassDefFoundError

通过@ConditionalOnClass 来判断当前项目中使用的是哪种配置中心,然后装配对应的 Listener。上面的代码看上去没问题,在实际使用的过程去报了下面的错误:

Caused by: java.lang.NoClassDefFoundError: Lcom/alibaba/nacos/api/config/ConfigService;
at java.lang.Class.getDeclaredFields0(Native Method) ~[na:1.8.0_40]
at java.lang.Class.privateGetDeclaredFields(Class.java:2583) ~[na:1.8.0_40]
at java.lang.Class.getDeclaredFields(Class.java:1916) ~[na:1.8.0_40]
at org.springframework.util.ReflectionUtils.getDeclaredFields(ReflectionUtils.java:755) ~[spring-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
... 22 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.alibaba.nacos.api.config.ConfigService
at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[na:1.8.0_40]
at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_40]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) ~[na:1.8.0_40]
at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_40]
... 26 common frames omitted

比如我的项目是用的 Apollo,然后我集成了动态线程池,在启动的时候就报上面的错误了,错误原因是找不到 Nacos 相关的类。

但其实我已经用了@ConditionalOnClass 来判断,这个是因为你的 DynamicThreadPoolAutoConfiguration 类是生效的,Spring 会去装载 DynamicThreadPoolAutoConfiguration 类,DynamicThreadPoolAutoConfiguration 中有 NacosConfigUpdateListener 的实例化操作,而项目中又没有依赖 Nacos,所以就报错了。

这种情况我们需要将装配的逻辑拆分的更细,直接用一个单独的类去配置,将@ConditionalOnClass 放在类上。

这里我采用了静态内部类的方式,如果项目中没有依赖 Nacos,那么 NacosConfiguration 就不会生效,也就不会去初始化 NacosConfigUpdateListener。

@Configuration
@ConditionalOnClass(value = com.alibaba.nacos.api.config.ConfigService.class)
protected static class NacosConfiguration {
@Bean
public NacosConfigUpdateListener nacosConfigUpdateListener() {
return new NacosConfigUpdateListener();
}
}
@Configuration
@ConditionalOnClass(value = com.ctrip.framework.apollo.ConfigService.class)
protected static class ApolloConfiguration {
@Bean
public ApolloConfigUpdateListener apolloConfigUpdateListener() {
return new ApolloConfigUpdateListener();
}
}

这个地方我顺便提一个点,就是为什么我们平时要多去看看开源框架的源码。因为像这种适配多个框架的逻辑比较常见,那么一些开源框架中肯定也有类似的逻辑。如果你之前有看过其他的框架是怎么实现的,那么这里你就会直接采取那种方式。

比如 Spring Cloud OpenFeign 中对 Http 的客户端做了多个框架的适配,你可以用 HttpClient 也可以用 Okhttp,这不就是跟我们这个一样的逻辑么。

我们看下源码就知道了,如下图:

56c79984ff486460f0a8b6d1bc45c9c0.png
1a3374b7e7b7d3c700a86ae9b5ecc0f8.png

Apollo 自动刷新问题

在实现的过程中还遇到一个问题也跟大家分享下,就是 Apollo 中@ConfigurationProperties 配置类,在配置信息变更后不会自动刷新,需要配合 RefreshScope 或者 EnvironmentChangeEvent 来实现。

下图是 Apollo 文档的原话:

051d1bb85612c30634d238f4ec3f1470.png
图片

Nacos 刷新是没问题的,只不过在收到配置变更的消息时,配置信息还没刷新到 Bean 里面去,所以再刷新的时候单独起了一个线程去做,然后在这个线程中睡眠了 1 秒钟(可通过配置调整)。

如果按照 Apollo 文档中给的方式,肯定是可以实现的。但是不太好,因为需要依赖 Spring Cloud Context。主要是考虑到使用者并不一定会用到 Spring Cloud,我们的基础是 Spring Boot。

万一使用者就是在 Spring Boot 项目中用了 Apollo, 然后又用了我的动态线程池,这怎么搞?

最后我采用了手动刷新的方式,当配置发生变更的时候,我会通过 Apollo 的客户端,重新拉取整个配置文件的内容,然后手动刷新配置类。

config.addChangeListener(changeEvent -> {
ConfigFileFormat configFileFormat = ConfigFileFormat.Properties;
String getConfigNamespace = finalApolloNamespace;
if (finalApolloNamespace.contains(ConfigFileFormat.YAML.getValue())) {
configFileFormat = ConfigFileFormat.YAML;
// 去除.yaml后缀,getConfigFile时候会根据类型自动追加
getConfigNamespace = getConfigNamespace.replaceAll("." + ConfigFileFormat.YAML.getValue(), "");
}
ConfigFile configFile = ConfigService.getConfigFile(getConfigNamespace, configFileFormat);
String content = configFile.getContent();
if (finalApolloNamespace.contains(ConfigFileFormat.YAML.getValue())) {
poolProperties.refreshYaml(content);
} else {
poolProperties.refresh(content);
}
dynamicThreadPoolManager.refreshThreadPoolExecutor(false);
log.info("线程池配置有变化,刷新完成");
});

刷新逻辑:

public void refresh(String content) {
Properties properties = new Properties();
try {
properties.load(new ByteArrayInputStream(content.getBytes()));
} catch (IOException e) {
log.error("转换Properties异常", e);
}
doRefresh(properties);
}
public void refreshYaml(String content) {
YamlPropertiesFactoryBean bean = new YamlPropertiesFactoryBean();
bean.setResources(new ByteArrayResource(content.getBytes()));
Properties properties = bean.getObject();
doRefresh(properties);
}
private void doRefresh(Properties properties) {
Map dataMap = new HashMap((Map) properties);
ConfigurationPropertySource sources = new MapConfigurationPropertySource(dataMap);
Binder binder = new Binder(sources);
binder.bind("kitty.threadpools", Bindable.ofInstance(this)).get();
}

目前只支持 Properties 和 Yaml 文件配置格式。

感兴趣的 Star 下呗:https://github.com/yinjihuan/kitty[2]

关于作者:尹吉欢,简单的技术爱好者,《Spring Cloud 微服务-全栈技术与案例解析》, 《Spring Cloud 微服务 入门 实战与进阶》作者, 公众号 猿天地 发起人。个人微信 jihuan900,欢迎勾搭。

参考资料

[1]

PropertySourcesProcessor.java: https://github.com/ctripcorp/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesProcessor.java

[2]

kitty: https://github.com/yinjihuan/kitty

相关推荐

  • 嘘!异步事件这样用真的好么?

  • 一时技痒,撸了个动态线程池,源码放Github了

  • 熬夜之作:一文带你了解Cat分布式监控

  • 笑话:大厂都在用的任务调度框架我能不知道吗???

  • 为什么参与开源项目的程序员找工作时特别抢手?

后台回复 学习资料 领取学习视频

5a638550b1d1deaba7e60af3e94cf0be.png

如有收获,点个在看,诚挚感谢699ddfee1077f5ed3acb1e3ef7bbeb7d.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/504066.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c语言 把字符串转换为变量名_如何将抓取下来的unicode字符串转换为中文

如果抓取的数据是json数据,那么直接将抓取的数据用json格式输出出来就行了。如下:response requests.get(url, headersself.headers).json()如果是unicode字符串,那么请继续往下看大家有没有遇见抓取下来的数据是unicode字符串的?如下图所示…

c++ 读取访问权限冲突_Linux系统利用可执行文件的Capabilities实现权限提升

一、操作目的和应用场景Capabilities机制是在Linux内核2.2之后引入的,原理很简单,就是将之前与超级用户root(UID0)关联的特权细分为不同的功能组,Capabilites作为线程(Linux并不真正区分进程和线程)的属性存在,每个功能组都可以独…

Python 数据分析 Matplotlib篇 时间序列数据绘制折线图(第4讲)

Python 数据分析 Matplotlib篇 时间序列数据绘制折线图(第4讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹…

家装强电弱电布线图_家装水电施工标准(图文版),装修小白一眼也能看懂。...

如果把家比喻成一个人,房子是骨骼,那么水电路则相当于人体的血管和动脉,正因为他们的存在,才赋予家鲜活的生命。由此,水电施工自然也成为家装工程的重中之重。水电走线原则※ 水电弹线放样施工,使用切割机开…

0基础必看:如何轻松成为C语言高手

诞生于上世纪70年代的C语言是一门古老的语言了, 但作为一门底层语言,时至今日它仍然非常强大。学习C语言能够为学习其他更复杂的语言打下良好的基础,因为你在C语言中学习到的知识对几乎所有的编程语言都适用。下面就来看看如何开始用C语言编程吧。   工具   Mic…

python列表元素之和_python实现计算列表元素之和

目标:定义一个数字列表,并计算列表元素之和。 例如: 输入 : [12, 15, 3, 10] 输出 : 40 方法一:total 0 list1 [11, 5, 17, 18, 23] for ele in range(0, len(list1)): total total list1[ele] print("列表元素之和为: &…

双水泵轮换工作原理图_一用一备式冷凝水泵应急电源的设计与实现

为保障山西通州集团兴益化工有限公司10万吨甲醇工程一用一备式冷凝水泵的安全运行,专门设计动力负载应急电源。作者阐述了动力负载应急电源基本原理及其控制系统设计,经现场调试运行,动力负载应急电源很好满足生产现场要求。山西通州集团兴益…

基础学习——C语言递归解决分鱼问题

如有小伙伴想学习C语言基础,可以进群731871503进行交流学习,提升编程,共同进步 问题描述 A、B、C、D、E这5个人合伙夜间捕鱼,凌晨时都已经疲惫不堪,于是各自在河边的树丛中找地方睡着了。第二天日上三竿时&#xff0…

memkind版本查看_不同价位值得买轻薄本推荐~2020国庆篇

说起轻薄本,你理想中的一台优秀机型是什么样的?轻薄便携、质感上乘、高颜值、逼格、手感佳、续航持久、屏幕素质高、独立小键盘、性能激进……和游戏本的鲜明对比,注定了是两种不同的“菜”。2020年,随着AMD的崛起,部分…

main方法 如何去掉http debug日志_在MyBatis中如何使用collection标签实现嵌套查询?...

# 需求升级在上篇博客《一对多的关系,在MyBatis中如何映射?》中,我们实现了需求:根据用户id查询用户信息的同时获取用户拥有的角色。因为角色可以拥有多个权限,所以本篇博客我们升级需求为:根据用户id查询用…

movielens推荐系统_案例|推荐系统的评估指标

推荐系统能够为用户提供个性化体验,现在基本上各大电商平台、资讯平台都会用推荐系统为自家评价下的用户提供千人千面的服务。平均精度均值(Mean Average Precision,MAP)便是评估推荐系统性能的度量标准之一。但是,使用…

mysql群集配置_CentOS7 - 建立一个MySQL集群

Standing up a MySQL cluster此配方将指导您完成设置MySQL群集的过程。 通过跨多个系统划分数据并维护副本以避免单点故障,群集数据库可以应对可伸缩性和高可用性的挑战。集群的成员称为节点。 MySQL集群中有三种节点类型:数据节点,API节点和…

叮!您收到一份超值Java基础入门资料!

Java语言有什么特点?如何最大效率的学习?深浅拷贝到底有何区别?阿里巴巴高级开发工程师为大家带来Java系统解读,带你掌握Java技术要领,突破重点难点,入门面向对象编程,以详细示例带领大家Java基…

jop怎么读音英语怎么说_“春晚”英语怎么说?

大家都说近年来的春节年味越来越淡,每年陪老人一起看春晚、上网吐槽春晚,应该算是最有年味的一件事了吧!你期待今年的春晚吗?在春晚即将开播之际,先和我一起了解一些有关“春晚”的英语知识吧!01、“春晚”…

2019 年软件开发人员必学的编程语言 Top 3

这篇文章将探讨编程语言世界的现在和未来,这些语言让新一代软件开发者成为这个数字世界的关键参与者,他们让这个世界变得更健壮、连接更加紧密和更有意义。开发者要想在 2019 年脱颖而出,这三门语言一定要关注。 作为软件开发者,…

小数据量计算最大lyapunov代码_Software | 计算Lyapunov指数的GUI工具箱及函数包汇总...

二十年前读书时用过的Lyapunov Exponents Toolbox,推荐给做非线性动力学入门的同学们。那个时候不会编写程序,做分支图、算连续或者离散系统的Lyapunov指数,极其困难,还好有这个工具箱,慢慢看源程序学会写一点改进的程…

git 小乌龟 更新分支_git常用操作

Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。Git 与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式&a…

web 服务器 内存 影响_C/C++服务器开发常用的7大开源库,让你在同行中脱颖而出...

C/C服务器开发7大常用开源库,在这里简单介绍一下。1、OpenSSLOpenSSL是一个开放源代码的安全套接字层密码软件库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,应用程序可以使用这个包来进行安全通信,避免窃听&…

pandas 更改单元格的值_懂Excel轻松入门Python数据分析包pandas(二十四):连续区域...

此系列文章收录在公众号中:数据大宇宙 > 数据处理 >E-pd转发本文并私信我"python",即可获得Python资料以及更多系列文章(持续更新的)经常听别人说 Python 在数据领域有多厉害,结果学了很长时间,连数据处理都麻烦得…

产生式是蕴含式_独栋别墅~下沉式庭院设计

遇见美. 发现美创造美. 成就美有趣、有情奢享生活.创艺空间设计项目概述:这是一套婚房,从设计到装修完毕,历经三年。这是一生幸福开启的地方。一层为会客厅、室内花园、餐厅、茶室、老人房。原始房屋整体空间不够方正,空间利用率低…