在開展微服務(wù)的過(guò)程中,了解要考慮哪些因素可能是非常有挑戰(zhàn)性的事情。沒有可以直接使用的金科玉律损晤。每個(gè)過(guò)程都是不同的,因?yàn)槊總€(gè)組織面臨的都是不同的環(huán)境红竭。在本文中尤勋,我將從初創(chuàng)公司的角度分享我們學(xué)習(xí)到的經(jīng)驗(yàn)和面臨的挑戰(zhàn),以及我下次引入微服務(wù)時(shí)茵宪,會(huì)在哪些方面采取不同的做法最冰。
核心要點(diǎn)
從一個(gè)易于抽取的小候選功能開始,以便于盡早獲得微服務(wù)的體驗(yàn)稀火;
要預(yù)先重點(diǎn)關(guān)注構(gòu)建和部署自動(dòng)化以及監(jiān)控暖哨;
盡早處理橫切性的關(guān)注點(diǎn),避免給生產(chǎn)效率帶來(lái)負(fù)面的影響凰狞,比如為單體應(yīng)用繼續(xù)增加功能或者為每個(gè)微服務(wù)重新實(shí)現(xiàn)橫切性的關(guān)注點(diǎn)篇裁;
將系統(tǒng)的事件驅(qū)動(dòng)功能設(shè)計(jì)得易于演化,考慮采用事件流的方案以減少數(shù)據(jù)副本的成本并降低添加新微服務(wù)的門檻赡若;
需要注意达布,轉(zhuǎn)換至微服務(wù)的過(guò)程并不是獨(dú)立運(yùn)轉(zhuǎn)的。相反逾冬,它受到很多環(huán)境因素的影響黍聂。當(dāng)心那些阻礙你前進(jìn)或拖你后腿的環(huán)境因素,對(duì)它們進(jìn)行相應(yīng)的調(diào)整身腻,或者至少要在整個(gè)組織中意識(shí)到這些問題产还。
在開展微服務(wù)的過(guò)程中,了解要考慮哪些因素可能是非常有挑戰(zhàn)性的事情嘀趟,對(duì)于小團(tuán)隊(duì)來(lái)講更是如此雕沉。遺憾的是,沒有可以直接使用的金科玉律去件。每個(gè)過(guò)程都是不同的坡椒,因?yàn)槊總€(gè)組織面臨的都是不同的環(huán)境。在本文中尤溜,我將從初創(chuàng)公司的角度分享我們學(xué)習(xí)到的經(jīng)驗(yàn)和面臨的挑戰(zhàn)倔叼,以及我下次引入微服務(wù)時(shí),會(huì)在哪些方面采取不同的做法宫莱。
從單體應(yīng)用到微服務(wù)的旅程該如何開始丈攒?
最初,從各個(gè)方面看授霸,我都是從單體應(yīng)用開始的:我們整個(gè)團(tuán)隊(duì)基于一個(gè)相互協(xié)作的產(chǎn)品開展工作巡验,將其實(shí)現(xiàn)為同一個(gè)代碼庫(kù)并且基于同一個(gè)技術(shù)棧。在一段時(shí)間內(nèi)碘耳,這種方式能夠很好地運(yùn)轉(zhuǎn)显设。
隨著時(shí)間的推移,所有的事情都在演化:團(tuán)隊(duì)在增長(zhǎng)辛辨,我們?yōu)楫a(chǎn)品不斷添加越來(lái)越多的特性捕捂,代碼庫(kù)變得越來(lái)越大,用戶的數(shù)量也在不斷增長(zhǎng)斗搞。這聽起來(lái)非常不錯(cuò)指攒,對(duì)吧?但是……
現(xiàn)在僻焚,要完成一件事情需要非常長(zhǎng)的時(shí)間:會(huì)議允悦、討論和決策都要比以往消耗更長(zhǎng)的時(shí)間。職責(zé)無(wú)法清晰地劃分虑啤,明確具體責(zé)任需要花費(fèi)一定的時(shí)間隙弛,比如當(dāng)出現(xiàn)了 bug 的時(shí)候。我們的過(guò)程變得更加緩慢咐旧,生產(chǎn)效率也受到了影響驶鹉。
我們添加的特性越多,產(chǎn)品使用起來(lái)就越復(fù)雜铣墨。產(chǎn)品的可用性和用戶體驗(yàn)因?yàn)椴粩嗟奶匦孕薷亩軗p室埋。我們不但沒有很好地解決用戶的問題,反而讓他們更加困惑伊约。
因?yàn)椴捎脝误w軟件架構(gòu)姚淆,我們很難在不影響整個(gè)系統(tǒng)的情況下添加新的特性,釋放新的變更也變得非常復(fù)雜屡律,即便我們只修改了幾行代碼腌逢,也需要重新構(gòu)建和部署整個(gè)產(chǎn)品。這導(dǎo)致部署會(huì)具有很高的風(fēng)險(xiǎn)性超埋,因此部署的頻率也不那么頻繁搏讶,因?yàn)樾绿匦缘陌l(fā)布非常緩慢佳鳖。
因此,對(duì)系統(tǒng)進(jìn)行分離和轉(zhuǎn)換的需求就出現(xiàn)了媒惕。
在三年前系吩,我們改變了產(chǎn)品策略。我們關(guān)注可用性和用戶體驗(yàn)的提升并將我們的產(chǎn)品 JUST SOCIAL 拆分成了多個(gè)獨(dú)立的應(yīng)用妒蔚,其中每個(gè)應(yīng)用負(fù)責(zé)特定的場(chǎng)景穿挨。我們不斷演化這個(gè)理念,提供不同的應(yīng)用來(lái)共享文檔肴盏、實(shí)時(shí)交流科盛、管理任務(wù)、共享可編輯的內(nèi)容和協(xié)作的新聞以及管理 profile菜皂。
同時(shí)贞绵,我們將整個(gè)團(tuán)隊(duì)拆分成了多個(gè)更小的團(tuán)隊(duì),并為每個(gè)團(tuán)隊(duì)分派了特定的一組協(xié)作應(yīng)用(collaboration app)幌墓,從而實(shí)現(xiàn)定義了良好的職責(zé)劃分但壮。我們想要建立自治化的團(tuán)隊(duì),能夠讓他們按照自己的節(jié)奏獨(dú)立地圍繞系統(tǒng)不同的組成部分開展工作常侣,將跨團(tuán)隊(duì)的影響降低到最小蜡饵。
在將我們的產(chǎn)品拆分為多個(gè)獨(dú)立的協(xié)作應(yīng)用并將團(tuán)隊(duì)分為多個(gè)更小的團(tuán)隊(duì)之后,接下來(lái)順理成章的步驟就是將自治性和靈活性反映到軟件架構(gòu)中胳施,這是通過(guò)引入微服務(wù)實(shí)現(xiàn)的溯祸。
我們引入微服務(wù)的驅(qū)動(dòng)力在于讓系統(tǒng)的不同組成部分能夠?qū)崿F(xiàn)自治,讓他們按照自己獨(dú)立的節(jié)奏開展工作舞肆,將跨團(tuán)隊(duì)的影響降到最低焦辅。通過(guò)獨(dú)立地開發(fā)、部署和擴(kuò)展協(xié)同應(yīng)用椿胯,我們希望能夠快速地發(fā)布變更筷登。
我們的微服務(wù)之旅首先是從識(shí)別適合采取微服務(wù)的候選功能開始的。為了識(shí)別合適的候選功能哩盲,我們必須要考慮如何建模良好服務(wù)的核心概念前方。核心概念遵循服務(wù)間松耦合和服務(wù)內(nèi)高內(nèi)聚的原則。服務(wù)內(nèi)的高內(nèi)聚通常反映在保持相關(guān)行為的一致性方面廉油。在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中惠险,相關(guān)行為反應(yīng)為限界上下文(Bounded Context)。限界上下文是領(lǐng)域模型中的語(yǔ)義邊界抒线,服務(wù)會(huì)負(fù)責(zé)定義良好的一個(gè)業(yè)務(wù)功能班巩,限界上下文會(huì)對(duì)服務(wù)進(jìn)行描述。
在我們的場(chǎng)景中嘶炭,我們使用協(xié)作應(yīng)用作為高層級(jí)的限界上下文抱慌,它反映了粗粒度的服務(wù)邊界逊桦。這是一個(gè)很好的起點(diǎn),后續(xù)我們會(huì)將它們拆分為更加細(xì)粒度的服務(wù)層遥缕。
我們首先從 JUST DRIVE 的限界上下文開始卫袒,也就是負(fù)責(zé)文檔管理的協(xié)作應(yīng)用。每個(gè)文檔都是由作者創(chuàng)建的单匣。作者相關(guān)的數(shù)據(jù)來(lái)自 profile,而后者又是由 profile 管理的限界上下文來(lái)進(jìn)行管理的宝穗,這個(gè)功能依然位于單體應(yīng)用中户秤。
我們從頭構(gòu)建了一個(gè)共存(co-existing)的服務(wù)。它實(shí)際上并不完全與當(dāng)前功能的相同逮矛,相反鸡号,我們引入了新的 UI、添加了更多的特性并將數(shù)據(jù)結(jié)構(gòu)做了重大的變更须鼎。新服務(wù)的限界上下文包括負(fù)責(zé)業(yè)務(wù)邏輯的領(lǐng)域模型鲸伴、編排用例的和管理事務(wù)的應(yīng)用服務(wù)以及輸入輸出的適配器,比如 REST 端點(diǎn)和用于持久化管理的適配器晋控。新服務(wù)會(huì)獨(dú)占文檔狀態(tài)汞窗,也就是說(shuō),它是唯一能夠讀取和寫入文檔的服務(wù)赡译。
如前文所述仲吏,每個(gè)文檔都是由作者創(chuàng)建的,作者的數(shù)據(jù)來(lái)源于單體應(yīng)用所管理的 profile 數(shù)據(jù)蝌焚。
那么問題就來(lái)了裹唆,新服務(wù)和單體應(yīng)用之間該如何交互呢?
為了避免每次展現(xiàn)文檔的時(shí)候都從 profile 服務(wù)中獲取作者數(shù)據(jù)只洒,我們?cè)谛碌姆?wù)中保留了相關(guān)作者數(shù)據(jù)的一個(gè)本地副本许帐。只要不破壞數(shù)據(jù)的所有權(quán),數(shù)據(jù)冗余是沒有問題的毕谴,在我們這個(gè)場(chǎng)景中成畦,只要 profile 相關(guān)的限界上下文依然獨(dú)占 profile 狀態(tài)即可。
由于本地副本和原始的數(shù)據(jù)會(huì)隨著時(shí)間的推移而產(chǎn)生差異析珊,所以單體應(yīng)用需要在 profile 更新的時(shí)候通知我們羡鸥。在 profile 發(fā)生變化的時(shí)候,單體應(yīng)用會(huì)發(fā)布一個(gè) ProfileUpdatedEvent 事件忠寻,新服務(wù)需要訂閱這個(gè)事件惧浴。新服務(wù)消費(fèi)該事件并相應(yīng)地更新本地副本。
這種事件驅(qū)動(dòng)的服務(wù)集成方式降低了服務(wù)之間的耦合奕剃,因?yàn)槲覀儸F(xiàn)在不需要跨上下文遠(yuǎn)程直接查詢單體應(yīng)用了衷旅。這種方式增加了自治性捐腿,新服務(wù)能夠?qū)Ρ镜馗北咀鋈魏问虑椋夷軌蜃寯?shù)據(jù)連接(join)更加高效柿顶,因?yàn)樗梢允褂帽镜馗北具B接作者數(shù)據(jù)茄袖,無(wú)需通過(guò)網(wǎng)絡(luò)。
我們從頭構(gòu)建了一個(gè)共存的服務(wù)嘁锯,并且為了實(shí)現(xiàn)數(shù)據(jù)復(fù)制的目的宪祥,引入了事件驅(qū)動(dòng)形式的服務(wù)交互。
我們遇到了什么挑戰(zhàn)以及是如何解決的
從頭開始構(gòu)建共存的服務(wù)通常是一種很好的分解策略家乘,當(dāng)你想要擺脫某些東西的束縛時(shí)蝗羊,更是如此,比如想要脫離過(guò)時(shí)的業(yè)務(wù)邏輯或者現(xiàn)有的技術(shù)棧仁锯。但是在解耦第一個(gè)服務(wù)的時(shí)候耀找,我們一次性做了太多的事情。如前文所述业崖,我們不僅從頭構(gòu)建了一個(gè)共存的服務(wù)野芒,還引入了新的 UI、添加了更多的特性双炕,還對(duì)數(shù)據(jù)結(jié)構(gòu)做了重大的變更狞悲。在開始的時(shí)候,我們承擔(dān)了太多的責(zé)任雄家,所以在很晚的時(shí)候才看到結(jié)果效诅。但是,在開始階段趟济,快速得到結(jié)果以獲取使用微服務(wù)的經(jīng)驗(yàn)和信心是非常重要的乱投。
在下一個(gè)備選服務(wù)中,我們采取了不同的方式顷编。我們關(guān)注 chat 應(yīng)用的高層級(jí)限界上下文戚炫,并遵循自上而下的漸進(jìn)式分解策略,逐步抽取已有的代碼媳纬。我們首先將 UI 抽取為單獨(dú)的 Web 應(yīng)用双肤,并在單體應(yīng)用側(cè)引入了 REST-API,這樣被抽取出來(lái) Web 應(yīng)用可以訪問該 API钮惠。在這一步椿每,我們可以獨(dú)立地開發(fā)和部署 Web 應(yīng)用朴上,從而能夠?qū)?UI 進(jìn)行快速迭代匪凉。
在抽取完 UI 之后式矫,我們就可以更進(jìn)一步,解耦業(yè)務(wù)邏輯。分解業(yè)務(wù)邏輯會(huì)對(duì)代碼帶來(lái)重大的變更缩赛。根據(jù)依賴關(guān)系耙箍,我們可能需要提供一個(gè)臨時(shí)的 REST API 供單體應(yīng)用使用,以解決業(yè)務(wù)邏輯抽取后所帶來(lái)的問題酥馍。此時(shí)辩昆,我們依然共享相同的數(shù)據(jù)存儲(chǔ)。
為了實(shí)現(xiàn)非耦合的獨(dú)立服務(wù)旨袒,我們最終需要切分?jǐn)?shù)據(jù)存儲(chǔ)汁针,以確保新服務(wù)能夠獨(dú)占 chat 的狀態(tài)。
在每個(gè) chat 討論中砚尽,都會(huì)涉及到參與者扇丛。chat 參與者的數(shù)據(jù)來(lái)源于單體應(yīng)用中的 profile 數(shù)據(jù)。如前面描述的 DRIVE 樣例類似尉辑,我們保存一個(gè) chat 參與者數(shù)據(jù)的本地副本,并訂閱 ProfileUpdatedEvent 事件较屿,從而讓本地副本數(shù)據(jù)與單體應(yīng)用中原始數(shù)據(jù)的保持同步隧魄。
從此處開始,我們就可以繼續(xù)從單體應(yīng)用中抽取下一個(gè)限界上下文隘蝎,或者將我們的粗粒度服務(wù)隨后拆分為更細(xì)粒度的服務(wù)购啄。
另外一項(xiàng)挑戰(zhàn)是對(duì)授權(quán)的處理
幾乎對(duì)于每個(gè)服務(wù),我們都會(huì)面臨如何授權(quán)的問題嘱么。我為你描述一個(gè)背景:授權(quán)處理是非常細(xì)粒度的狮含,一直向下延伸到領(lǐng)域?qū)ο蠹?jí)別。每個(gè)協(xié)作應(yīng)用都要控制其領(lǐng)域?qū)ο蟮臋?quán)限曼振,比如文檔的權(quán)限是由該文檔所在的父文件夾的授權(quán)設(shè)置來(lái)控制的几迄。
另一方面,授權(quán)不僅僅是細(xì)粒度的冰评,還依賴于服務(wù)之間的交互映胁,在某些場(chǎng)景下,領(lǐng)域?qū)ο蟮氖跈?quán)還依賴于父領(lǐng)域?qū)ο蟮氖跈?quán)信息甲雅,而父領(lǐng)域?qū)ο蟮氖跈?quán)信息是位于其他服務(wù)中的解孙,比如,要讀取某個(gè)內(nèi)容頁(yè)相關(guān)的文檔或者為內(nèi)容頁(yè)添加文檔的話抛人,需要依賴于這個(gè)頁(yè)面的授權(quán)設(shè)置弛姜,而這個(gè)頁(yè)面的授權(quán)配置位于與文檔本身不同的服務(wù)中。
因?yàn)檫@些復(fù)雜的需求妖枚,解決分布式授權(quán)的問題給我們帶來(lái)了很大的困擾廷臼,而且我們沒有在早期提供解決方案。這樣帶來(lái)的結(jié)果完全適得其反。其中一個(gè)后果就是我們添加了一個(gè)新的服務(wù)到單體應(yīng)用中中剩,而單體應(yīng)用其實(shí)早就已經(jīng)解決過(guò)授權(quán)的問題了忌穿。我們讓單體應(yīng)用變得更大了,而不是讓它變得更小结啼。另外一個(gè)后果就是掠剑,我們開始在每個(gè)服務(wù)上都實(shí)現(xiàn)授權(quán)。起初郊愧,這種做法看上去是合理的朴译,因?yàn)槲覀冏畛醯募僭O(shè)是授權(quán)屬于領(lǐng)域模型所在的限界上下文,但是我們忽略了服務(wù)之間的依賴關(guān)系属铁。所以眠寿,我們不斷地來(lái)回復(fù)制數(shù)據(jù),增加了沖突的風(fēng)險(xiǎn)焦蘑。
長(zhǎng)話短說(shuō):我們最終將授權(quán)處理合并到了一個(gè)中心化的微服務(wù)中盯拱。
與中心化服務(wù)一并出現(xiàn)的是引入分布式單體應(yīng)用的風(fēng)險(xiǎn)。當(dāng)修改系統(tǒng)中的某一部分時(shí)例嘱,你必須要同時(shí)修改其他的組成部分狡逢,這是已引入分布式單體應(yīng)用的強(qiáng)烈信號(hào)。以我們的場(chǎng)景為例拼卵,當(dāng)引入需要授權(quán)的新協(xié)作應(yīng)用時(shí)奢浑,我們需要同時(shí)修改中心化的授權(quán)服務(wù)。我們同時(shí)遇到了單體應(yīng)用和分布式應(yīng)用的缺點(diǎn):服務(wù)是緊耦合的腋腮,而且服務(wù)還需要通過(guò)緩慢雀彼、不穩(wěn)定的網(wǎng)絡(luò)來(lái)進(jìn)行通信。
于是即寡,我們提供了一個(gè)通用的契約徊哑,這個(gè)契約屬于授權(quán)服務(wù),所有的下游服務(wù)都必須要遵守該契約嘿悬。在我們的場(chǎng)景中实柠,服務(wù)會(huì)將授權(quán)相關(guān)的行為轉(zhuǎn)換成授權(quán)服務(wù)能夠理解的契約,授權(quán)服務(wù)不需要額外的轉(zhuǎn)換善涨。這種轉(zhuǎn)換是在每個(gè)下游服務(wù)中發(fā)生的窒盐,而不是在中心化的授權(quán)服務(wù)中發(fā)生的。這種通用契約能夠確保我們?cè)谝胄碌姆?wù)時(shí)钢拧,不需要同時(shí)修改和重新部署中心化的認(rèn)證服務(wù)了蟹漓。有個(gè)先決條件是這個(gè)通用的契約是穩(wěn)定的,或者說(shuō)至少向下兼容源内,否則的話葡粒,我們會(huì)將問題轉(zhuǎn)移給下游服務(wù),這會(huì)導(dǎo)致它們需要不斷進(jìn)行更新。
我們學(xué)習(xí)到了什么
在開始階段需要特別注意嗽交,最好從易于提取的小型服務(wù)開始卿嘲,以便于快速得到結(jié)果并獲取使用微服務(wù)的早期經(jīng)驗(yàn)。如果要處理粗粒度的大型服務(wù)夫壁,就我們而言拾枣,將拆分過(guò)程分為增量式的步驟會(huì)更加易于管理,例如增量式地由上到下進(jìn)行分解盒让,也就是每次只執(zhí)行一個(gè)可管理的步驟梅肤。
盡早處理橫切性的關(guān)注點(diǎn)非常重要,這樣能夠避免適得其反的后果邑茄,比如不斷擴(kuò)大單體應(yīng)用而不是縮減它姨蝴,或者在每個(gè)服務(wù)中都重新實(shí)現(xiàn)橫切性的關(guān)注點(diǎn)。
在引入中心化的橫切服務(wù)時(shí)肺缕,需要注意不要引入分布式單體應(yīng)用左医。在這種情況下,通用且穩(wěn)定的契約能夠幫助我們避免出現(xiàn)分布式單體應(yīng)用同木。
要設(shè)計(jì)易于演化的系統(tǒng)炒辉,事件驅(qū)動(dòng)的服務(wù)交互方式是實(shí)現(xiàn)服務(wù)間高度解耦的關(guān)鍵。事件可以用作通知泉手,也可以用于生成數(shù)據(jù)副本(關(guān)于事件驅(qū)動(dòng)的狀態(tài)轉(zhuǎn)移,參見上文關(guān)于從頭構(gòu)建共存服務(wù)的內(nèi)容)偶器,我們還可以通過(guò)長(zhǎng)期保留事件將事件存儲(chǔ)作為主要的數(shù)據(jù)源斩萌。
當(dāng)事件單純用于通知的目的時(shí),其他上下文中的額外數(shù)據(jù)通常會(huì)以跨上下文查詢的方式直接進(jìn)行請(qǐng)求屏轰,比如 REST 請(qǐng)求颊郎。我們可能會(huì)更喜歡遠(yuǎn)程查詢的簡(jiǎn)潔性,而不愿處理本地維護(hù)數(shù)據(jù)集所帶來(lái)的開銷霎苗,在數(shù)據(jù)集會(huì)不斷增長(zhǎng)的情況下更是如此姆吭。但是遠(yuǎn)程查詢?cè)黾恿朔?wù)之間的耦合性,并且在運(yùn)行時(shí)將服務(wù)綁定在了一起唁盏。
我們可以將對(duì)其他上下文的遠(yuǎn)程查詢進(jìn)行內(nèi)部化處理内狸,這是通過(guò)引入相關(guān)跨上下文數(shù)據(jù)的本地副本來(lái)實(shí)現(xiàn)的。如上面的 JUST DRIVE 樣例所述厘擂,為了避免每次展現(xiàn)文檔的時(shí)候都從 profile 服務(wù)中請(qǐng)求相關(guān)的作者數(shù)據(jù)昆淡,我們復(fù)制了作者數(shù)據(jù),并在文檔微服務(wù)中保留了一個(gè)本地副本刽严。我們需要保證副本數(shù)據(jù)和原始數(shù)據(jù)的同步昂灵,這意味著當(dāng)原始數(shù)據(jù)變化的時(shí)候,要立即同步我們的本地副本。為了獲取已修改數(shù)據(jù)的通知眨补,服務(wù)需要訂閱包含數(shù)據(jù)變化的事件并相應(yīng)地更新本地副本管削。在本例中,事件是用來(lái)生成數(shù)據(jù)副本的撑螺,這樣能夠避免遠(yuǎn)程查詢并降低服務(wù)之間的耦合性含思。這種方式也能實(shí)現(xiàn)更好的自治性,因?yàn)榉?wù)能夠?qū)Ρ镜馗北緢?zhí)行任何操作实蓬。
對(duì)于事件驅(qū)動(dòng)服務(wù)的交互茸俭,我們?cè)谠缙诰鸵肓?Apache Kafka,這是一個(gè)分布式安皱、具有容錯(cuò)性调鬓、可擴(kuò)展的日志提交服務(wù)。最初酌伊,我們使用 Apache Kafka 的主要目的是實(shí)現(xiàn)通知和生成數(shù)據(jù)副本的功能腾窝。最近,我們引入 Apache Kafka Streams 作為共享的事實(shí)源居砖,以減少數(shù)據(jù)復(fù)制的開銷并實(shí)現(xiàn)服務(wù)的高可插拔性虹脯,降低新服務(wù)進(jìn)入的壁壘。
流是無(wú)界有序且持續(xù)更新的結(jié)構(gòu)化數(shù)據(jù)記錄組成的序列奏候。數(shù)據(jù)記錄有一個(gè) key-value 對(duì)組成循集。
當(dāng)你的服務(wù)在 Apache Kafka 流上下文中啟動(dòng)時(shí),Kafka 主題將會(huì)加載到你的流中蔗草,你可以在服務(wù)的范圍內(nèi)處理它咒彤。主題通常是一個(gè)邏輯分類,表明了哪些服務(wù)可以發(fā)布和訂閱咒精。每個(gè)流都會(huì)緩沖到一個(gè)狀態(tài)存儲(chǔ)中镶柱,這是一個(gè)輕量級(jí)的基于硬盤的數(shù)據(jù)。加載的流會(huì)在你自己的代碼中使用模叙,不會(huì)在 Kafka 代理中運(yùn)行歇拆,它運(yùn)行在你的微服務(wù)進(jìn)程中。流能夠讓數(shù)據(jù)出現(xiàn)在任何需要的地方范咨,這會(huì)增強(qiáng)性能和自治性故觅。
Apache Kafka 提供了一個(gè) Stream API。Stream 可以借助領(lǐng)域特定語(yǔ)言(Domain Specific Language渠啊,DSL)進(jìn)行連接逻卖、過(guò)濾、分組或聚合昭抒,流中的每條消息都可以使用類似函數(shù)的操作進(jìn)行處理评也,比如映射炼杖、轉(zhuǎn)換或窺探等。
在實(shí)現(xiàn)流處理的時(shí)候盗迟,通常會(huì)同時(shí)需要流以及進(jìn)行功能增強(qiáng)的數(shù)據(jù)庫(kù)坤邪。Kafka 的 Streams API 通過(guò)對(duì)流和表的核心抽象提供了該功能。在流和表之前其實(shí)存在緊密的關(guān)聯(lián)關(guān)系罚缕,也就是所謂的流 - 表二元性(stream-table duality)艇纺。流可以看做表的變更日志,流中的每條數(shù)據(jù)記錄都捕獲了表中的一次狀態(tài)變更邮弹。表可以視為快照黔衡,對(duì)應(yīng)于流中每個(gè) key 的最新值。
當(dāng)我們想要展現(xiàn)一條文檔及其作者數(shù)據(jù)時(shí)腌乡,借助 Kafka Streams盟劫,我們可以這樣做:文檔服務(wù)根據(jù) document 主題創(chuàng)建一個(gè) KStream,并根據(jù) profile 主題得到的作者相關(guān) profile 數(shù)據(jù)來(lái)完善該文檔与纽。在這個(gè)增強(qiáng)的過(guò)程中侣签,文檔服務(wù)會(huì)根據(jù) profile 主題創(chuàng)建 KTable。現(xiàn)在急迂,我們可以將流和表進(jìn)行連接影所,并將它的結(jié)果保存為新的狀態(tài)存儲(chǔ),這樣就可以在外部進(jìn)行訪問了僚碎,運(yùn)行方式類似于內(nèi)置的 Materialized View猴娩。每當(dāng) profile 或文檔更新的時(shí)候,它相關(guān)的 Materialized View 也會(huì)進(jìn)行更新勺阐。
將 Apache Kafka Streams 與其他的事件驅(qū)動(dòng)方式進(jìn)行對(duì)比的話胀溺,它不需要維護(hù)本地副本,這減少了維護(hù)數(shù)據(jù)副本和保持?jǐn)?shù)據(jù)同步的開銷皆看。Apache Kafka Streams 會(huì)將數(shù)據(jù)推送到需要的地方,并且運(yùn)行在與服務(wù)相同的進(jìn)程中背零。它增加了可插拔性腰吟,你可以插入新的服務(wù)并立即使用流,不需要搭建額外的數(shù)據(jù)存儲(chǔ)徙瓶。它能夠減少開銷毛雇,增強(qiáng)性能、自治性并降低新服務(wù)的進(jìn)入壁壘侦镇。
這個(gè)轉(zhuǎn)換的過(guò)程并不是隔離運(yùn)行的灵疮,它會(huì)受到各種環(huán)境因素的影響:團(tuán)隊(duì)的規(guī)模、結(jié)構(gòu)和技能都會(huì)影響到怎樣做才是可控的壳繁,尤其是在開始階段震捣,如果是一個(gè)的團(tuán)隊(duì)并且 DevOps 經(jīng)驗(yàn)很欠缺的話荔棉,將會(huì)對(duì)轉(zhuǎn)換的速度造成一定的影響。
你的轉(zhuǎn)換過(guò)程還會(huì)受到一個(gè)因素的影響蒿赢,那就是你依然要處理遺留的系統(tǒng)润樱。維護(hù)它所耗費(fèi)的時(shí)間會(huì)相應(yīng)地減少進(jìn)行轉(zhuǎn)換的時(shí)間。運(yùn)行時(shí)環(huán)境也會(huì)影響這個(gè)過(guò)程羡棵。你是在內(nèi)部環(huán)境中運(yùn)行還是作為云原生應(yīng)用運(yùn)行壹若?你是否能夠依賴托管服務(wù),比如托管的 API- 網(wǎng)關(guān)皂冰,還是需要自行搭建和維護(hù)店展?
如果你的策略是在短期內(nèi)引入新特性的話,那么就會(huì)面臨決策上的糾結(jié)秃流,那就是將新需求在何處實(shí)現(xiàn):如果作為新的獨(dú)立服務(wù)的話赂蕴,會(huì)耗費(fèi)一定的時(shí)間,如果采取快捷的方式剔应,將其添加到單體應(yīng)用上睡腿,那就會(huì)帶來(lái)讓單體應(yīng)用越來(lái)越大,而不能對(duì)其進(jìn)行縮減的風(fēng)險(xiǎn)峻贮。
注意那些阻礙前進(jìn)或減緩速度的環(huán)境因素席怪,并相應(yīng)地調(diào)整它們,或者至少在你的組織中引起注意纤控。記住: 每一次過(guò)程都是不同的挂捻,你的過(guò)程可能和我們的完全不同。
如果下次繼續(xù)引入微服務(wù)的話船万,在哪些方面的做法會(huì)有所不同
首先刻撒,我會(huì)檢查組織的戰(zhàn)略是否與微服務(wù)的目標(biāo)相一致,那就是最大化產(chǎn)品的敏捷性以及獨(dú)立快速地發(fā)布變更耿导,例如声怔,如果你的組織關(guān)注較長(zhǎng)的發(fā)布周期并希望將所有內(nèi)容部署在一起,那么微服務(wù)可能不是最佳選擇舱呻,因?yàn)闊o(wú)法充分利用微服務(wù)的優(yōu)勢(shì)醋火。
如果你決定采用微服務(wù)的話,每個(gè)人都必須投入其中箱吕,包括管理層芥驳。每個(gè)人都需要意識(shí)到這個(gè)過(guò)程是非常復(fù)雜和耗時(shí)的,當(dāng)你還沒有多少經(jīng)驗(yàn)的時(shí)候更是如此茬高。
與產(chǎn)品相符的兆旬、跨功能的、自治的團(tuán)隊(duì)可以很好地與微服務(wù)架構(gòu)模式協(xié)作怎栽,但是應(yīng)該盡早考慮向 DevOps 文化的轉(zhuǎn)變丽猬。每個(gè)團(tuán)隊(duì)都應(yīng)該為持續(xù)的迭代做好準(zhǔn)備宿饱,并且能夠開發(fā)、發(fā)布宝鼓、運(yùn)維和監(jiān)控他們負(fù)責(zé)的服務(wù)刑棵。
將單體應(yīng)用拆分成多個(gè)獨(dú)立的服務(wù),只是整個(gè)過(guò)程的一部分愚铡,而如何運(yùn)維它們則是另外一回事兒蛉签。你擁有的服務(wù)越多,它們的自動(dòng)化構(gòu)建和部署流程就變得越重要沥寥。
如果我重做一次的話碍舍,我將從一個(gè)易于抽取的小型候選服務(wù)開始,不僅要關(guān)注它的拆分邑雅,還要關(guān)注構(gòu)建和部署的自動(dòng)化片橡,并預(yù)先監(jiān)控第一個(gè)服務(wù),它可以作為后續(xù)服務(wù)的基礎(chǔ)淮野。要搭建這個(gè)基礎(chǔ)環(huán)境捧书,可能需要從每個(gè)組抽取一個(gè)人形成一個(gè)臨時(shí)的任務(wù)組。
每個(gè)微服務(wù)從一開始就應(yīng)該有自己的 CI/CD 管道骤星。另一個(gè)需要考慮的問題是將每個(gè)微服務(wù)進(jìn)行容器化经瓷,從而能夠得到輕量級(jí)、封裝好的運(yùn)行時(shí)環(huán)境洞难,它能夠在各個(gè)階段中保持一致舆吮,如果你以后想要在云環(huán)境中運(yùn)行服務(wù)的話,更需如此队贱。
另外色冀,還需要盡早考慮監(jiān)控的問題,包括日志聚合柱嫌。監(jiān)控不僅包括服務(wù)器锋恬,還包括服務(wù)指標(biāo),如請(qǐng)求延遲编丘、吞吐量和錯(cuò)誤率与学,以便于跟蹤服務(wù)的健康狀況和可用性。要形成結(jié)構(gòu)化和標(biāo)準(zhǔn)化的日志輸出瘪吏,如時(shí)間格式(如 ISO8601)和時(shí)區(qū)(如 UTC),并引入具有 correlation id 和日志聚合的請(qǐng)求上下文蜗巧,這有助于問題的診斷和剖析掌眠。
很多事情需要預(yù)先處理,這非常耗時(shí)并且需要得到整個(gè)組織的關(guān)注幕屹。微服務(wù)是實(shí)現(xiàn)最大化產(chǎn)品敏捷性的投資蓝丙,而不在于削減成本级遭。
為了保持在市場(chǎng)上的競(jìng)爭(zhēng)力,產(chǎn)品的敏捷性和持續(xù)改進(jìn)是區(qū)別于競(jìng)爭(zhēng)對(duì)手的關(guān)鍵因素渺尘。微服務(wù)可以提升產(chǎn)品的敏捷性并持續(xù)改善挫鸽,但是它需要每個(gè)人的貢獻(xiàn),包括管理者鸥跟。
獲取以上高級(jí)架構(gòu)最新視頻丢郊,
歡迎加入Java進(jìn)階架構(gòu)交流群:142019080。直接點(diǎn)擊鏈接加群医咨。https://jq.qq.com/?_wv=1027&k=5lXBNZ7