一、前言
微服務(wù)(MicroServices)是一種架構(gòu)風(fēng)格,一個(gè)大型復(fù)雜軟件應(yīng)用由多個(gè)微服務(wù)和前端展示層組成。系統(tǒng)中的各個(gè)微服務(wù)可被獨(dú)立部署,各個(gè)微服務(wù)之間是松耦合的。每個(gè)微服務(wù)僅關(guān)注于完成一件任務(wù)并很好地完成該任務(wù)。在所有情況下,每個(gè)任務(wù)代表著一個(gè)小的業(yè)務(wù)能力。
以往我們開發(fā)應(yīng)用程序都是單體應(yīng)用(可以理解為一個(gè)部署包包含了項(xiàng)目的所有功能),雖然開發(fā)和部署比較方便,但后期隨著業(yè)務(wù)的不斷增加為了能夠達(dá)到響應(yīng)業(yè)務(wù)需求,單體應(yīng)用的開發(fā)迭代和性能瓶頸等問題愈發(fā)明顯,微服務(wù)就是解決此問題的有效手段。
想要回答為什么要使用微服務(wù)架構(gòu)的問題,首先應(yīng)該了解一體化架構(gòu)。
二、什么是一體化架構(gòu)
一體化架構(gòu)顧名思義,將應(yīng)用各層打成一個(gè)包來部署。為了讓代碼正常工作,一體化應(yīng)用的所有組件缺一不可。
以典型的3層傳統(tǒng)web應(yīng)用為例,該應(yīng)用由用戶界面、數(shù)據(jù)庫、服務(wù)器端應(yīng)用組成。這里的服務(wù)器端應(yīng)用被稱為monolith(一體化或者單體),包含表現(xiàn)、業(yè)務(wù)層、數(shù)據(jù)層。所有代碼都存在于同一個(gè)代碼庫中。為了讓代碼工作起來,它被部署成為一個(gè)單元。任何一個(gè)小的改動(dòng)變化,都需要重新構(gòu)建和部署整個(gè)應(yīng)用。
單體應(yīng)用架構(gòu)
二、什么是微服務(wù)架構(gòu)
微服務(wù)架構(gòu)是一種架構(gòu)風(fēng)格,整個(gè)應(yīng)用被劃分并設(shè)計(jì)為以業(yè)務(wù)域
為模型的松散耦合的獨(dú)立服務(wù)。微服務(wù)中的“微”非常具有欺騙性,事實(shí)上它沒有規(guī)定服務(wù)的規(guī)模有多小或多大。
這里的重點(diǎn)是每個(gè)獨(dú)立服務(wù)都有一個(gè)業(yè)務(wù)邊界,可以獨(dú)立開發(fā)、測(cè)試、部署、監(jiān)控和擴(kuò)展,甚至可以用不同的編程語言開發(fā)它們。
微服務(wù)架構(gòu)
在基于微服務(wù)的架構(gòu)中,理想情況下
每個(gè)組件或服務(wù)都有自己的數(shù)據(jù)庫。沒有集中式數(shù)據(jù)庫,我們可以根據(jù)需要為每個(gè)單獨(dú)的微服務(wù)使用NoSQL、RDBMS或任何其他數(shù)據(jù)庫,這也是讓微服務(wù)真正獨(dú)立的原因之一。
三、一體化架構(gòu)的問題
或者說是微服務(wù)架構(gòu)所解決的問題。
3.1難以擴(kuò)展
一體化架構(gòu)應(yīng)用只能通過在負(fù)載均衡器后面放置整個(gè)應(yīng)用程序的多個(gè)實(shí)例來進(jìn)行水平擴(kuò)展。如果應(yīng)用中的特定服務(wù)需要擴(kuò)展,則沒有簡(jiǎn)單的選項(xiàng)。我們需要完整地?cái)U(kuò)展應(yīng)用程序,這顯然會(huì)造成不必要的資源浪費(fèi)。
相比之下,基于微服務(wù)的應(yīng)用程序允許我們根據(jù)需要獨(dú)立擴(kuò)展單個(gè)服務(wù)。在上圖中,如果需要縮放服務(wù)B,則可以有10個(gè)實(shí)例,同時(shí)保持其他實(shí)例,并可以根據(jù)需要隨時(shí)更改。
3.2交付時(shí)間長(zhǎng)
一體化架構(gòu)在單個(gè)應(yīng)用的任何部分/層中進(jìn)行的任何更改都需要構(gòu)建和部署整個(gè)應(yīng)用程序。個(gè)人開發(fā)人員還需要下載整個(gè)應(yīng)用程序代碼來修復(fù)和測(cè)試,而不僅僅是受影響的模塊,這就影響到了持續(xù)部署的效率。
而在微服務(wù)架構(gòu)中,如果僅在一百個(gè)微服務(wù)中的一個(gè)中需要改變,則僅構(gòu)建和部署改變的微服務(wù),沒有必要部署一切。我們甚至可以在短時(shí)間內(nèi)多次部署。
3.3應(yīng)用復(fù)雜性
過去,隨著應(yīng)用規(guī)模的增長(zhǎng)(功能、功能等),團(tuán)隊(duì)也會(huì)相應(yīng)擴(kuò)張,應(yīng)用很快就就會(huì)變得復(fù)雜和交織在一起。隨著不同的團(tuán)隊(duì)不斷修改代碼,維護(hù)模塊化結(jié)構(gòu)慢慢變得越來越困難,并慢慢導(dǎo)致像意大利面一樣交織的代碼。這不僅會(huì)影響代碼質(zhì)量,還會(huì)影響整個(gè)組織。
在基于微服務(wù)的應(yīng)用中,每個(gè)團(tuán)隊(duì)都在單獨(dú)的微服務(wù)上工作,代碼會(huì)有序很多。
3.4沒有明確的所有權(quán)
在一體化應(yīng)用中,看起來獨(dú)立的團(tuán)隊(duì)實(shí)際上并不是獨(dú)立的。它們同時(shí)在相同的代碼庫上工作,嚴(yán)重依賴于彼此。
在基于微服務(wù)的應(yīng)用中,獨(dú)立團(tuán)隊(duì)處理單獨(dú)的微服務(wù)。一個(gè)團(tuán)隊(duì)將擁有一個(gè)完整的微服務(wù)。工作的明確所有權(quán)明確控制服務(wù)的一切,包括開發(fā)、部署和監(jiān)控。
3.5故障級(jí)聯(lián)
如果沒有正確設(shè)計(jì),一體化應(yīng)用的一部分失敗可能會(huì)級(jí)聯(lián)并導(dǎo)致整個(gè)系統(tǒng)崩潰。
在基于微服務(wù)的架構(gòu)的情況下,我們可以使用斷路器
來避免這種故障。
3.6Dev和Ops之間的墻
開發(fā)團(tuán)隊(duì)通常會(huì)進(jìn)行開發(fā)、測(cè)試,一旦部署,就會(huì)將維護(hù)和支持的所有權(quán)交給運(yùn)維團(tuán)隊(duì),應(yīng)用此時(shí)與開發(fā)團(tuán)隊(duì)無關(guān)了,而運(yùn)維團(tuán)隊(duì)需要努力在生產(chǎn)環(huán)境中支持一體化架構(gòu)應(yīng)用。
在基于微服務(wù)的應(yīng)用中,團(tuán)隊(duì)的組織理解為“構(gòu)建它、運(yùn)行它”,開發(fā)團(tuán)隊(duì)繼續(xù)在生產(chǎn)中擁有該應(yīng)用。
3.7陷入某種技術(shù)/語言
使用一體化架構(gòu),意味著被某種已實(shí)現(xiàn)的技術(shù)/語言鎖定。如果需要更改技術(shù)/語言,則必須重寫整個(gè)應(yīng)用程序。
使用微服務(wù),每個(gè)服務(wù)可以根據(jù)需求和業(yè)務(wù)使用不同的技術(shù)或語言實(shí)現(xiàn)。任何改變服務(wù)技術(shù)/語言的決定都只需要重寫該特定服務(wù),因?yàn)樗形⒎?wù)都是相互獨(dú)立的。
3.8支持微服務(wù)的正確工具/技術(shù)的可用性
幾年前,我們還沒有適當(dāng)?shù)墓ぞ吆图夹g(shù)來支持微服務(wù)。但自從Docker容器和云基礎(chǔ)設(shè)施(特別是PaaS)向大眾提供服務(wù)以來,微服務(wù)正在大規(guī)模采用,因?yàn)樗鼈兲峁┝宋覀兯璧摹白杂伞?,而無需進(jìn)行傳統(tǒng)的配置程序。
四、認(rèn)識(shí)微服務(wù)小結(jié)
4.1 微服務(wù)架構(gòu)優(yōu)點(diǎn)
每個(gè)服務(wù)足夠內(nèi)聚,足夠小,代碼容易理解,開發(fā)效率高
服務(wù)直接可以獨(dú)立部署,讓持續(xù)部署成為可能
每個(gè)服務(wù)可以各自進(jìn)行水平和垂直擴(kuò)展,而且每個(gè)服務(wù)可以根
據(jù)需要部署到合適的硬件和軟件上容易擴(kuò)大開發(fā)團(tuán)隊(duì),可以根據(jù)每個(gè)組件組織開發(fā)團(tuán)隊(duì)
提高容錯(cuò)性,一個(gè)服務(wù)的問題不會(huì)讓整個(gè)系統(tǒng)癱瘓
系統(tǒng)不會(huì)長(zhǎng)期限制在某個(gè)技術(shù)棧上
降低成本??杀M量復(fù)用已有功能,避免重復(fù)造輪子??梢源蟠鬁p少項(xiàng)目建設(shè)過程的調(diào)研、設(shè)計(jì)、開發(fā)、測(cè)試、運(yùn)維的成本。
易于開發(fā)與維護(hù):一個(gè)微服務(wù)只關(guān)心一個(gè)特定的業(yè)務(wù)功能,所以他業(yè)務(wù)清晰,代碼量較少。獨(dú)立開發(fā)部署服務(wù)。
局部修改容易,所以開發(fā)上線速度和靈活性
更高的代碼質(zhì)量
獲得圍繞業(yè)務(wù)功能創(chuàng)建/組織的代碼
提高生產(chǎn)力。開發(fā)人員專職自己的微服務(wù)開發(fā),對(duì)業(yè)務(wù)和代碼都熟悉。
更容易擴(kuò)展
技術(shù)棧不受限:每個(gè)服務(wù)可用不同的開發(fā)語言
按需伸縮:可根據(jù)不同的需求,實(shí)現(xiàn)細(xì)粒度的拓展。
為多端化服務(wù)(多種客戶端,例如:瀏覽器,車載終端,安卓,IOS等等)打下堅(jiān)實(shí)的基礎(chǔ)。
持續(xù)集成和持續(xù)交付的應(yīng)用大大提高生產(chǎn)力。提高開發(fā)人員生產(chǎn)力, 開發(fā)人員只需要將代碼推送到代碼倉庫即可。該代碼將被集成,測(cè)試,部署,再次測(cè)試,與基礎(chǔ)功能(Maven依賴)合并,經(jīng)過質(zhì)量審查,并準(zhǔn)備以極高的信心進(jìn)行部署。減少了手動(dòng)操作的環(huán)節(jié),極大的提高了重復(fù)工作的效率。
4.2 微服務(wù)的缺點(diǎn)
運(yùn)維的技術(shù)復(fù)雜度和相應(yīng)的運(yùn)維成本(測(cè)試、變更、部署)
必須建立開發(fā)運(yùn)維一體化機(jī)制Devops
必須有完備的監(jiān)控手段和自動(dòng)化恢復(fù)手段
分區(qū)數(shù)據(jù)庫可能帶來的業(yè)務(wù)數(shù)據(jù)同步與一致性問題
微服務(wù)的接口將成為變更的敏感位(盡量保持接口的穩(wěn)定性,不能經(jīng)常變化入?yún)⒑统鰠ⅲ?/p>
運(yùn)維要求高:服務(wù)更多意味著運(yùn)維的投入,單體應(yīng)用只需保證一個(gè)應(yīng)用的正常運(yùn)行,而在微服務(wù)中,需要保證幾十上百的服務(wù)正常運(yùn)行與協(xié)作。服務(wù)越小,獨(dú)立性更好,但是相應(yīng)的服務(wù)數(shù)量就越多。每個(gè)服務(wù)都需要獨(dú)立的配置、部署、監(jiān)控、日志收集等,成本呈指數(shù)級(jí)增長(zhǎng)。需要更高的自動(dòng)化運(yùn)維策略。
微服務(wù)應(yīng)用作為分布式系統(tǒng)帶來了復(fù)雜性。當(dāng)應(yīng)用是整體應(yīng)用程序時(shí),模塊之間調(diào)用都在應(yīng)用之內(nèi),即使進(jìn)行分布式部署,依然在應(yīng)用內(nèi)調(diào)用。可是微服務(wù)是多個(gè)獨(dú)立的服務(wù),當(dāng)進(jìn)行模塊調(diào)用的時(shí)候,分布式將會(huì)麻煩。
多個(gè)獨(dú)立數(shù)據(jù)庫,事務(wù)的實(shí)現(xiàn)更具挑戰(zhàn)性。
依賴管理復(fù)雜,
測(cè)試微服務(wù)變得復(fù)雜,當(dāng)一個(gè)服務(wù)依賴另外一個(gè)服務(wù)時(shí),測(cè)試時(shí)候需要另外一個(gè)服務(wù)的支持。
五、直觀體驗(yàn)一下微服務(wù)
5.1 微服務(wù)功能
該微服務(wù)實(shí)現(xiàn)如下功能:為vue開發(fā)的小程序及h5頁面提供數(shù)據(jù)接口(就是HTTP的訪問地址),可以查詢編程語言的服務(wù)。
實(shí)現(xiàn)了如下的微服務(wù)接口:
通過篩選條件查詢:https://localhost:8888/v2/vue/api/programLanguage/getByName?language_name=P
返回:['PHP','Python']
無條件的全量查詢:https://localhost:8888/v2/vue/api/programLanguage/getAll
返回:['C','vue','java','PHP','Python','C++']
另外一種條件查詢方式:https://localhost:8888/v2/vue/api/programLanguage/getdetail/C
返回:{'laguage_name':'C','star_number':10,'desc':'萬物之源C語言。C語言于1969年至1973年之間由AT&T公司旗下貝爾實(shí)驗(yàn)室創(chuàng)建完成,用于構(gòu)建Unix操作系統(tǒng)。C語言是一種通用型命令式計(jì)算機(jī)編程語言,其支持結(jié)構(gòu)化編程、詞匯變量范圍與遞歸,同時(shí)亦是套能夠預(yù)防各類未預(yù)期操作的靜態(tài)類型系統(tǒng)。其最初構(gòu)建目標(biāo)在于編寫系統(tǒng)軟件。'}
5.2 代碼示例
以第一個(gè)接口https://localhost:8888/v2/vue/api/programLanguage/getByName
代碼為例。
@ApiVersion(2) // 加入接口url的版本控制,http://localhost:8888/v2/vue/api/programLanguage/getByName?language_name=C
@RequestMapping(value = '/programLanguage/getByName')
public List<String> getByName(@RequestParam String languageName) {
List<String> filterList = languageList.stream().
filter(s -> s.toLowerCase().contains(languageName.toLowerCase())).
collect(Collectors.toList());
return filterList;
}
六、微服務(wù)拆分的策略
6.0 微服務(wù)拆分原則
2、微服務(wù)粒度適中:以完成一個(gè)完整的業(yè)務(wù)需求來拆分微服務(wù),不是越細(xì)越好;
(1)以業(yè)務(wù)模型切入:以業(yè)務(wù)模型中的業(yè)務(wù)活動(dòng)為基本單元進(jìn)行拆分,即微服務(wù)邊界最大不能超過業(yè)務(wù)活動(dòng);
(2)演進(jìn)式拆分:在實(shí)際應(yīng)用過程中根據(jù)負(fù)載情況進(jìn)行微服務(wù)拆分的細(xì)化,實(shí)現(xiàn)性能升級(jí);
(3)讀寫分離:把負(fù)責(zé)產(chǎn)生與維護(hù)數(shù)據(jù)的業(yè)務(wù)功能和負(fù)責(zé)查詢搜索數(shù)據(jù)的業(yè)務(wù)功能分開。
3、避免環(huán)形依賴與雙向依賴:可應(yīng)用服務(wù)上移、服務(wù)下移等手段避免生成環(huán)形依賴,應(yīng)用回調(diào)等手段避免生成雙向依賴;
4、考慮團(tuán)隊(duì)結(jié)構(gòu):以完成一個(gè)完整的業(yè)務(wù)邏輯所需求的前、中、后端來拆分,便于開發(fā)團(tuán)隊(duì)分別實(shí)現(xiàn)。
6.1 錯(cuò)誤策略
6.2 正確策略
微服務(wù)拆分的核心需求:高內(nèi)聚,低耦合,盡可能的減少微服務(wù)間的調(diào)用,盡可能的減少分布式事務(wù);
微服務(wù)應(yīng)該足夠小,能夠分配一個(gè)團(tuán)隊(duì)(5人左右)去實(shí)現(xiàn),但也不能過細(xì)。
6.2.1 整體思路
6.2.1.1 圍繞限界上下文邊界
理想情況下限界上下文與微服務(wù)為1:1 考慮到其他原則和現(xiàn)實(shí)約束,實(shí)際微服務(wù)的劃分有可能在限界上下文圖的基礎(chǔ)上進(jìn)行合并。 微服務(wù)拆分的底線是不能打破聚合
,打破聚合會(huì)破壞事務(wù)一致性和業(yè)務(wù)約束。
6.2.1.2 對(duì)齊不同業(yè)務(wù)變化速率/相關(guān)度
需注意:
絕對(duì)速率:有些業(yè)務(wù)只是建設(shè)階段變更頻率較高,維護(hù)期相對(duì)穩(wěn)定;有些業(yè)務(wù)在可預(yù)見的將來有持續(xù)需求,變化頻率較高。需要將二者區(qū)分放入不同的微服務(wù)。 相對(duì)速率:軟件系統(tǒng)的響應(yīng)力最終體現(xiàn)在需求的端到端交付周期。如果兩個(gè)業(yè)務(wù)的相關(guān)度比較高,變化速率接近,一個(gè)業(yè)務(wù)需求到來總是需要同時(shí)修改這兩個(gè)上下文。需要將二者放入同一個(gè)微服務(wù)。
6.2.1.3 考慮系統(tǒng)非功能性需求
伸縮性:有些業(yè)務(wù)在容量上有極大的彈性伸縮需求(如秒殺),需要設(shè)計(jì)為獨(dú)立微服務(wù) 可用性:有些業(yè)務(wù)在可用性上有極高要求(如交易撮合),需要設(shè)計(jì)為獨(dú)立微服務(wù) 安全性:有些業(yè)務(wù)在(數(shù)據(jù))安全性上有獨(dú)立的要求,需要設(shè)計(jì)為獨(dú)立微服務(wù) 其他非功能性需求:微服務(wù)視角主要關(guān)注伸縮性、可用性和安全性,但其他非功能性需求也是設(shè)計(jì)一個(gè)系統(tǒng)需要考慮的
6.2.1.4 其他設(shè)計(jì)約束
復(fù)雜度
微服務(wù)在調(diào)測(cè)、部署、運(yùn)維等方面會(huì)帶來額外的復(fù)雜度和開銷。將微服務(wù)粒度拆分過細(xì)反而是反模式。需要考慮需要解決問題的復(fù)雜度,將相對(duì)簡(jiǎn)單的服務(wù)合并在一起。團(tuán)隊(duì)結(jié)構(gòu)
建議一個(gè)微服務(wù)由一個(gè)獨(dú)立的2 pizza team(7+-2個(gè)人)進(jìn)行端到端的開發(fā)和維護(hù)。團(tuán)隊(duì)規(guī)模過大或過小有時(shí)也是服務(wù)拆分粒度不合理的跡象。此外需要考慮團(tuán)隊(duì)成員的能力搭配。技術(shù)異構(gòu)
有些遺留系統(tǒng),業(yè)務(wù)軟件包(如ERP)或技術(shù)組件(如搜索引擎)形成了天然的服務(wù)邊界,是很難被打破成微服務(wù)。
6.2.1.0 戰(zhàn)略設(shè)計(jì)階段詳解
6.2.1.1 業(yè)務(wù)目標(biāo)梳理
價(jià)值定位畫布是價(jià)值定位設(shè)計(jì)里的經(jīng)典實(shí)踐,描述了產(chǎn)品提供的價(jià)值如何與顧客需求之間建立聯(lián)系,以及為什么顧客要去買你的產(chǎn)品。
通過分析用戶工作中的成就和痛點(diǎn)來找到需要提供的服務(wù),明確產(chǎn)品存在的價(jià)值和意義。
6.2.1.2 場(chǎng)景梳理方法
步驟1: 選定由價(jià)值定位畫布中提煉出的一塊問題域/業(yè)務(wù);
步驟2: 參與者針對(duì)所選定的問題域,發(fā)散思考其中存在的各個(gè)服務(wù)場(chǎng)景,可以采用貼便簽紙的方式;
步驟3: 對(duì)發(fā)散所得的服務(wù)場(chǎng)景進(jìn)行收斂整理,得到場(chǎng)景分類清單。
6.2.1.3 流程梳理和領(lǐng)域建模示例
6.2.1.4 限界上下文設(shè)計(jì)示例
6.2.1.5 確定微服務(wù)拆分
設(shè)計(jì)因素1:圍繞限界上下文邊界,
理想情況下限界上下文與微服務(wù)為1:1,
微服務(wù)拆分的底線是不能打破聚合,打破聚合會(huì)破壞事務(wù)一致性和業(yè)務(wù)約束。設(shè)計(jì)因素2:考慮系統(tǒng)非功能性需求:安全性、伸縮性等 設(shè)計(jì)因素3:其他設(shè)計(jì)約束:復(fù)雜度、團(tuán)隊(duì)結(jié)構(gòu)、技術(shù)異構(gòu)
6.2.1.6 分層模型
6.2.1.7 需求變更
七、微服務(wù)拆分模式
7.1 絞殺模式 (Strangler)
遺留系統(tǒng)不再進(jìn)行功能新增
,只做 Bug 修復(fù)和例行維護(hù)。這樣帶來的變更風(fēng)險(xiǎn)最小,但演進(jìn)時(shí)間較長(zhǎng)。7.2 修繕者模式(Rehab)
八、微服務(wù)改造的切入點(diǎn)
數(shù)據(jù)源維度
第三方接口-->全新業(yè)務(wù)全新數(shù)據(jù)庫-->老業(yè)務(wù)簡(jiǎn)單數(shù)據(jù)庫結(jié)構(gòu)-->老業(yè)務(wù)復(fù)雜數(shù)據(jù)庫結(jié)構(gòu)-->老的第三方封閉系統(tǒng)。
接口類別維度
單一查詢接口-->全新業(yè)務(wù)涉及增刪改查接口-->老業(yè)務(wù)數(shù)據(jù)寫入接口。