Java教程:nacos入門系列之配置中心
配置的釋出與訂閱
我們先來看看如何使用nacos提供的api來實現配置的釋出與訂閱
釋出配置:
public
class
ConfigPub {
public
static
void
main
(String[] args)
throws NacosException {
final String dataId=
"test";
final String group=
"DEFAULT_GROUP";
ConfigService configService= NacosFactory.createConfigService(
"localhost:8848");
configService.publishConfig(dataId,group,
"test config body");
}
}
訂閱配置:
public
static
void
main
(String[] args)
throws NacosException, InterruptedException {
final String dataId=
"test";
final String group=
"DEFAULT_GROUP";
ConfigService configService= NacosFactory.createConfigService(
"localhost:8848");
configService.addListener(dataId, group,
new Listener() {
@Override
public Executor
getExecutor
() {
return
null;
}
@Override
public
void
receiveConfigInfo
(String configInfo) {
System.out.println(
"receiveConfigInfo:"+configInfo);
}
});
Thread.sleep(Integer.MAX_VALUE);
}
}
根據上面的demo可以看到透過dataId和group可以定位一個配置檔案。
深入瞭解配置釋出
1-釋出的配置資訊會透過http請求呼叫具體的服務
agent.httpPost(url, headers, params, encode, POST_TIMEOUT);
服務類為 ConfigController:處理配置相關的http請求
persistService
.insertOrUpdateTag(configInfo, tag, srcIp, srcUser, time,
false);
EventDispatcher.fireEvent(
new ConfigDataChangeEvent(
false, dataId,
group, tenant, tag,
time.getTime()));
可以看到釋出的配置首先會進行持久化,然後會觸發變更通知。
持久化這裡就不做分析,我們來看看fireEvent這個方法:
EventDispatcher.fireEvent:
static
public
void
fireEvent(
Event
event) {
if (
null ==
event) {
throw
new IllegalArgumentException(
"event is null");
}
for (AbstractEventListener listener : getEntry(
event.getClass()).listeners) {
try {
listener.onEvent(
event);
}
catch (Exception e) {
log.error(e.toString(), e);
}
}
}
這裡可以看到具體呼叫了listener.onEvent(
event);
這裡只要找到AbstractEventListener 具體的實現類是哪個就可以。
AbstractEventListener主要有兩個實現類:
AsyncNotifyService
LongPollingService
我們可以透過
event的型別去判斷,因為這裡onEvent的引數型別為ConfigDataChangeEvent,
所以我們可以清楚的知道我們要找的實現類是AsyncNotifyService。
每個AbstractEventListener初始化的時候都會先將自己加入到listeners中
final CopyOnWriteArrayList<AbstractEventListener> listeners;
public
AbstractEventListener(
) {
/**
* automatic register
*/
EventDispatcher.addEventListener(
this);
}
我們可以直接看看AsyncNotifyService的onEvent方法:
public
void
onEvent(
Event
event) {
// 併發產生 ConfigDataChangeEvent
if (event instanceof ConfigDataChangeEvent) {
ConfigDataChangeEvent evt = (ConfigDataChangeEvent) event;
long dumpTs = evt.lastModifiedTs;
String dataId = evt.dataId;
String group = evt.group;
String tenant = evt.tenant;
String tag = evt.tag;
//Member{address='192.168.31.192:8848'}
Collection<Member> ipList = memberManager.allMembers();
// 其實這裡任何型別佇列都可以
Queue<NotifySingleTask> queue = new LinkedList<NotifySingleTask>();
for (Member member : ipList) {
queue.add(new NotifySingleTask(dataId, group, tenant, tag, dumpTs,
member.getAddress(), evt.isBeta));
}
EXECUTOR.execute(new AsyncTask(httpclient, queue));
}
}
上面的方法主要實現的是:
獲取所有的nacos服務節點,然後對其執行非同步任務AsyncTask。
AsyncTask中會從佇列中獲取每個節點的NotifySingleTask資訊,然後進行http請求,呼叫通知配置資訊改變
的服務。具體服務在CommunicationController中實現。
/**
* 通知配置資訊改變
*/
@GetMapping("/dataChange")
這個方法放在後面分析。
深入瞭解配置訂閱
初始化:
NacosConfigService初始化的時候構造了ClientWorker,並且透過ClientWorker啟動了兩個執行緒池。
worker = new ClientWorker(agent, configFilterChainManager, properties);
第一個執行緒池每
10ms執行一次checkConfigInfo();
executor.scheduleWithFixedDelay(new Runnable() {
@Override
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();
// 向上取整為批數,限制LongPollingRunnable處理配置的個數。
int longingTaskCount =(int) Math.
ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
if (longingTaskCount > currentLongingTaskCount) {
for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
// 要判斷任務是否在執行 這塊需要好好想想。
//任務列表現在是無序的。變化過程可能有問題
executorService.
execute(new LongPollingRunnable(i));
//這裡的i就代表taskId
}
currentLongingTaskCount = longingTaskCount;
}
}
這裡主要的作用是提交LongPollingRunnable任務到第二個執行緒池中去執行。
並且每個LongPollingRunnable只會處理
3000個配置。
我們來看看LongPollingRunnable的實現
List<CacheData> cacheDatas = new ArrayList<CacheData>();
List<String> inInitializingCacheList = new ArrayList<String>();
try {
// check failover
config
for (CacheData cacheData : cacheMap.get().values()) {
if (cacheData.getTaskId() == taskId) {
cacheDatas.add(cacheData);
...
}
}
cacheMap中儲存了配置資訊,從磁碟中載入獲取。
透過taskId從 cacheMap中獲取需要被當前LongPollingRunnable任務處理的配置,放入到cacheDatas集合。
我們來看看是在哪裡設定的taskId
int taskId = cacheMap.get().size() / (int) ParamUtil.getPerTaskConfigSize();
cache.setTaskId(taskId);
可以看到這裡和上面相對應,每
3000個配置的taskId是相同的。因為每個LongPollingRunnable執行緒會處理
3000個配置。
// check server
config 向服務端請求變化的配置
List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
//從Server獲取值變化了的DataID列表。返回的物件裡只有dataId和group是有效的。 保證不返回NULL。
return checkUpdateConfigStr(sb.toString(), isInitializingCacheList);
這裡訂閱配置的客戶端會向服務端傳送http長輪詢請求,來獲取變化的配置資訊
長輪詢請求不會立刻返回結果,而是當有配置發生變化時返回,設定了超時時間
30s,如果超過了設定的
超時時間沒有配置更新,則會預設返回。然後重新發起一次長輪詢的請求。
HttpResult result = agent.httpPost(Constants.CONFIG_CONTROLLER_PATH +
"/listener",
headers, params,
agent.getEncode(), readTimeoutMs);
長輪詢的週期預設為
30s:
timeout=Math.
max(NumberUtils.toInt(properties.getProperty(PropertyKeyConst.CONFIG_LONG_POLL_TIMEOUT),
Constants.CONFIG_LONG_POLL_TIMEOUT), Constants.MIN_CONFIG_LONG_POLL_TIMEOUT);
具體服務實現類在ConfigController中:
@PostMapping(
"/listener")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void listener(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
....
//
do long-polling
inner.doPollingConfig(request, response, clientMd5Map, probeModify.length());
}
doPollingConfig方法:
// 服務端處理長輪詢請求
if (LongPollingService.isSupportLongPolling(request)) {
longPollingService.addLongPollingClient(request, response, clientMd5Map,
probeRequestSize);
return HttpServletResponse.SC_OK +
"";
}
使用執行緒池處理請求:
scheduler.
execute(
new ClientLongPolling(asyncContext, clientMd5Map, ip, probeRequestSize, timeout,
appName, tag));
接著來看ClientLongPolling是一個執行緒實現類
首先會觸發一個延時任務,然後將自己加入到佇列:allSubs.add(this);
allSubs中維護了所有長輪訓請求。
那麼肯定會有一個地方去消費allSubs佇列中的請求.
這個消費的地方就是onEvent方法:
LongPollingService其實就是我們上面提到的AbstractEventListener,因此也實現了onEvent方法。
@Override
public void onEvent(Event event) {
if (isFixedPolling()) {
// ignore
}
else {
if (event instanceof LocalDataChangeEvent) {
LocalDataChangeEvent evt = (LocalDataChangeEvent)event;
scheduler.
execute(new DataChangeTask(evt.groupKey, evt.isBeta,
evt.betaIps));
}
}
}
這個event方法就是去處理配置變化的情況,主要邏輯在DataChangeTask中:
從allSubs獲取維護的請求中相同dataId+group的請求,比如:(test+DEFAULT_GROUP)
然後進行這個對長輪詢的請求進行返回。
for (Iterator<ClientLongPolling> iter = allSubs.iterator(); iter.hasNext(); ) {
ClientLongPolling clientSub = iter.
next();
//groupKey test+DEFAULT_GROUP
if (clientSub.clientMd5Map.containsKey(groupKey)) {
......
iter.
remove(); // 刪除訂閱關係
LogUtil.clientLog.info(
"{}|{}|{}|{}|{}|{}|{}",
(System.currentTimeMillis() - changeTime),
"in-advance",
RequestUtil.getRemoteIp((HttpServletRequest)clientSub.asyncContext.getRequest()),
"polling",
clientSub.clientMd5Map.size(), clientSub.probeRequestSize, groupKey);
clientSub.sendResponse(Arrays.asList(groupKey));
}
}
那是哪裡觸發了LongPollingService裡面的onEvent 方法呢?
當然是在配置釋出後進行觸發的,還記得CommunicationController中的dataChange服務嗎?
配置釋出後會透過http請求呼叫nacos服務中的dataChange服務。透過dataChange服務就可以通知
nacos服務中儲存的長輪訓的請求了。
並且這個方法是獲取所有nacos服務節點去遍歷執行的,因此不管變更配置對應的長輪詢儲存在哪個節點,
都會可以被獲取到。
/**
* 通知配置資訊改變
*/
@GetMapping(
"/dataChange")
此處會呼叫DumpService中的方法儲存配置檔案到磁碟,並快取md5.
DiskUtil.saveToDisk(dataId, group, tenant, content);
public static void updateMd5(String groupKey, String md5, long lastModifiedTs) {
CacheItem cache = makeSure(groupKey);
if (cache.md5 == null || !cache.md5.equals(md5)) {
cache.md5 = md5;
cache.lastModifiedTs = lastModifiedTs;
EventDispatcher.fireEvent(new LocalDataChangeEvent(groupKey));
}
}
可以看到當配置變更,就會觸發fireEvent的LocalDataChangeEvent事件。
總結:
到這裡,配置中心整體是實現基本上告一段落,還要很多細節沒有涉及到,需要在真正的使用過程中來探索和發現
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69923331/viewspace-2703052/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- nacos入門系列之配置中心
- 配置中心nacos
- Java入門系列之finalJava
- 保姆教程系列二、Nacos實現註冊中心
- SpringCloud Nacos 配置中心SpringGCCloud
- Nacos配置中心原理
- nacos配置中心使用
- SpringCloud入門(五)Nacos註冊中心(上)SpringGCCloud
- SpringCloud入門(六)Nacos註冊中心(下)SpringGCCloud
- Java入門系列之重寫Java
- SpringBoot使用Nacos配置中心Spring Boot
- nacos作為配置中心
- Nacos配置中心規範
- Docker 搭建 Nacos 配置中心Docker
- Springcloud alibaba nacos配置中心SpringGCCloud
- SpringCloud Alibaba入門之Nacos(SCA)SpringGCCloud
- Nacos配置中心 (介紹與配置)
- 保姆教程系列三、Nacos Config--服務配置
- Spring Cloud Alibaba系列(二)nacos作為服務配置中心SpringCloud
- Spring Cloud Alibaba基礎教程:使用Nacos作為配置中心SpringCloud
- springcloud alibaba-nacos配置中心SpringGCCloud
- springboot整合nacos註冊中心和配置中心Spring Boot
- SpringCloud系列之分散式配置中心極速入門與實踐SpringGCCloud分散式
- Nacos系列:Nacos的Java SDK使用Java
- 配置中心之Nacos簡介,使用及Go簡單整合Go
- Nacos註冊中心+配置管理
- 【Nacos】微服務配置中心介紹微服務
- Nacos 配置中心介紹及使用
- 整合 nacos註冊中心配置使用
- 配置中心Nacos(服務發現)
- Spring Cloud Alibaba(5)---Nacos(配置中心)SpringCloud
- 初探Nacos(四)-- SpringBoot下使用Nacos作為配置中心Spring Boot
- Spring Cloud 從入門到精通(一)Nacos 服務中心初探SpringCloud
- nacos 作為配置中心使用心得--配置使用
- Spring Security系列之極速入門與實踐教程Spring
- SpringBoot系列之Elasticsearch極速入門與實際教程Spring BootElasticsearch
- 配置中心的設計-nacos vs apollo
- nacos統一配置中心原始碼解析原始碼