分布式配置中心服務(wù)端如何實(shí)時(shí)更新?
點(diǎn)擊上方“Java金融”,選擇“設(shè)為星標(biāo)”
后臺(tái)回復(fù)"888"獲取bat面試題集
引言前面有寫過(guò)一篇《分布式配置中心apollo是如何實(shí)時(shí)感知配置被修改》,也就是客戶端client是如何知道配置被修改了,有不少讀者私信我你既然說(shuō)了client端是如何感知的,那服務(wù)端又是如何知道配置被修改了。今天我們就一起來(lái)看看Apollo在Portal修改了配置文件,怎么通知到configService的。什么是portal和configService 建議可以看看這一篇文章篇《分布式配置中心apollo是如何實(shí)時(shí)感知配置被修改》,里面對(duì)這些模塊都有簡(jiǎn)單的介紹,你如果實(shí)在不想看也行,我直接截個(gè)圖過(guò)來(lái)
我們來(lái)看官網(wǎng)提供的一張圖
★1.用戶在Portal操作配置發(fā)布 2.Portal調(diào)用Admin Service的接口操作發(fā)布 3.Admin Service發(fā)布配置后,發(fā)送ReleaseMessage給各個(gè)Config Service 4.Config Service收到ReleaseMessage后,通知對(duì)應(yīng)的客戶端
”
上面的流程就是從Portal到ConfigService主要流程,下面我們來(lái)看看具體的細(xì)節(jié)。要知道細(xì)節(jié)我們要自己動(dòng)手去調(diào)試一把源碼。我們可以照著官網(wǎng)的文檔,自己本地把項(xiàng)目run起來(lái)。文檔寫的還是很詳細(xì)的,只要按照步驟來(lái)都能運(yùn)行的起來(lái)。我們隨便新建一個(gè)項(xiàng)目然后去編輯下key,然后打開瀏覽器的F12當(dāng)我們點(diǎn)擊提交按鈕的時(shí)候我們就知道她到底調(diào)用了那些接口,有了接口我們就知道了入口剩下的就是打斷點(diǎn)進(jìn)行調(diào)試了。
portal 如何獲取AdminService
根據(jù)這個(gè)方法我們是不是就可以定位到portal模塊后端代碼的controller。找到對(duì)應(yīng)的controller打開看一看基本沒有什么業(yè)務(wù)邏輯
然后portal緊接著就是去調(diào)用adminService了。
根據(jù)上圖我們就可以的方法我們就可以找到對(duì)應(yīng)的adminService了,portal是如何找到對(duì)應(yīng)的adminService服務(wù)的,因?yàn)閍dminService 是可以部署多臺(tái)機(jī)器,這里就要用到服務(wù)注冊(cè)和發(fā)現(xiàn)了adminService只有被注冊(cè)到服務(wù)中心,portal才可以通過(guò)服務(wù)注冊(cè)中心來(lái)獲取對(duì)應(yīng)的adminService服務(wù)了。Apollo 默認(rèn)是采用eureka來(lái)作為服務(wù)注冊(cè)和發(fā)現(xiàn),它也提供了nacos、consul來(lái)作為服務(wù)注冊(cè)和發(fā)現(xiàn),還提供了一種kubernetes不采用第三方來(lái)做服務(wù)注冊(cè)和發(fā)現(xiàn),直接把服務(wù)的地址配置在數(shù)據(jù)庫(kù)。如果地址有多個(gè)可以在數(shù)據(jù)庫(kù)逗號(hào)分隔。
它提供了四種獲取服務(wù)列表的實(shí)現(xiàn)方式,如果我們使用的注冊(cè)中心是eureka 我們是不是需要通過(guò)eureka的api去獲取服務(wù)列表,如果我們的服務(wù)發(fā)現(xiàn)使用的是nacos我們是不是要通過(guò)nacos的API去獲取服務(wù)列表。。。所以Apollo提供了一個(gè)MetaService 層,封裝服務(wù)發(fā)現(xiàn)的細(xì)節(jié),對(duì)Portal和Client而言,永遠(yuǎn)通過(guò)一個(gè)Http接口獲取Admin Service和Config Service的服務(wù)信息,而不需要關(guān)心背后實(shí)際的服務(wù)注冊(cè)和發(fā)現(xiàn)組件。就跟我們平時(shí)搬磚一樣沒有啥是通過(guò)增加一個(gè)中間層解決不了的問題,一個(gè)不行那就再加一個(gè)。所以MetaService提供了兩個(gè)接口services/admin 和services/config 來(lái)分別獲取Admin Service和Config Service的服務(wù)信息。那么Portal 是如何來(lái)調(diào)用services/admin這個(gè)接口的呢?在 apollo-portal 項(xiàng)目里面com.ctrip.framework.apollo.portal.component#AdminServiceAddressLocator 這個(gè)類里面,
- 這個(gè)類在加載的時(shí)候會(huì)通過(guò)MetaService 提供的services/admin 接口獲取adminService的服務(wù)地址進(jìn)行緩存。
??@PostConstruct
??public?void?init()?{
????allEnvs?=?portalSettings.getAllEnvs();
????//init?restTemplate
????restTemplate?=?restTemplateFactory.getObject();
????
????refreshServiceAddressService?=
????????Executors.newScheduledThreadPool(1,?ApolloThreadFactory.create("ServiceLocator",?true));
?//?創(chuàng)建延遲任務(wù),1s后開始執(zhí)行獲取AdminService服務(wù)地址
????refreshServiceAddressService.schedule(new?RefreshAdminServerAddressTask(),?1,?TimeUnit.MILLISECONDS);
??}
上面要去MetaService 請(qǐng)求地址,那么MetaService的地址又是什么呢?這個(gè)又如何獲?。縞om.ctrip.framework.apollo.portal.environment#DefaultPortalMetaServerProvider 這個(gè)類。
portal 這個(gè)模塊說(shuō)完了,我們接著回到adminService了。通過(guò)portal調(diào)用adminService的接口地址我們很快可以找到它的入口
AdminService 的實(shí)現(xiàn)也很簡(jiǎn)單
??@PreAcquireNamespaceLock
@PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items")
??public?ItemDTO?create(@PathVariable("appId")?String?appId,
????????????????????????@PathVariable("clusterName")?String?clusterName,
????????????????????????@PathVariable("namespaceName")?String?namespaceName,?@RequestBody?ItemDTO?dto)?{
????Item?entity?=?BeanUtils.transform(Item.class,?dto);
????ConfigChangeContentBuilder?builder?=?new?ConfigChangeContentBuilder();
????Item?managedEntity?=?itemService.findOne(appId,?clusterName,?namespaceName,?entity.getKey());
????if?(managedEntity?!=?null)?{
??????throw?new?BadRequestException("item?already?exists");
????}
????entity?=?itemService.save(entity);
????builder.createItem(entity);
????dto?=?BeanUtils.transform(ItemDTO.class,?entity);
????Commit?commit?=?new?Commit();
????commit.setAppId(appId);
????commit.setClusterName(clusterName);
????commit.setNamespaceName(namespaceName);
????commit.setChangeSets(builder.build());
????commit.setDataChangeCreatedBy(dto.getDataChangeLastModifiedBy());
????commit.setDataChangeLastModifiedBy(dto.getDataChangeLastModifiedBy());
????commitService.save(commit);
????return?dto;
??}
PreAcquireNamespaceLock 注解
首先方法上有個(gè)@PreAcquireNamespaceLock 這個(gè)注解,這個(gè)根據(jù)名字都應(yīng)該能夠去猜一個(gè)大概就是去獲取NameSpace的分布式鎖,現(xiàn)在分布式鎖比較常見的方式是采用redis和zookeeper。但是在這里apollo是采用數(shù)據(jù)庫(kù)來(lái)實(shí)現(xiàn)的,具體怎么細(xì)節(jié)大家可以去看看源碼應(yīng)該都看的懂,無(wú)非就是加鎖往DB里面插入一條數(shù)據(jù),釋放鎖然后把這個(gè)數(shù)據(jù)進(jìn)行刪除。稍微有點(diǎn)不一樣的就是如果獲取鎖失敗,就直接返回失敗了,不會(huì)在繼續(xù)自旋或者休眠重新去獲取鎖。因?yàn)楂@取鎖失敗說(shuō)明已經(jīng)有其他人在你之前修改了配置,只有這個(gè)人新增的配置被發(fā)布或者刪除之后,其他人才能繼續(xù)新增配置,這樣的話就會(huì)導(dǎo)致一個(gè)NameSpace只能同時(shí)被一個(gè)人修改。這個(gè)限制是默認(rèn)關(guān)閉的需要我們?cè)跀?shù)據(jù)庫(kù)里面去配置(ApolloConfigDb的ServiceConfig表)
一般我們應(yīng)用的配置修改應(yīng)該是比較低頻的,多人同時(shí)去修改的話情況會(huì)比較少,再說(shuō)有些公司是開發(fā)提交配置,測(cè)試去發(fā)布配置,提交和修改不能是同一個(gè)人,這樣的話新增配置沖突就更少了,應(yīng)該沒有必要去配置namespace.lock.switch=true一個(gè)namespace只能一個(gè)人去修改。
接下來(lái)的代碼就非常簡(jiǎn)單明了,就是一個(gè)簡(jiǎn)單的參數(shù)判斷然后執(zhí)行入庫(kù)操作了,把數(shù)據(jù)插入到Item表里面。這是我們新增的配置數(shù)據(jù)就已經(jīng)保存了。效果如下
這時(shí)候新增的配置是不起作用的,不會(huì)推送給客戶端的。只是單純一個(gè)類似于草稿的狀態(tài)。
發(fā)布配置
接下來(lái)我們要使上面新增的配置生效,并且推送給客戶端。同樣的我們點(diǎn)擊發(fā)布按鈕然后就能知道對(duì)應(yīng)的后端方法入口
我們通過(guò)這個(gè)接口可以直接找到adminService的方法入口
?public?ReleaseDTO?publish(@PathVariable("appId")?String?appId,
????????????????????????????@PathVariable("clusterName")?String?clusterName,
????????????????????????????@PathVariable("namespaceName")?String?namespaceName,
????????????????????????????@RequestParam("name")?String?releaseName,
????????????????????????????@RequestParam(name?=?"comment",?required?=?false)?String?releaseComment,
????????????????????????????@RequestParam("operator")?String?operator,
????????????????????????????@RequestParam(name?=?"isEmergencyPublish",?defaultValue?=?"false")?boolean?isEmergencyPublish)?{
????Namespace?namespace?=?namespaceService.findOne(appId,?clusterName,?namespaceName);
????if?(namespace?==?null)?{
??????throw?new?NotFoundException(String.format("Could?not?find?namespace?for?%s?%s?%s",?appId,
????????????????????????????????????????????????clusterName,?namespaceName));
????}
????Release?release?=?releaseService.publish(namespace,?releaseName,?releaseComment,?operator,?isEmergencyPublish);
????//send?release?message
????Namespace?parentNamespace?=?namespaceService.findParentNamespace(namespace);
????String?messageCluster;
????if?(parentNamespace?!=?null)?{
??????messageCluster?=?parentNamespace.getClusterName();
????}?else?{
??????messageCluster?=?clusterName;
????}
????messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId,?messageCluster,?namespaceName),
??????????????????????????????Topics.APOLLO_RELEASE_TOPIC);
????return?BeanUtils.transform(ReleaseDTO.class,?release);
??}
- 上述代碼就不仔細(xì)展開分析了,感興趣的可以自己斷點(diǎn)調(diào)試下我們重點(diǎn)看下
releaseService.publish這個(gè)方法,里面有一些灰度發(fā)布相關(guān)的邏輯,不過(guò)這個(gè)不是本文的重點(diǎn),這個(gè)方法主要是往release表插入數(shù)據(jù)。 - 接下來(lái)就是
messageSender.sendMessage這個(gè)方法了,這個(gè)方法主要是往ReleaseMessage表里面插入一條記錄。保存完ReleaseMessage這個(gè)表會(huì)得到相應(yīng)的主鍵ID,然后把這個(gè)ID放入到一個(gè)隊(duì)列里面。然后在加載DatabaseMessageSender的時(shí)候會(huì)默認(rèn)起一個(gè)定時(shí)任務(wù)去獲取上面隊(duì)列里面放入的消息ID,然后找出比這這些ID小的消息刪除掉。發(fā)布流程就完了,這里也沒有說(shuō)到服務(wù)端是怎么感知有配置修改了的。
Config Service 通知配置變化
apolloConfigService 在服務(wù)啟動(dòng)的時(shí)候ReleaseMessageScanner 會(huì)啟動(dòng)一個(gè)定時(shí)任務(wù) 每隔1s去去查詢ReleaseMessage里面有沒有最新的消息,如果有就會(huì)通知到所有的消息監(jiān)聽器比如NotificationControllerV2、ConfigFileController等,這個(gè)消息監(jiān)聽器注冊(cè)是在ConfigServiceAutoConfiguration里面注冊(cè)的。NotificationControllerV2 得到配置發(fā)布的 AppId+Cluster+Namespace 后,會(huì)通知對(duì)應(yīng)的客戶端,這樣就從portal到configService 到 client 整個(gè)消息通知變化就串起來(lái)了。服務(wù)端通知客戶端的具體細(xì)節(jié)可以看看《分布式配置中心apollo是如何實(shí)時(shí)感知配置被修改》
總結(jié)
這樣服務(wù)端配置如何更新的流程就完了。
★1.用戶在Portal操作配置發(fā)布 2.Portal調(diào)用Admin Service的接口操作發(fā)布 3.Admin Service發(fā)布配置后,發(fā)送ReleaseMessage給各個(gè)Config Service 4.Config Service收到ReleaseMessage后,通知對(duì)應(yīng)的客戶端
”
apollo的源碼相對(duì)于其他中間件來(lái)說(shuō)還是相對(duì)于比較簡(jiǎn)單的,比較適合于想研究下中間件源碼,又不知道如何下手的同學(xué) 。
結(jié)束
- 由于自己才疏學(xué)淺,難免會(huì)有紕漏,假如你發(fā)現(xiàn)了錯(cuò)誤的地方,還望留言給我指出來(lái),我會(huì)對(duì)其加以修正。
- 如果你覺得文章還不錯(cuò),你的轉(zhuǎn)發(fā)、分享、贊賞、點(diǎn)贊、留言就是對(duì)我最大的鼓勵(lì)。
- 感謝您的閱讀,十分歡迎并感謝您的關(guān)注。
站在巨人的肩膀 https://www.apolloconfig.com/#/zh/design/apollo-design?id=%e4%b8%80%e3%80%81%e6%80%bb%e4%bd%93%e8%ae%be%e8%ae%a1 https://www.iocoder.cn/Apollo/client-polling-config/
往期精選
- 5種SpringMvc的異步處理方式你都了解嗎?
- 從源碼分析幾道必問線程池的面試題?
- 面試高頻題:springBoot自動(dòng)裝配的原理你能說(shuō)出來(lái)嗎?
- 高并發(fā)系統(tǒng)三大利器之緩存
- 終于有人把 java代理 講清楚了,萬(wàn)字詳解!
- 10分鐘帶你入門git到github
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)、等等。獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù) 666?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)
