介紹
nacos是阿裡18年開源的作為配置中心及服務發現的中間件,本文主要讨論其作為配置中心的一些功能及實作。 下圖描述了spring cloud config Appollo Nacos三個配置中心的一些特性,個人比較傾向于nacos,因為nacos 部署、使用特别友善,跟spring整個生态無縫結合。

nacos架構圖:
使用
nacos由server client mysql組成,server包含配置管理及client擷取配置的功能,資料由mysql存儲,server通過http服務暴露配置,client使用http長輪詢來擷取server端配置的變更,通過監聽器對變更資料進行處理,nacos也支援udp方式來推送配置的變更。來看下nacos的使用,現在主流應用都使用spring boot或者spring cloud, 是以看下spring boot及spring cloud結合nacos的使用。
spring boot使用方式:
1.引入依賴:
依賴的版本0.1.x對應spring boot 1.x,0.2.x對應spring boot 2.x,由于項目使用log4j2日志,是以需要去除logback依賴,并且log4j2日志的版本需要在2.7.0以上,推薦2.10.0版本
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>0.1.1</version>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
2.增加配置:
nacos.config.server-addr=127.0.0.1:8848
nacos.config.context-path=nacos
3.啟動類增加annotation
@NacosPropertySource(dataId = "spring-boot-demo.properties", autoRefreshed = true)
@NacosPropertySource(dataId = "share.properties", groupId = Constants.DEFAULTGROUP, autoRefreshed = true, properties = @NacosProperties(namespace = "af0a428a-ff43-4971-8b3d-4eaf4ffcfbc9"))
可以配置多個注解擷取多個屬性源。注解中定義的資訊是拉取配置需要用到的資訊,dataId(必傳)指屬性源的id,一般是項目名稱+profile+檔案字尾,groupId指配置的分組,預設DEFAULTGROUP,autoRefreshed 指定是否重新整理,namespace 指定配置命名空間,比如不同區域服務可以使用不同命名空間。不同的分組和命名空間可以對配置進行不同次元的管理。
[email protected](value = "${xx}") spring bean中使用該注解注入配置,不過使用這種方式無法實時重新整理,因為sprint boot監聽更新隻是重新整理了上下文environment對象中的配置,沒有重新生成spring bean。
spring cloud使用方式:
1.引入依賴:
依賴的版本0.1.x對應spring boot 1.x,0.2.x對應spring boot 2.x,并且log4j2日志的版本需要在2.7.0以上,推薦2.10.0版本
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>0.1.1.RELEASE</version>
</dependency>
2.增加配置:
spring.cloud.nacos.config.context-path=nacos
spring.cloud.nacos.config.shared-dataids=common.properties
spring.cloud.nacos.config.refreshable-dataids=common.properties
spring.cloud.nacos.config.ext-config[0].dataId=ext.properties
spring.cloud.nacos.config.ext-config[0].refresh=true
spring.cloud.nacos.config.namespace=af0a428a-ff43-4971-8b3d-4eaf4ffcfbc9
spring.cloud.nacos.config.prefix=${spring.application.name}
spring.cloud.nacos.config.file-extension=properties
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
如上配置需要添加到bootstrap配置檔案才可生效,可定義shared-dataids擷取一些公共配置,使用ext-config擷取額外配置,通過prefix和file-extension确定dataId,以上配置的namespace會作用于所有shared、 ext和應用的dataId,不像spring boot方式,不同的NacosPropertySource注解中的namespace會分别生效
3.spring bean增加@RefreshScope,通過@Value(value = "${xx}") 注入配置,該方式可以達到實時重新整理效果,spring cloud通過監聽變更會銷毀bean,下次使用生成bean時重新注入屬性達到重新整理效果。
nacos實作
client拉取配置和感覺配置變更的核心接口是ConfigService,定義了釋出、删除、擷取配置,添加删除監聽器、擷取server狀态這些接口
String getConfig(String dataId, String group, long timeoutMs) throws NacosException;
void addListener(String dataId, String group, Listener listener) throws NacosException;
boolean publishConfig(String dataId, String group, String content) throws NacosException;
boolean removeConfig(String dataId, String group) throws NacosException;
void removeListener(String dataId, String group, Listener listener);
String getServerStatus();
實作類NacosConfigService對配置的增删改查操作通過一個HttpAgent來通路server暴露的http服務來實作,NacosConfigService内部通過ClientWorker類來對不同http請求擷取的配置進行check重新整理
final ScheduledExecutorService executor;
final ExecutorService executorService;
/**
* groupKey -> cacheData
*/
AtomicReference<Map<String, CacheData>> cacheMap = new AtomicReference<Map<String, CacheData>>(
new HashMap<String, CacheData>());
ClientWorker内部的核心成員如上,cacheMap 維護了不同groupKey(通過dataId,group,namespace組成)到CacheData(配置資料)映射,每次注冊監聽器之後把對應配置資訊放入這個map,之後通過executorService每10ms送出一個LongPollingRunnable的線程
executor.scheduleWithFixedDelay(new Runnable() {
public void run() {
try {
checkConfigInfo();
} catch (Throwable e) {
LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
}
}
}, 1L, 10L, TimeUnit.MILLISECONDS);
checkConfigInfo方法如下
public void checkConfigInfo() {
// 分任務
int listenerSize = cacheMap.get().size();
// 向上取整為批數
int longingTaskCount = (int)Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
if (longingTaskCount > currentLongingTaskCount) {
for (int i = (int)currentLongingTaskCount; i < longingTaskCount; i++) {
// 要判斷任務是否在執行 這塊需要好好想想。 任務清單現在是無序的。變化過程可能有問題
executorService.execute(new LongPollingRunnable(i));
}
currentLongingTaskCount = longingTaskCount;
}
}
通過executorService送出一個長輪詢檢查線程,代碼比較長就不貼了,流程就是對每個CacheData(配置資料),檢查配置目前版本跟本地配置檔案最後變更時間是否一緻确定配置是否重新整理,重新整理則通知監聽器做對應處理,然後使用http請求(預設逾時30s)server端這個位址/v1/cs/config/listener(長輪詢),server端這個位址會保持連接配接,如果配置沒有變更,請求會在停留30s後傳回,如果有配置更變,請求會馬上傳回,client接收到配置變更,會更新本地配置檔案并且更新版本号。
server端通過LongPollingService來完成保持連接配接的能力,核心成員如下:
final ScheduledExecutorService scheduler;
/**
* 長輪詢訂閱關系
*/
final Queue<ClientLongPolling> allSubs;
ClientLongPolling是一個線程,通過scheduler預設30s定時處理請求,如果server端接收到配置變更請求,則通過釋出LocalDataChangeEvent事件,LongPollingService繼承監聽器,觸發監聽執行DataChangeTask,内部通過allSubs維護的異步請求上下文AsyncContext直接傳回response。
spring boot內建nacos通過NacosPropertySourcePostProcessor,該類實作BeanFactoryPostProcessor及EnvironmentAware,通過nacos client擷取配置放入spring 上下文,通過是否重新整理來決定是否注冊監聽。
spring cloud通過NacosPropertySourceLocator,在PropertySourceBootstrapConfiguration這個ApplicationContextInitializer裡加載屬性源,監聽ApplicationReadyEvent事件後為屬性源注冊監聽處理變更。
使用感受
server端本地可通過standalone模式啟動,也可源碼直接運作,友善調試,client使用也很友善,支援配置分組和命名空間的隔離,支援配置重新整理復原,配置灰發功能還有待開發,權限管理不足,總體使用還是非常簡單友善,spring 生态內建度很高,比較推薦使用。