日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2024/7/23 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 apollo源码分析 感知_Kitty中的动态线程池支持Nacos,Apollo多配置中心了 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

  • 回顧昨日
  • nacos 集成
    • Spring Cloud Alibaba 方式
    • Nacos Spring Boot 方式
  • Apollo 集成
  • 自研配置中心對(duì)接
  • 無(wú)配置中心對(duì)接
  • 實(shí)現(xiàn)源碼分析
    • 兼容 Apollo 和 Nacos NoClassDefFoundError
    • Apollo 自動(dòng)刷新問(wèn)題

回顧昨日

上篇文章 《一時(shí)技癢,擼了個(gè)動(dòng)態(tài)線程池,源碼放 Github 了》發(fā)出后很多讀者私下問(wèn)我這個(gè)能不能用到工作中,用肯定是可以用的,本身來(lái)說(shuō)是對(duì)線程池的擴(kuò)展,然后對(duì)接了配置中心和監(jiān)控。

目前用的話主要存在下面幾個(gè)問(wèn)題:

  • 還沒(méi)發(fā)布到 Maven 中央倉(cāng)庫(kù)(后續(xù)會(huì)做),可以自己編譯打包發(fā)布到私有倉(cāng)庫(kù)(臨時(shí)方案)
  • 耦合了 Nacos,如果你項(xiàng)目中沒(méi)有用 Nacos 或者用的其他的配置中心怎么辦?(本文內(nèi)容)
  • 只能替換業(yè)務(wù)線程池,像一些框架中的線程池?zé)o法替換(構(gòu)思中)

本文的重點(diǎn)就是介紹如何對(duì)接 Nacos 和 Apollo,因?yàn)橐婚_(kāi)始就支持了 Nacos,但是支持的方式是依賴了 Spring Cloud Alibaba ,如果是沒(méi)有用 Spring Cloud Alibaba 如何支持,也是需要擴(kuò)展的。

Nacos 集成

Nacos 集成的話分兩種方式,一種是你的項(xiàng)目使用了 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

然后在項(xiàng)目中的 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 方式

如果你的項(xiàng)目只是用了 Nacos 的 Spring Boot Starter,比如下面:

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

那么集成的步驟跟 Spring Cloud Alibaba 方式一樣,唯一不同的就是配置的加載方式。使用@NacosPropertySource 進(jìn)行加載。

@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 中關(guān)閉 Spring Cloud Alibaba Nacos Config 的自動(dòng)配置。

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

如果你項(xiàng)目中用到了多個(gè) namespace 的話,需要在線程池的 namespace 中指定,主要是監(jiān)聽(tīng)配置修改需要用到。

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

自研配置中心對(duì)接

如果你們項(xiàng)目使用的是自研的配置中心那該怎么使用動(dòng)態(tài)線程池呢?

最好的方式是跟 Nacos 一樣,將配置跟 Spring 進(jìn)行集成,封裝成 PropertySource。

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

因?yàn)榕渲妙愂怯玫?#64;ConfigurationProperties,這樣就相當(dāng)于無(wú)縫集成了。

如果沒(méi)和 Spring 進(jìn)行集成,那也是有辦法的,可以在項(xiàng)目啟動(dòng)后獲取你們的配置,然后修改

DynamicThreadPoolProperties 配置類,再初始化線程池即可,具體步驟跟下面的無(wú)配置中心對(duì)接一致。DynamicThreadPoolManager 提供了 createThreadPoolExecutor()來(lái)創(chuàng)建線程池。

無(wú)配置中心對(duì)接

如果你的項(xiàng)目中沒(méi)有使用配置中心怎么辦?還是可以照樣使用動(dòng)態(tài)線程池的。

直接將線程池的配置信息放在項(xiàng)目的 application 配置文件中即可,但是這樣的缺點(diǎn)就是無(wú)法動(dòng)態(tài)修改配置信息了。

如果想有動(dòng)態(tài)修改配置的能力,可以稍微擴(kuò)展下,這邊我提供下思路。

編寫一個(gè) Rest API,參數(shù)就是整個(gè)線程池配置的內(nèi)容,可以是 Properties 文件也可以是 Yaml 文件格式。

這個(gè) API 的邏輯就是注入我們的 DynamicThreadPoolProperties,調(diào)用 refresh()刷新 Properties 文件,調(diào)用 refreshYaml()刷新 Yaml 文件。

然后注入 DynamicThreadPoolManager,調(diào)用 refreshThreadPoolExecutor()刷新線程池參數(shù)。

實(shí)現(xiàn)源碼分析

首先,我們要實(shí)現(xiàn)的需求是同時(shí)適配 Nacos 和 Apollo 兩個(gè)主流的配置中心,一般有兩種做法。

第一種:將跟 Nacos 和 Apollo 相關(guān)的代碼獨(dú)立成一個(gè)模塊,使用者按需引入。

第二種:還是一個(gè)項(xiàng)目,內(nèi)部做兼容。

我這邊采取的是第二種,因?yàn)榇a量不多,沒(méi)必要拆分成兩個(gè)。

需要在 pom 中同時(shí)增加兩個(gè)配置中心的依賴,需要設(shè)置成可選(optional=true)。

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

然后內(nèi)部將監(jiān)聽(tīng)配置動(dòng)態(tài)調(diào)整線程池參數(shù)的邏輯分開(kāi),ApolloConfigUpdateListener 和 NacosConfigUpdateListener。

在自動(dòng)裝配 Bean 的時(shí)候按需裝配對(duì)應(yīng)的 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

通過(guò)@ConditionalOnClass 來(lái)判斷當(dāng)前項(xiàng)目中使用的是哪種配置中心,然后裝配對(duì)應(yīng)的 Listener。上面的代碼看上去沒(méi)問(wèn)題,在實(shí)際使用的過(guò)程去報(bào)了下面的錯(cuò)誤:

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

比如我的項(xiàng)目是用的 Apollo,然后我集成了動(dòng)態(tài)線程池,在啟動(dòng)的時(shí)候就報(bào)上面的錯(cuò)誤了,錯(cuò)誤原因是找不到 Nacos 相關(guān)的類。

但其實(shí)我已經(jīng)用了@ConditionalOnClass 來(lái)判斷,這個(gè)是因?yàn)槟愕?DynamicThreadPoolAutoConfiguration 類是生效的,Spring 會(huì)去裝載 DynamicThreadPoolAutoConfiguration 類,DynamicThreadPoolAutoConfiguration 中有 NacosConfigUpdateListener 的實(shí)例化操作,而項(xiàng)目中又沒(méi)有依賴 Nacos,所以就報(bào)錯(cuò)了。

這種情況我們需要將裝配的邏輯拆分的更細(xì),直接用一個(gè)單獨(dú)的類去配置,將@ConditionalOnClass 放在類上。

這里我采用了靜態(tài)內(nèi)部類的方式,如果項(xiàng)目中沒(méi)有依賴 Nacos,那么 NacosConfiguration 就不會(huì)生效,也就不會(huì)去初始化 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();
}
}

這個(gè)地方我順便提一個(gè)點(diǎn),就是為什么我們平時(shí)要多去看看開(kāi)源框架的源碼。因?yàn)橄襁@種適配多個(gè)框架的邏輯比較常見(jiàn),那么一些開(kāi)源框架中肯定也有類似的邏輯。如果你之前有看過(guò)其他的框架是怎么實(shí)現(xiàn)的,那么這里你就會(huì)直接采取那種方式。

比如 Spring Cloud OpenFeign 中對(duì) Http 的客戶端做了多個(gè)框架的適配,你可以用 HttpClient 也可以用 Okhttp,這不就是跟我們這個(gè)一樣的邏輯么。

我們看下源碼就知道了,如下圖:

Apollo 自動(dòng)刷新問(wèn)題

在實(shí)現(xiàn)的過(guò)程中還遇到一個(gè)問(wèn)題也跟大家分享下,就是 Apollo 中@ConfigurationProperties 配置類,在配置信息變更后不會(huì)自動(dòng)刷新,需要配合 RefreshScope 或者 EnvironmentChangeEvent 來(lái)實(shí)現(xiàn)。

下圖是 Apollo 文檔的原話:

圖片

Nacos 刷新是沒(méi)問(wèn)題的,只不過(guò)在收到配置變更的消息時(shí),配置信息還沒(méi)刷新到 Bean 里面去,所以再刷新的時(shí)候單獨(dú)起了一個(gè)線程去做,然后在這個(gè)線程中睡眠了 1 秒鐘(可通過(guò)配置調(diào)整)。

如果按照 Apollo 文檔中給的方式,肯定是可以實(shí)現(xiàn)的。但是不太好,因?yàn)樾枰蕾?Spring Cloud Context。主要是考慮到使用者并不一定會(huì)用到 Spring Cloud,我們的基礎(chǔ)是 Spring Boot。

萬(wàn)一使用者就是在 Spring Boot 項(xiàng)目中用了 Apollo, 然后又用了我的動(dòng)態(tài)線程池,這怎么搞?

最后我采用了手動(dòng)刷新的方式,當(dāng)配置發(fā)生變更的時(shí)候,我會(huì)通過(guò) Apollo 的客戶端,重新拉取整個(gè)配置文件的內(nèi)容,然后手動(dòng)刷新配置類。

config.addChangeListener(changeEvent -> {
ConfigFileFormat configFileFormat = ConfigFileFormat.Properties;
String getConfigNamespace = finalApolloNamespace;
if (finalApolloNamespace.contains(ConfigFileFormat.YAML.getValue())) {
configFileFormat = ConfigFileFormat.YAML;
// 去除.yaml后綴,getConfigFile時(shí)候會(huì)根據(jù)類型自動(dòng)追加
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("轉(zhuǎn)換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]

關(guān)于作者:尹吉?dú)g,簡(jiǎn)單的技術(shù)愛(ài)好者,《Spring Cloud 微服務(wù)-全棧技術(shù)與案例解析》, 《Spring Cloud 微服務(wù) 入門 實(shí)戰(zhàn)與進(jìn)階》作者, 公眾號(hào) 猿天地?發(fā)起人。個(gè)人微信 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

相關(guān)推薦

  • 噓!異步事件這樣用真的好么?

  • 一時(shí)技癢,擼了個(gè)動(dòng)態(tài)線程池,源碼放Github了

  • 熬夜之作:一文帶你了解Cat分布式監(jiān)控

  • 笑話:大廠都在用的任務(wù)調(diào)度框架我能不知道嗎???

  • 為什么參與開(kāi)源項(xiàng)目的程序員找工作時(shí)特別搶手?

后臺(tái)回復(fù)?學(xué)習(xí)資料?領(lǐng)取學(xué)習(xí)視頻

如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝

總結(jié)

以上是生活随笔為你收集整理的apollo源码分析 感知_Kitty中的动态线程池支持Nacos,Apollo多配置中心了的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。