做者:古霜卡比
www.cnblogs.com/skabyy/p/11396571.htmlhtml
本文將介紹微服務架構和相關的組件,介紹他們是什麼以及爲何要使用微服務架構和這些組件。本文側重於簡明地表達微服務架構的全局圖景,所以不會涉及具體如何使用組件等細節。前端
要理解微服務,首先要先理解不是微服務的那些。一般跟微服務相對的是單體應用,即將全部功能都打包成在一個獨立單元的應用程序。java
從單體應用到微服務並非一蹴而就的,這是一個逐漸演變的過程。本文將以一個網上超市應用爲例來講明這一過程。spring
幾年前,小明和小皮一塊兒創業作網上超市。小明負責程序開發,小皮負責其餘事宜。當時互聯網還不發達,網上超市仍是藍海。只要功能實現了就能隨便賺錢。數據庫
因此他們的需求很簡單,只須要一個網站掛在公網,用戶可以在這個網站上瀏覽商品、購買商品;另外還需一個管理後臺,能夠管理商品、用戶、以及訂單數據。編程
咱們整理一下功能清單:小程序
網站微信小程序
用戶註冊、登陸功能緩存
商品展現安全
下單
管理後臺
用戶管理
商品管理
訂單管理
因爲需求簡單,小明左手右手一個慢動做,網站就作好了。管理後臺出於安全考慮,不和網站作在一塊兒,小明右手左手慢動做重播,管理網站也作好了。
整體架構圖以下:
小明揮一揮手,找了家雲服務部署上去,網站就上線了。上線後好評如潮,深受各種肥宅喜好。小明小皮美滋滋地開始躺着收錢。
好景不長,沒過幾天,各種網上超市緊跟着拔地而起,對小明小皮形成了強烈的衝擊。
在競爭的壓力下,小明小皮決定開展一些營銷手段:
開展促銷活動。好比元旦全場打折,春節買二送一,情人節狗糧優惠券等等。
拓展渠道,新增移動端營銷。除了網站外,還須要開發移動端APP,微信小程序等。
精準營銷。利用歷史數據對用戶進行分析,提供個性化服務。
……
這些活動都須要程序開發的支持。小明拉了同窗小紅加入團隊。小紅負責數據分析以及移動端相關開發。小明負責促銷活動相關功能的開發。
由於開發任務比較緊迫,小明小紅沒有好好規劃整個系統的架構,隨便拍了拍腦殼,決定把促銷管理和數據分析放在管理後臺裏,微信和移動端APP另外搭建。通宵了幾天後,新功能和新應用基本完工。
這時架構圖以下:
這一階段存在不少不合理的地方:
網站和移動端應用有不少相同業務邏輯的重複代碼。
數據有時候經過數據庫共享,有時候經過接口調用傳輸。接口調用關係雜亂。
單個應用爲了給其餘應用提供接口,漸漸地越改越大,包含了不少原本就不屬於它的邏輯。應用邊界模糊,功能歸屬混亂。
管理後臺在一開始的設計中保障級別較低。加入數據分析和促銷管理相關功能後出現性能瓶頸,影響了其餘應用。
數據庫表結構被多個應用依賴,沒法重構和優化。
全部應用都在一個數據庫上操做,數據庫出現性能瓶頸。特別是數據分析跑起來的時候,數據庫性能急劇降低。
開發、測試、部署、維護愈發困難。即便只改動一個小功能,也須要整個應用一塊兒發佈。
有時候發佈會不當心帶上了一些未經測試的代碼,或者修改了一個功能後,另外一個意想不到的地方出錯了。
爲了減輕發佈可能產生的問題的影響和線上業務停頓的影響,全部應用都要在凌晨三四點執行發佈。
發佈後爲了驗證應用正常運行,還得盯到次日白天的用戶高峯期……
團隊出現推諉扯皮現象。關於一些公用的功能應該建設在哪一個應用上的問題經常要爭論好久,最後要麼乾脆各作各的,或者隨便放個地方可是都不維護。
儘管有着諸多問題,但也不可否認這一階段的成果:快速地根據業務變化建設了系統。
不過緊迫且繁重的任務容易令人陷入局部、短淺的思惟方式,從而作出妥協式的決策。在這種架構中,每一個人都只關注在本身的一畝三分地,缺少全局的、長遠的設計。
久而久之,系統建設將會愈來愈困難,甚至陷入不斷推翻、重建的循環。
幸虧小明和小紅是有追求有理想的好青年。意識到問題後,小明和小紅從瑣碎的業務需求中騰出了一部分精力,開始梳理總體架構,針對問題準備着手改造。
要作改造,首先你須要有足夠的精力和資源。若是你的需求方(業務人員、項目經理、上司等)很強勢地一心追求需求進度,以至於你沒法挪出額外的精力和資源的話,那麼你可能沒法作任何事……
在編程的世界中,最重要的即是抽象能力。微服務改造的過程實際上也是個抽象的過程。小明和小紅整理了網上超市的業務邏輯,抽象出公用的業務能力,作成幾個公共服務:
用戶服務
商品服務
促銷服務
訂單服務
數據分析服務
各個應用後臺只需從這些服務獲取所需的數據,從而刪去了大量冗餘的代碼,就剩個輕薄的控制層和前端。
這一階段的架構以下:
這個階段只是將服務分開了,數據庫依然是共用的,因此一些煙囪式系統的缺點仍然存在:
數據庫成爲性能瓶頸,而且有單點故障的風險。
數據管理趨向混亂。即便一開始有良好的模塊化設計,隨着時間推移,總會有一個服務直接從數據庫取另外一個服務的數據的現象。
數據庫表結構可能被多個服務依賴,牽一髮而動全身,很難調整。
若是一直保持共用數據庫的模式,則整個架構會愈來愈僵化,失去了微服務架構的意義。
所以小明和小紅一氣呵成,把數據庫也拆分了。全部持久化層相互隔離,由各個服務本身負責。另外,爲了提升系統的實時性,加入了消息隊列機制。
架構以下:
徹底拆分後各個服務能夠採用異構的技術。好比數據分析服務可使用數據倉庫做爲持久化層,以便於高效地作一些統計計算;商品服務和促銷服務訪問頻率比較大,所以加入了緩存機制等。
還有一種抽象出公共邏輯的方法是把這些公共邏輯作成公共的框架庫。這種方法能夠減小服務調用的性能損耗。可是這種方法的管理成本很是高昂,很難保證全部應用版本的一致性。
數據庫拆分也有一些問題和挑戰:好比說跨庫級聯的需求,經過服務查詢數據顆粒度的粗細問題等。可是這些問題能夠經過合理的設計來解決。整體來講,數據庫拆分是一個利大於弊的。
微服務架構還有一個技術外的好處,它使整個系統的分工更加明確,責任更加清晰,每一個人專心負責爲其餘人提供更好的服務。
在單體應用的時代,公共的業務功能常常沒有明確的歸屬。
最後要麼各作各的,每一個人都從新實現了一遍;要麼是隨機一我的(通常是能力比較強或者比較熱心的人)作到他負責的應用裏面。
在後者的狀況下,這我的在負責本身應用以外,還要額外負責給別人提供這些公共的功能——而這個功能原本是無人負責的,僅僅由於他能力較強/比較熱心,就莫名地背鍋(這種狀況還被美其名曰能者多勞)。
結果最後你們都不肯意提供公共的功能。久而久之,團隊裏的人漸漸變得各自爲政,再也不關心全局的架構設計。
從這個角度上看,使用微服務架構同時也須要組織結構作相應的調整。因此說作微服務改造須要管理者的支持。
改造完成後,小明和小紅分清楚各自的鍋。兩人十分滿意,一切就像是麥克斯韋方程組同樣漂亮完美。
然而……
春天來了,萬物復甦,又到了一年一度的購物狂歡節。眼看着日訂單數量蹭蹭地上漲,小皮小明小紅喜笑顏開。惋惜好景不長,樂極生悲,忽然嘣的一下,系統掛了。推薦:這纔是微服務劃分的正確姿式!
以往單體應用,排查問題一般是看一下日誌,研究錯誤信息和調用堆棧。而微服務架構整個應用分散成多個服務,定位故障點很是困難。
小明一臺機器一臺機器地查看日誌,一個服務一個服務地手工調用。
通過十幾分鐘的查找,小明終於定位到故障點:促銷服務因爲接收的請求量太大而中止響應了。其餘服務都直接或間接地會調用促銷服務,因而也跟着宕機了。
在微服務架構中,一個服務故障可能會產生雪崩效用,致使整個系統故障。其實在節前,小明和小紅是有作過請求量評估的。
按照預計,服務器資源是足以支持節日的請求量的,因此確定是哪裏出了問題。
不過形勢緊急,隨着每一分每一秒流逝的都是白花花的銀子,所以小明也沒時間排查問題,當機立斷在雲上新建了幾臺虛擬機,而後一臺一臺地部署新的促銷服務節點。
幾分鐘的操做後,系統總算是勉強恢復正常了。整個故障時間內估計損失了幾十萬的銷售額,三人的心在滴血……
過後,小明簡單寫了個日誌分析工具(量太大了,文本編輯器幾乎打不開,打開了肉眼也看不過來),統計了促銷服務的訪問日誌,發如今故障期間,商品服務因爲代碼問題,在某些場景下會對促銷服務發起大量請求。
這個問題並不複雜,小明手指抖一抖,修復了這個價值幾十萬的Bug。
問題是解決了,但誰也沒法保證不會再發生相似的其餘問題。
微服務架構雖然邏輯設計上看是完美的,但就像積木搭建的華麗宮殿同樣,經不起風吹草動。微服務架構雖然解決了舊問題,也引入了新的問題:
微服務架構整個應用分散成多個服務,定位故障點很是困難。
穩定性降低。服務數量變多致使其中一個服務出現故障的機率增大,而且一個服務故障可能致使整個系統掛掉。事實上,在大訪問量的生產場景下,故障老是會出現的。
服務數量很是多,部署、管理的工做量很大。
開發方面:如何保證各個服務在持續開發的狀況下仍然保持協同合做。
測試方面:服務拆分後,幾乎全部功能都會涉及多個服務。本來單個程序的測試變爲服務間調用的測試。測試變得更加複雜。
小明小紅痛定思痛,決心好好解決這些問題。對故障的處理通常從兩方面入手,一方面儘可能減小故障發生的機率,另外一方面下降故障形成的影響。
在高併發分佈式的場景下,故障常常是忽然間就雪崩式爆發。因此必須創建完善的監控體系,儘量發現故障的徵兆。
微服務架構中組件繁多,各個組件所須要監控的指標不一樣。
好比Redis緩存通常監控佔用內存值、網絡流量,數據庫監控鏈接數、磁盤空間,業務服務監控併發數、響應延遲、錯誤率等。
所以若是作一個大而全的監控系統來監控各個組件是不大現實的,並且擴展性會不好。
通常的作法是讓各個組件提供報告本身當前狀態的接口(metrics接口),這個接口輸出的數據格式應該是一致的。
而後部署一個指標採集器組件,定時從這些接口獲取並保持組件狀態,同時提供查詢服務。最後還須要一個UI,從指標採集器查詢各項指標,繪製監控界面或者根據閾值發出告警。
大部分組件都不須要本身動手開發,網絡上有開源組件。小明下載了RedisExporter和MySQLExporter,這兩個組件分別提供了Redis緩存和MySQL數據庫的指標接口。
微服務則根據各個服務的業務邏輯實現自定義的指標接口。
而後小明採用Prometheus做爲指標採集器,Grafana配置監控界面和郵件告警。這樣一套微服務監控系統就搭建起來了:
在微服務架構下,一個用戶的請求每每涉及多個內部服務調用。
爲了方便定位問題,須要可以記錄每一個用戶請求時,微服務內部產生了多少服務調用,及其調用關係。這個叫作鏈路跟蹤。
咱們用一個Istio文檔裏的鏈路跟蹤例子來看看效果:
圖片來自Istio文檔
從圖中能夠看到,這是一個用戶訪問productpage頁面的請求。在請求過程當中,productpage服務順序調用了details和reviews服務的接口。而reviews服務在響應過程當中又調用了ratings的接口。
整個鏈路跟蹤的記錄是一棵樹:
要實現鏈路跟蹤,每次服務調用會在HTTP的HEADERS中記錄至少記錄四項數據:
traceId:traceId標識一個用戶請求的調用鏈路。具備相同traceId的調用屬於同一條鏈路。
spanId:標識一次服務調用的ID,即鏈路跟蹤的節點ID。
parentId:父節點的spanId。
requestTime & responseTime:請求時間和響應時間。
另外,還須要調用日誌收集與存儲的組件,以及展現鏈路調用的UI組件。
以上只是一個極簡的說明,關於鏈路跟蹤的理論依據可詳見Google的Dapper
瞭解了理論基礎後,小明選用了Dapper的一個開源實現Zipkin。而後手指一抖,寫了個HTTP請求的攔截器,在每次HTTP請求時生成這些數據注入到HEADERS,同時異步發送調用日誌到Zipkin的日誌收集器中。
這裏額外提一下,HTTP請求的攔截器,能夠在微服務的代碼中實現,也可使用一個網絡代理組件來實現(不過這樣子每一個微服務都須要加一層代理)。
鏈路跟蹤只能定位到哪一個服務出現問題,不能提供具體的錯誤信息。查找具體的錯誤信息的能力則須要由日誌分析組件來提供。
日誌分析組件應該在微服務興起以前就被普遍使用了。即便單體應用架構,當訪問數變大、或服務器規模增多時,日誌文件的大小會膨脹到難以用文本編輯器進行訪問,更糟的是它們分散在多臺服務器上面。
排查一個問題,須要登陸到各臺服務器去獲取日誌文件,一個一個地查找(並且打開、查找都很慢)想要的日誌信息。
所以,在應用規模變大時,咱們須要一個日誌的「搜索引擎」。以便於能準確的找到想要的日誌。另外,數據源一側還須要收集日誌的組件和展現結果的UI組件:
小明調查了一下,使用了大名鼎鼎地ELK日誌分析組件。ELK是Elasticsearch、Logstash和Kibana三個組件的縮寫。
Elasticsearch:搜索引擎,同時也是日誌的存儲。
Logstash:日誌採集器,它接收日誌輸入,對日誌進行一些預處理,而後輸出到Elasticsearch。
Kibana:UI組件,經過Elasticsearch的API查找數據並展現給用戶。
最後還有一個小問題是如何將日誌發送到Logstash。一種方案是在日誌輸出的時候直接調用Logstash接口將日誌發送過去。推薦:Spring Boot & ES 實戰,值得參考!
這樣一來又(咦,爲啥要用「又」)要修改代碼……因而小明選用了另外一種方案:日誌仍然輸出到文件,每一個服務裏再部署個Agent掃描日誌文件而後輸出給Logstash。
拆分紅微服務後,出現大量的服務,大量的接口,使得整個調用關係亂糟糟的。
常常在開發過程當中,寫着寫着,突然想不起某個數據應該調用哪一個服務。或者寫歪了,調用了不應調用的服務,原本一個只讀的功能結果修改了數據……
爲了應對這些狀況,微服務的調用須要一個把關的東西,也就是網關。推薦:牛逼哄哄的 API 網關是什麼鬼?
在調用者和被調用者中間加一層網關,每次調用時進行權限校驗。另外,網關也能夠做爲一個提供服務接口文檔的平臺。
使用網關有一個問題就是要決定在多大粒度上使用:最粗粒度的方案是整個微服務一個網關,微服務外部經過網關訪問微服務,微服務內部則直接調用;最細粒度則是全部調用,無論是微服務內部調用或者來自外部的調用,都必須經過網關。
折中的方案是按照業務領域將微服務分紅幾個區,區內直接調用,區間經過網關調用。
因爲整個網上超市的服務數量還不算特別多,小明採用的最粗粒度的方案:
前面的組件,都是旨在下降故障發生的可能性。然而故障老是會發生的,因此另外一個須要研究的是如何下降故障產生的影響。推薦:Spring Cloud Eureka 註冊中心集羣搭建,Greenwich 最新版!
關注微信公衆號:Java技術棧,在後臺回覆:cloud,能夠獲取我整理的 N 篇最新Spring Cloud 教程,都是乾貨。
最粗暴的(也是最經常使用的)故障處理策略就是冗餘。通常來講,一個服務都會部署多個實例,這樣一來可以分擔壓力提升性能,二來即便一個實例掛了其餘實例還能響應。
冗餘的一個問題是使用幾個冗餘?這個問題在時間軸上並無一個切確的答案。根據服務功能、時間段的不一樣,須要不一樣數量的實例。
好比在平日裏,可能4個實例已經夠用;而在促銷活動時,流量大增,可能須要40個實例。所以冗餘數量並非一個固定的值,而是根據須要實時調整的。
通常來講新增實例的操做爲:
部署新實例
將新實例註冊到負載均衡或DNS上
操做只有兩步,但若是註冊到負載均衡或DNS的操做爲人工操做的話,那事情就不簡單了。想一想新增40個實例後,要手工輸入40個IP的感受……
解決這個問題的方案是服務自動註冊與發現。
首先,須要部署一個服務發現服務,它提供全部已註冊服務的地址信息的服務。DNS也算是一種服務發現服務。
而後各個應用服務在啓動時自動將本身註冊到服務發現服務上。而且應用服務啓動後會實時(按期)從服務發現服務同步各個應用服務的地址列表到本地。
服務發現服務也會按期檢查應用服務的健康狀態,去掉不健康的實例地址。
這樣新增實例時只須要部署新實例,實例下線時直接關停服務便可,服務發現會自動檢查服務實例的增減。
服務發現還會跟客戶端負載均衡配合使用。因爲應用服務已經同步服務地址列表在本地了,因此訪問微服務時,能夠本身決定負載策略。
甚至能夠在服務註冊時加入一些元數據(服務版本等信息),客戶端負載則根據這些元數據進行流量控制,實現A/B測試、藍綠髮布等功能。
服務發現有不少組件能夠選擇,好比說Zookeeper 、Eureka、Consul、Etcd等。不太小明以爲本身水平不錯,想炫技,因而基於Redis本身寫了一個……
當一個服務由於各類緣由中止響應時,調用方一般會等待一段時間,而後超時或者收到錯誤返回。推薦:Hystrix理論+實戰。
若是調用鏈路比較長,可能會致使請求堆積,整條鏈路佔用大量資源一直在等待下游響應。
因此當屢次訪問一個服務失敗時,應熔斷,標記該服務已中止工做,直接返回錯誤。直至該服務恢復正常後再從新創建鏈接。
圖片來自《微服務設計》
當下遊服務中止工做後,若是該服務並不是核心業務,則上游服務應該降級,以保證核心業務不中斷。推薦閱讀:服務降級的概念及應用手段。
好比網上超市下單界面有一個推薦商品湊單的功能,當推薦模塊掛了後,下單功能不能一塊兒掛掉,只須要暫時關閉推薦功能便可。
一個服務掛掉後,上游服務或者用戶通常會習慣性地重試訪問。
這致使一旦服務恢復正常,極可能由於瞬間網絡流量過大又馬上掛掉,在棺材裏重複着仰臥起坐。所以服務須要可以自我保護——限流。
限流策略有不少,最簡單的好比當單位時間內請求數過多時,丟棄多餘的請求。另外,也能夠考慮分區限流。僅拒絕來自產生大量請求的服務的請求。
例如商品服務和訂單服務都須要訪問促銷服務,商品服務因爲代碼問題發起了大量請求,促銷服務則只限制來自商品服務的請求,來自訂單服務的請求則正常響應。
微服務架構下,測試分爲三個層次:
端到端測試:覆蓋整個系統,通常在用戶界面機型測試。
服務測試:針對服務接口進行測試。
單元測試:針對代碼單元進行測試。
三種測試從上到下實施的容易程度遞增,可是測試效果遞減。端到端測試最費時費力,可是經過測試後咱們對系統最有信心。單元測試最容易實施,效率也最高,可是測試後不能保證整個系統沒有問題。
因爲端到端測試實施難度較大,通常只對核心功能作端到端測試。
一旦端到端測試失敗,則須要將其分解到單元測試:則分析失敗緣由,而後編寫單元測試來重現這個問題,這樣將來咱們即可以更快地捕獲一樣的錯誤。服務測試的難度在於服務會常常依賴一些其餘服務。這個問題能夠經過Mock Server解決:
單元測試你們都很熟悉了。咱們通常會編寫大量的單元測試(包括迴歸測試)儘可能覆蓋全部代碼。
指標接口、鏈路跟蹤注入、日誌引流、服務註冊發現、路由規則等組件以及熔斷、限流等功能都須要在應用服務上添加一些對接代碼。若是讓每一個應用服務本身實現是很是耗時耗力的。
基於DRY的原則,小明開發了一套微服務框架,將與各個組件對接的代碼和另一些公共代碼抽離到框架中,全部的應用服務都統一使用這套框架進行開發。
使用微服務框架能夠實現不少自定義的功能。甚至能夠將程序調用堆棧信息注入到鏈路跟蹤,實現代碼級別的鏈路跟蹤。或者輸出線程池、鏈接池的狀態信息,實時監控服務底層狀態。
使用統一的微服務框架有一個比較嚴重的問題:框架更新成本很高。每次框架升級,都須要全部應用服務配合升級。
固然,通常會使用兼容方案,留出一段並行時間等待全部應用服務升級。
可是若是應用服務很是多時,升級時間可能會很是漫長。而且有一些很穩定幾乎不更新的應用服務,其負責人可能會拒絕升級……
所以,使用統一微服務框架須要完善的版本管理方法和開發管理規範。
另外一種抽象公共代碼的方法是直接將這些代碼抽象到一個反向代理組件。每一個服務都額外部署這個代理組件,全部出站入站的流量都經過該組件進行處理和轉發。這個組件被稱爲Sidecar。
Sidecar不會產生額外網絡成本。Sidecar會和微服務節點部署在同一臺主機上而且共用相同的虛擬網卡。因此sidecar和微服務節點的通訊實際上都只是經過內存拷貝實現的。
_圖片來自:_Pattern: Service Mesh
Sidecar只負責網絡通訊。還須要有個組件來統一管理全部sidecar的配置。在Service Mesh中,負責網絡通訊的部分叫數據平面(data plane),負責配置管理的部分叫控制平面(control plane)。數據平面和控制平面構成了Service Mesh的基本架構。
_圖片來自:_Pattern: Service Mesh
Sevice Mesh相比於微服務框架的優勢在於它不侵入代碼,升級和維護更方便。它常常被詬病的則是性能問題。即便迴環網絡不會產生實際的網絡請求,但仍然有內存拷貝的額外成本。另外有一些集中式的流量處理也會影響性能。
微服務不是架構演變的終點。往細走還有Serverless、FaaS等方向。另外一方面也有人在唱合久必分分久必合,從新發現單體架構……
無論怎樣,微服務架構的改造暫時告一段落了。小明知足地摸了摸日益光滑的腦殼,打算這個週末休息一下約小紅喝杯咖啡。
近期熱文推薦:
1.Java 15 正式發佈, 14 個新特性,刷新你的認知!!
2.終於靠開源項目弄到 IntelliJ IDEA 激活碼了,真香!
3.我用 Java 8 寫了一段邏輯,同事直呼看不懂,你試試看。。
以爲不錯,別忘了隨手點贊+轉發哦!