這篇文章主要目的是面向初接觸微服務的朋友簡單介紹微服務基礎建設所需要的各個模塊以及緣由外恕。
起點
首先,我們得有一個“服務”。根據(jù)定義狈孔,我們可以把每個服務實例都視作一個黑盒。這個盒子有著明確的輸入點和輸出點材义,并且(理想情況下)僅通過這些輸入和輸出點和外界產(chǎn)生關聯(lián)均抽。每個服務實例會擁有專屬的網(wǎng)絡地址、獨立的計算資源其掂,并且獨立部署油挥。客戶端通過訪問服務實例的地址來調(diào)用服務 API款熬。不同服務也可以相互調(diào)用深寥。
配置管理器:統(tǒng)一管理配置
在微服務體系中,每個服務都獨立部署和運行华烟,團隊可以根據(jù)需要自行選擇增加和減少計算資源翩迈。一個服務可能會跑多個實例,每個服務實例都會需要做配置盔夜。為了方便統(tǒng)一調(diào)整配置负饲,我們可以把配置中心化,每個服務實例都去找配置管理器(Configuration Manager)拿配置喂链。當配置更新的時候返十,我們也可以讓服務實例再去拿新的配置。
服務名冊:解耦主機地址
這也引出了一個問題:網(wǎng)絡地址(比如 IP)很容易因為擴容椭微、維護而變動洞坑,調(diào)用者難以實時獲知可用的地址。
鑒于此蝇率,我們可以把網(wǎng)絡地址抽象成不容易變動的概念迟杂,比如給每個服務一個固定的名字”灸剑互聯(lián)網(wǎng)使用 DNS 來解決這個問題排拷,對應到微服務基建里面就是服務名冊(Service Registry)。
每個服務實例在運行期間锅尘,都會以心跳的形式向服務名冊發(fā)送注冊信息监氢,包括服務的 ID 、訪問地址以及健康狀況。這樣浪腐,需要訪問服務的時候纵揍,客戶端就可以先問服務名冊拿可用的實例地址,然后再訪問實例來調(diào)用服務议街。除了更好地定位實例地址泽谨,服務名冊還可以在某些實例下線、維護或升級的時候把其臨時從名冊中去掉傍睹,讓服務不斷線隔盛。
服務之間的調(diào)用也是如此,先找名冊拿網(wǎng)絡地址拾稳,再進行調(diào)用吮炕。
API 網(wǎng)關:入口和路由
找名冊要地址,然后調(diào)用服務 API访得,這些是每個客戶端都會去做的瑣事龙亲,我們完全可以把這些事情抽象、集中悍抑,把服務的 API 整合到一個大的中心點鳄炉,然后把要地址和調(diào)用服務 API 這樣的細節(jié)封裝起來,所有客戶端都只跟這個中心點對話搜骡,不再直接訪問單個服務拂盯。
從結構上看,這個中心點把整個架構劃分成了內(nèi)外兩部分记靡,內(nèi)部是所有的服務谈竿,客戶端則在外部,中心點站在中間摸吠。它作為內(nèi)外的唯一通道空凸,被順理成章地命名作“API 網(wǎng)關”(API Gateway),有時候也被稱做“邊緣服務”(Edge Service)寸痢。
API 網(wǎng)關作為唯一出入口呀洲,又占據(jù)了最前沿的有利位置,所以有時還會承載別的公共功能啼止,比如我們馬上會提到的鑒權道逗。
鑒權服務:身份和權限問題
順著這個架構繼續(xù)開發(fā),我們會遇到新的問題:不方便的鑒權献烦。
鑒權(Auth)包括了兩個部分:身份認證(Authentication)和權限驗證(Authorization)憔辫。身份認證關心的是“你是誰”,權限驗證關心的是“你能不能做某件事”仿荆。
身份和權限都是高度中心化的概念。
對于一個系統(tǒng)來說,用戶的身份必須是統(tǒng)一的拢操。不能說這個用戶在做這個事情的時候是張三锦亦,做那個事情的時候是李四。此外令境,用戶的認證狀態(tài)也應該是統(tǒng)一的杠园。不能說用戶訪問這個服務的時候是已登錄認證,訪問另一個服務時又是未登錄狀態(tài)舔庶。所以抛蚁,只能有一個身份認證方。
權限稍微復雜一點惕橙。和身份不同瞧甩,權限通常分成兩種類別:功能權限和數(shù)據(jù)權限。這樣的劃分對應了現(xiàn)實世界中常見的權限模式:你的角色決定了你的職能弥鹦,而職能范圍通常由附加條件來限制肚逸。比如,你是一個法官彬坏,對案件有裁決權朦促,但是你是 A 區(qū)的法官,只能判 A 區(qū)的案子栓始。再比如务冕,某個快餐門店的經(jīng)理有權看員工的詳細資料,但是只能看自己門店的員工資料幻赚。
兩種權限都由全局的規(guī)則來確定禀忆,而不掌握在執(zhí)行部門。比如坯屿,誰來判案油湖,取決于法律,而不取決于法院领跛。誰能查看誰的資料乏德,也不由資料保管部門決定,而由規(guī)章制度決定吠昭。
在現(xiàn)實的情況中喊括,組織可能會有專門的審核部門來驗證權限,但對那些不是特別敏感的權限矢棚,企業(yè)會讓各個部門自行驗證郑什。不過不管誰來執(zhí)行驗證,都必須拿著同一份規(guī)章制度蒲肋,不能各說各話蘑拯。這份制度必須由中心機構來統(tǒng)一制定钝满、維護。也就是說申窘,權限的管理也應該中心化弯蚜。
明確鑒權中心化之后,我們就可以開發(fā)一個公用的鑒權服務剃法,執(zhí)行身份認證和權限驗證碎捺。下一個問題是:誰來發(fā)起鑒權?
所有服務的調(diào)用都要求調(diào)用者明確自己的身份贷洲,所以自然身份認證越靠前越好收厨。作為出入口的 API 網(wǎng)關自然是發(fā)起身份認證的不二之選。權限驗證則稍微復雜优构,完全值得另起一文詳述诵叁。此處我們暫時假定權限驗證也由 API 網(wǎng)關來發(fā)起。
消息中介:異步和通知
開發(fā)繼續(xù)進行俩块,一切風平浪靜黎休,技術上暫時沒有什么問題。不過玉凯,業(yè)務上有一個問題需要解決势腮。
比如,我們做一個在線商城漫仆,要求在訂單成功創(chuàng)建的一刻捎拯,倉庫就要啟動備貨和發(fā)貨的流程。問題是盲厌,訂單和倉儲是兩個服務署照,不同團隊在負責,而且從關注點來說吗浩,訂單服務并不關心倉儲相關的問題建芙,所以訂單服務不可能在創(chuàng)建訂單的時候去主動通知倉儲服務。倉儲服務只能定時輪詢訂單服務懂扼,看看有沒有新的訂單禁荸。這不僅麻煩,而且實時性不夠阀湿。
仔細想想赶熟,我們會發(fā)現(xiàn)這種需求很常見,信息的產(chǎn)生者并不知道(也不關心)誰會對信息產(chǎn)生興趣陷嘴。比如我們可能會有一個監(jiān)控服務需要實時展示產(chǎn)品銷量映砖,有一個 BI 服務需要獲取客戶購買產(chǎn)品的信息來做分析,等等灾挨。既然這是一個常見需求邑退,我們不妨把它模式化竹宋,形成一個機制:信息產(chǎn)生者把通知發(fā)出來,收到通知的人再確定是否需要采取行動地技。
這就意味著我們需要再引入一個中心化的公共服務:消息中介(Message Broker)逝撬。當某個事件發(fā)生的時候(比如用戶激活成功、訂單創(chuàng)建成功)乓土,服務可以朝消息隊列發(fā)一條消息。而其他服務可以訂閱這些消息溯警,并針對這些消息做出反應趣苏。
比如,倉儲服務可以訂閱訂單創(chuàng)建成功的消息梯轻。這樣食磕,訂單成功創(chuàng)建后,訂單服務將這個消息發(fā)到消息中介喳挑,消息中介通知倉儲服務彬伦,倉儲服務一看,就問訂單服務要新的訂單信息伊诵,最后单绑,啟動出庫流程。
消息中介除了能廣播事件之外曹宴,還能做異步調(diào)用搂橙。把同步的調(diào)用轉化成異步的回調(diào)。針對調(diào)用時間長和不要求實時結果的調(diào)用笛坦,可以增加性能区转,提升體驗。
前置后端:優(yōu)化前端開發(fā)
走到這里版扩,其實體系已經(jīng)比較完備》侠耄現(xiàn)在的問題是,如何讓微服務基建結構和研發(fā)團隊常見的結構更好地對應起來礁芦。這要求我們從康威定律的角度來看待整個基建的設計蜻韭。
在圍繞用戶和價值的軟件研發(fā)流程中,我們常用用戶歷程和用戶故事來捕捉和跟蹤價值的實現(xiàn)宴偿。一個用戶故事通常會包含一個有明確邊界湘捎、明確驗收標準和明確價值的業(yè)務步驟。
問題在于窄刘,支撐一個故事有前后兩端的研發(fā)工作窥妇,二者是不同步的。前端由業(yè)務流程和設計來驅動娩践,希望按順序產(chǎn)出活翩;后端則由業(yè)務資源和建模來驅動烹骨,希望按模塊來產(chǎn)出。
比如說材泄,前端常常會因為設計的原因調(diào)整自己需要的字段沮焕,而后端從建模的角度并沒有這個需要,也沒有動力頻繁地去跟隨前端的調(diào)整拉宗,使得前端不得不在不穩(wěn)定的網(wǎng)絡條件下傳輸多余的信息峦树,占用了寶貴的網(wǎng)絡帶寬。
此外旦事,前端呈現(xiàn)某個業(yè)務步驟的時候魁巩,有兩種信息不屬于當前必備信息,但常常需要和必要信息一起展示姐浮。一種是狀態(tài)信息谷遂,比如當前的登錄狀態(tài)和用戶名,短消息的數(shù)量等等卖鲤。一種是垂直相關的信息肾扰,比如在展示文章的時候順便展示一下相關的文章。
這就要求前端在調(diào)用主服務的同時還要再調(diào)用多個不同的服務蛋逾。且不說這些服務有可能會有調(diào)用超時集晚、出錯的可能,僅僅是多出來一堆異步請求换怖,就已經(jīng)足夠讓前端效率降低一大截了甩恼。
在微服務體系下,這些問題更加嚴重沉颂,因為現(xiàn)在不僅僅是前后端的差別条摸,不同服務還由不同團隊負責。這些團隊的訴求和日程不一铸屉,很難做到前端所需要的快速響應钉蒲。
這些問題和麻煩可能會催生一個“緩沖帶”,比如后端出專人來負責對接前端的需要彻坛,或者前端派駐一個人到后端來談需求顷啼。按康威定律,這種溝通體系昌屉,久而久之钙蒙,很容易以軟件的形式沉淀下來,形成一個專屬的中間層间驮。
要調(diào)和前后端的不同步是不可能的躬厌,而這種中間層是自然催生的解決方案,可以保留竞帽。新的問題是扛施,它的職責是什么鸿捧?應該把它放在哪?應該由誰來維護疙渣?
分析下來匙奴,其責任有二。第一是解耦前后端的工作妄荔,降低相互的影響泼菌。前端需要的東西可以寫在中間層里,讓它頻繁變化也沒有關系啦租。后端如果還沒有準備好酒贬,前端也可以在這一層模擬假的數(shù)據(jù)蛔六,不至于被阻塞阎姥。第二則是提升前端的運行效率瘪弓。前端可以把所需要的多個服務的東西統(tǒng)一匯總构资,一次拿完乌妒,免得發(fā)多個請求粤蝎。
放置的位置則在 API 網(wǎng)關之內(nèi)碰缔,讓它可以享有 API 網(wǎng)關所帶來的好處和保護赋除。
最后是維護問題阱缓。按照“誰主張,誰舉證”的原則举农,既然有了這個中間層荆针,好處讓前端得了,那么颁糟,理論上應該由前端來維護航背。
這樣,一個主要為前端服務的中間層就定義好了棱貌。不同類型的前端(桌面玖媚、移動)可能會有不同的需要,為了避免中間層的碎片化婚脱,我們可以讓各個中間層都特定的前端類型緊密耦合今魔,比如桌面專用、移動專用障贸。如此错森,每個中間層都像是某類型前端的專享后端,所以“前置后端”(Backend-for-Frontend篮洁,簡稱 BFF)也因此得名涩维。
回路熔斷器:提高容錯度
現(xiàn)在,調(diào)試也方便了嘀粱,我們又繼續(xù)開發(fā)激挪。一開始沒有什么問題辰狡,但部署到預生產(chǎn)環(huán)境的時候,又一個問題出現(xiàn)了:整個體系的容錯度很低垄分。一個小錯誤容易被層層傳遞和放大宛篇,導致整個體系的崩潰。
我們都知道薄湿,編程最麻煩的就是遠程調(diào)用叫倍。本地調(diào)用大部分時候結果是“成功”或“失敗”,但遠程調(diào)用則很可能是“無響應”豺瘤∵壕耄“無響應”有可能是正常的,對方可能稍后會給你結果坐求,也可能是因為對方已經(jīng)死了蚕泽,沒法給你響應。最壞的結果桥嗤,就是門口擠滿了人须妻,大家都在等你給結果,而你也在等別人給結果泛领,資源全部占用來等荒吏,什么也做不了。
不過渊鞋,遠程調(diào)用是無法避免的绰更。在微服務體系中,這個問題被進一步放大锡宋。這是因為微服務的模塊化以服務為單位儡湾,而每個服務獨立部署和運維,使得服務之間的調(diào)用成了家常便飯执俩。
在這種嚴峻的情況下盒粮,我們必須從架構上盡量提高整個服務體系的容錯度,讓個別服務的問題不至于影響到全局奠滑。
具體的做法丹皱,則是給遠程調(diào)用加一個熔斷閾值檢查,當調(diào)用超時次數(shù)超過閾值時宋税,就不再調(diào)用摊崭,直接返回錯誤。過一段時間之后杰赛,再把閾值恢復呢簸,嘗試繼續(xù)調(diào)用,重復前面的過程。這個機制就是回路熔斷根时,而這個工具則是回路熔斷器(Circuit Breaker)瘦赫。
除了隔離已經(jīng)出錯的服務實例,熔斷器還有一個重要的功能是提供備用方案蛤迎。雖然我們把所有業(yè)務都拆成了服務确虱,但服務有高低貴賤之分。有一些服務屬于關鍵服務替裆,一旦出問題校辩,則整個流程無法繼續(xù),有一些則屬于分支服務辆童,即便錯了宜咒,也不會影響大局。
比如說把鉴,購買商品的時候故黑,常常會根據(jù)用戶的習慣和當前正在購買的東西做一些推薦。負責推薦的服務出問題的話庭砍,大不了就不推薦了倍阐,不應該影響用戶正常的購買流程。同理逗威,如果是在線點餐的地址定位服務出問題了,我們也應該允許用戶手動選擇餐廳進行點餐——體驗雖然不佳岔冀,但至少正常的流程仍然可以走完凯旭。基于這個考慮使套,熔斷器應該為非必要的服務調(diào)用提供備用方案罐呼,盡量保證核心流程的順暢。
有了回路熔斷器侦高,遠程調(diào)用出錯的問題就從一定程度上緩解了嫉柴。結合回路熔斷器和對熔斷閾值變化的監(jiān)控,開發(fā)者可以更容易地發(fā)現(xiàn)問題奉呛,并及時采取行動计螺。
負載均衡器:提升服務彈性
要正式上線,我們還必須做好負載均衡(Load Balancing瞧壮,下簡稱 LB)登馒,提升整個服務的彈性。要做負載均衡咆槽,從理論上有兩種方式:
客戶端負載均衡(Client-Side LB):由客戶端來決定如何分散請求陈轿。 中間方負載均衡(Mid-Tier LB):由 DNS、網(wǎng)關等中間方來決定如何分散請求。
現(xiàn)在麦射,服務名冊中已經(jīng)有了服務及其對應的實例地址列表蛾娶,所以客戶端的負載均衡最簡便的方式就是把地址拉下來,然后依次或者隨機選擇可用的地址潜秋。中間方的負載均衡則選擇面較多蛔琅,從最外層的 DNS 到網(wǎng)關都可以不同程度地去按需要去做。
擴展基建
現(xiàn)在半等,微服務基建基本完成了揍愁。如果有需要,我們可以對這個基建進行擴展杀饵。在做擴展時莽囤,架構師應該注意區(qū)分哪些東西應該中心化,哪些東西應該由服務自行決定切距。 比如說朽缎,在本文提到的基建之中,(幾乎是)強制完全中心化的模塊有:
配置管理
服務名冊
消息隊列
其中谜悟,配置管理和服務名冊是所有服務都需要的基礎設施话肖,必然需要統(tǒng)一。消息隊列和日志收集都是為了跨服務的操作和追蹤葡幸,也必須中心化最筒。
半中心化的模塊則有:
路由
鑒權
路由和鑒權都必須統(tǒng)一,我們前面討論過蔚叨。不過床蜘,微服務可能會向外界暴露“自用”和“客用”等多套公共 API(比如快遞公司內(nèi)部使用的物流 API 和開放給第三方使用的物流 API),所以可能會有兩個 API 網(wǎng)關蔑水,對應會有兩套 API 目錄和兩套鑒權體系邢锯,所以,它們是“半中心化”搀别。
這些都是中心化丹擎、半中心化的選擇范例。每一次中心化的選擇都可能會讓整個架構變得死板歇父,失去靈活性蒂培,所以,我們在設計和擴展基建的時候應該特別注意這個問題榜苫。
除了中心化的選擇之外毁渗,架構發(fā)展的另一個關注點,是讓業(yè)務保持“黑盒”单刁。
我們把每個服務之間的關聯(lián)抽取了出來灸异,也把權限的定義和驗證抽取了出來府适,每個服務變得簡單而純粹,成了“純業(yè)務式服務”肺樟,等同于一個僅包含了業(yè)務規(guī)則的黑盒檐春。這樣,不管服務和模塊再多么伯,也沒有影響疟暖。業(yè)務的重用性也很高。
總而言之田柔,搭建好了微服務的必要設施之后俐巴,剩下的就要根據(jù)實際情況和項目經(jīng)驗來繼續(xù)調(diào)整了。比如硬爆,我們可能會選擇把很多功能合并到一層欣舵,以避免過度分層所帶來的不必要的性能損失,或者對整個基建進行一些細節(jié)微調(diào)缀磕。只要把控好“中心-自理”和“業(yè)務-非業(yè)務”之間的關系缘圈,這個基礎設施就能健康地發(fā)展。
微服務基建總結
總結此文袜蚕,微服務的基建應該包括如下一些組件(按請求流中的出場順序):
配置管理:配置集中管理糟把。
API 網(wǎng)關:對外的 API 總目錄;API 依賴關系牲剃;發(fā)起鑒權遣疯。
服務名冊:服務的注冊和發(fā)現(xiàn)。
鑒權服務:提供鑒權服務:認證身份凿傅,驗證功能權限缠犀。
前置后端:按前端的需求拆解請求、調(diào)用服務狭归,并匯總、轉換結果文判。
消息中介:全局通知機制过椎;異步調(diào)用機制。
回路熔斷:隔離出問題的服務并等待其恢復戏仓;提供備用方案疚宇。
負載均衡:避免服務過載。
需要說明的是赏殃,這些組件的組合形式敷待,具體拆分形式,是否需要仁热,都需要結合實際項目和團隊的情況來調(diào)整榜揖。本文權作拋磚引玉,請讀者知悉。在這里順便給大家推薦一個架構交流群:617434785举哟,里面會分享一些資深架構師錄制的視頻錄像:有Spring思劳,MyBatis,Netty源碼分析妨猩,高并發(fā)潜叛、高性能、分布式壶硅、微服務架構的原理威兜,JVM性能優(yōu)化這些成為架構師必備的知識體系。還能領取免費的學習資源庐椒。相信對于已經(jīng)工作和遇到技術瓶頸的碼友椒舵,在這個群里會有你需要的內(nèi)容