架構設計-微服務架構初步學習
參考:
https://www.cnblogs.com/skabyy/p/11396571.html
https://www.cnblogs.com/xiao2shiqi/p/11298663.html
https://www.jianshu.com/p/7293b148028f
一文詳解微服務架構
本文將介紹微服務架構和相關的元件,介紹他們是什麼以及為什麼要使用微服務架構和這些元件。本文側重於簡明地表達微服務架構的全域性圖景,因此不會涉及具體如何使用元件等細節。
為了防止不提供原網址的轉載,特在這裡加上原文連結:
https://www.cnblogs.com/skabyy/p/11396571.html
要理解微服務,首先要先理解不是微服務的那些。通常跟微服務相對的是單體應用,即將所有功能都打包成在一個獨立單元的應用程式。從單體應用到微服務並不是一蹴而就的,這是一個逐漸演變的過程。本文將以一個網上超市應用為例來說明這一過程。
最初的需求
幾年前,小明和小皮一起創業做網上超市。小明負責程式開發,小皮負責其他事宜。當時網際網路還不發達,網上超市還是藍海。只要功能實現了就能隨便賺錢。所以他們的需求很簡單,只需要一個網站掛在公網,使用者能夠在這個網站上瀏覽商品、購買商品;另外還需一個管理後臺,可以管理商品、使用者、以及訂單資料。
我們整理一下功能清單:
- 網站
- 使用者註冊、登入功能
- 商品展示
- 下單
- 管理後臺
- 使用者管理
- 商品管理
- 訂單管理
由於需求簡單,小明左手右手一個慢動作,網站就做好了。管理後臺出於安全考慮,不和網站做在一起,小明右手左手慢動作重播,管理網站也做好了。總體架構圖如下:
小明揮一揮手,找了家雲服務部署上去,網站就上線了。上線後好評如潮,深受各類肥宅喜愛。小明小皮美滋滋地開始躺著收錢。
隨著業務發展……
好景不長,沒過幾天,各類網上超市緊跟著拔地而起,對小明小皮造成了強烈的衝擊。
在競爭的壓力下,小明小皮決定開展一些營銷手段:
- 開展促銷活動。比如元旦全場打折,春節買二送一,情人節狗糧優惠券等等。
- 拓展渠道,新增移動端營銷。除了網站外,還需要開發移動端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介面將日誌傳送過去。這樣一來又(咦,為啥要用“又”)要修改程式碼……於是小明選用了另一種方案:日誌仍然輸出到檔案,每個服務裡再部署個Agent掃描日誌檔案然後輸出給Logstash。
閘道器 - 許可權控制,服務治理
拆分成微服務後,出現大量的服務,大量的介面,使得整個呼叫關係亂糟糟的。經常在開發過程中,寫著寫著,忽然想不起某個資料應該呼叫哪個服務。或者寫歪了,呼叫了不該呼叫的服務,本來一個只讀的功能結果修改了資料……
為了應對這些情況,微服務的呼叫需要一個把關的東西,也就是閘道器。在呼叫者和被呼叫者中間加一層閘道器,每次呼叫時進行許可權校驗。另外,閘道器也可以作為一個提供服務介面文件的平臺。
使用閘道器有一個問題就是要決定在多大粒度上使用:最粗粒度的方案是整個微服務一個閘道器,微服務外部通過閘道器訪問微服務,微服務內部則直接呼叫;最細粒度則是所有呼叫,不管是微服務內部呼叫或者來自外部的呼叫,都必須通過閘道器。折中的方案是按照業務領域將微服務分成幾個區,區內直接呼叫,區間通過閘道器呼叫。
由於整個網上超市的服務數量還不算特別多,小明採用的最粗粒度的方案:
服務註冊於發現 - 動態擴容
前面的元件,都是旨在降低故障發生的可能性。然而故障總是會發生的,所以另一個需要研究的是如何降低故障產生的影響。
最粗暴的(也是最常用的)故障處理策略就是冗餘。一般來說,一個服務都會部署多個例項,這樣一來能夠分擔壓力提高效能,二來即使一個例項掛了其他例項還能響應。
冗餘的一個問題是使用幾個冗餘?這個問題在時間軸上並沒有一個切確的答案。根據服務功能、時間段的不同,需要不同數量的例項。比如在平日裡,可能4個例項已經夠用;而在促銷活動時,流量大增,可能需要40個例項。因此冗餘數量並不是一個固定的值,而是根據需要實時調整的。
一般來說新增例項的操作為:
- 部署新例項
- 將新例項註冊到負載均衡或DNS上
操作只有兩步,但如果註冊到負載均衡或DNS的操作為人工操作的話,那事情就不簡單了。想想新增40個例項後,要手工輸入40個IP的感覺……
解決這個問題的方案是服務自動註冊與發現。首先,需要部署一個服務發現服務,它提供所有已註冊服務的地址資訊的服務。DNS也算是一種服務發現服務。然後各個應用服務在啟動時自動將自己註冊到服務發現服務上。並且應用服務啟動後會實時(定期)從服務發現服務同步各個應用服務的地址列表到本地。服務發現服務也會定期檢查應用服務的健康狀態,去掉不健康的例項地址。這樣新增例項時只需要部署新例項,例項下線時直接關停服務即可,服務發現會自動檢查服務例項的增減。
服務發現還會跟客戶端負載均衡配合使用。由於應用服務已經同步服務地址列表在本地了,所以訪問微服務時,可以自己決定負載策略。甚至可以在服務註冊時加入一些元資料(服務版本等資訊),客戶端負載則根據這些元資料進行流量控制,實現A/B測試、藍綠髮布等功能。
服務發現有很多元件可以選擇,比如說Zookeeper 、Eureka、Consul、Etcd等。不過小明覺得自己水平不錯,想炫技,於是基於Redis自己寫了一個……
熔斷、服務降級、限流
熔斷
當一個服務因為各種原因停止響應時,呼叫方通常會等待一段時間,然後超時或者收到錯誤返回。如果呼叫鏈路比較長,可能會導致請求堆積,整條鏈路佔用大量資源一直在等待下游響應。所以當多次訪問一個服務失敗時,應熔斷,標記該服務已停止工作,直接返回錯誤。直至該服務恢復正常後再重新建立連線。
圖片來自《微服務設計》
服務降級
當下遊服務停止工作後,如果該服務並非核心業務,則上游服務應該降級,以保證核心業務不中斷。比如網上超市下單介面有一個推薦商品湊單的功能,當推薦模組掛了後,下單功能不能一起掛掉,只需要暫時關閉推薦功能即可。
限流
一個服務掛掉後,上游服務或者使用者一般會習慣性地重試訪問。這導致一旦服務恢復正常,很可能因為瞬間網路流量過大又立刻掛掉,在棺材裡重複著仰臥起坐。因此服務需要能夠自我保護——限流。限流策略有很多,最簡單的比如當單位時間內請求數過多時,丟棄多餘的請求。另外,也可以考慮分割槽限流。僅拒絕來自產生大量請求的服務的請求。例如商品服務和訂單服務都需要訪問促銷服務,商品服務由於程式碼問題發起了大量請求,促銷服務則只限制來自商品服務的請求,來自訂單服務的請求則正常響應。
測試
微服務架構下,測試分為三個層次:
- 端到端測試:覆蓋整個系統,一般在使用者介面機型測試。
- 服務測試:針對服務介面進行測試。
- 單元測試:針對程式碼單元進行測試。
三種測試從上到下實施的容易程度遞增,但是測試效果遞減。端到端測試最費時費力,但是通過測試後我們對系統最有信心。單元測試最容易實施,效率也最高,但是測試後不能保證整個系統沒有問題。
由於端到端測試實施難度較大,一般只對核心功能做端到端測試。一旦端到端測試失敗,則需要將其分解到單元測試:則分析失敗原因,然後編寫單元測試來重現這個問題,這樣未來我們便可以更快地捕獲同樣的錯誤。
服務測試的難度在於服務會經常依賴一些其他服務。這個問題可以通過Mock Server解決:
單元測試大家都很熟悉了。我們一般會編寫大量的單元測試(包括迴歸測試)儘量覆蓋所有程式碼。
微服務框架
指標介面、鏈路跟蹤注入、日誌引流、服務註冊發現、路由規則等元件以及熔斷、限流等功能都需要在應用服務上新增一些對接程式碼。如果讓每個應用服務自己實現是非常耗時耗力的。基於DRY的原則,小明開發了一套微服務框架,將與各個元件對接的程式碼和另外一些公共程式碼抽離到框架中,所有的應用服務都統一使用這套框架進行開發。
使用微服務框架可以實現很多自定義的功能。甚至可以將程式呼叫堆疊資訊注入到鏈路跟蹤,實現程式碼級別的鏈路跟蹤。或者輸出執行緒池、連線池的狀態資訊,實時監控服務底層狀態。
使用統一的微服務框架有一個比較嚴重的問題:框架更新成本很高。每次框架升級,都需要所有應用服務配合升級。當然,一般會使用相容方案,留出一段並行時間等待所有應用服務升級。但是如果應用服務非常多時,升級時間可能會非常漫長。並且有一些很穩定幾乎不更新的應用服務,其負責人可能會拒絕升級……因此,使用統一微服務框架需要完善的版本管理方法和開發管理規範。
另一條路 - Service Mesh
另一種抽象公共程式碼的方法是直接將這些程式碼抽象到一個反向代理元件。每個服務都額外部署這個代理元件,所有出站入站的流量都通過該元件進行處理和轉發。這個元件被稱為Sidecar。
Sidecar不會產生額外網路成本。Sidecar會和微服務節點部署在同一臺主機上並且共用相同的虛擬網絡卡。所以sidecar和微服務節點的通訊實際上都只是通過記憶體拷貝實現的。
Sidecar只負責網路通訊。還需要有個元件來統一管理所有sidecar的配置。在Service Mesh中,負責網路通訊的部分叫資料平面(data plane),負責配置管理的部分叫控制平面(control plane)。資料平面和控制平面構成了Service Mesh的基本架構。
Sevice Mesh相比於微服務框架的優點在於它不侵入程式碼,升級和維護更方便。它經常被詬病的則是效能問題。即使迴環網路不會產生實際的網路請求,但仍然有記憶體拷貝的額外成本。另外有一些集中式的流量處理也會影響效能。
結束、也是開始
微服務不是架構演變的終點。往細走還有Serverless、FaaS等方向。另一方面也有人在唱合久必分分久必合,重新發現單體架構……
什麼是微服務?為什麼你要用微服務?
前言
最近幾年微服務很火,大家都在建設微服務,彷彿不談點微服務相關的技術,都顯得不是那麼主流了。
近幾年見識到身邊朋友的很多公司和團隊都在嘗試進行微服務的改變,但很多團隊並沒有實際微服務踩坑經驗,很多團隊甚至強行為了微服務而去微服務,最終寫成一個大型的分散式單體應用,就是改造後的系統既沒有微服務的快速擴容,靈活釋出的特性,也讓原本的單體應用失去了方便開發,部署容易的特性(專案拆為多份,開發部署複雜度都提高了),不得不說是得不償失。
作者親身經歷和參與幾個大型專案微服務的改造和建設。所以想作為實踐者跟大家分享關於微服務的實際經驗,幫助大家瞭解微服務的優缺點,從而可以結合自身業務做出更加合適的選擇,作為本篇文章的三個主題,例如:
- 什麼是微服務?為什麼要用微服務?
- 微服務解決什麼問題,又引入了什麼問題?
- 使用微服務應該要遵循哪些原則?什麼樣的情況你不應該使用微服務?
(PS:因為市面上太多對如果使用微服務框架工具的教程,所以本篇只是一篇關於微服務的總體概述性文章,不涉及各種微服務框架的安裝和使用教程,我們只談論微服務本身的設計模式的優缺點和適合應用的場景)
一:什麼是微服務?為什麼要用微服務?
什麼是微服務?(熟悉的同學可以直接跳過)
簡單舉例:看軍事新聞的同學應該都知道,一艘航空母艦作戰能力雖然很強,但是弱點太明顯,就是防禦能力太差,單艘的航空母艦很少單獨行動,通常航空母艦戰鬥群才是主要軍事力量,你可以把單艘航母理解為的單體應用(防禦差,機動性不好),把航母戰鬥群(排程複雜,維護費用高)理解為微服務。
大部分的開發者經歷和開發過單體應用,無論是傳統的 Servlet + JSP,還是 SSM,還是現在的 SpringBoot,它們都是單體應用,那麼長期陪伴我們的單體應用有什麼弊端?我們是面臨了什麼問題,導致我們要拋棄單體應用轉向微服務架構?個人總結主要問題如下:
- 部署成本高(無論是修改1行程式碼,還是10行程式碼,都要全量替換)
- 改動影響大,風險高(不論程式碼改動多小,成本都相同)
- 因為成本高,風險高,所以導致部署頻率低(無法快速交付客戶需求)
當然還有例如無法滿足快速擴容,彈性伸縮,無法適應雲環境特性等問題,但我們不一一詳談了,以上的問題,都是微服務架構要解決的問題,至於具體是怎麼解決的,我們先放到後面再聊
二:微服務解決什麼問題,又引入了什麼問題?
我們先看看微服務能帶給我們什麼?微服務架構的特點:
- 針對特定服務釋出,影響小,風險小,成本低
- 頻繁釋出版本,快速交付需求
- 低成本擴容,彈性伸縮,適應雲環境
我們知道一個樸素的理念,沒有任何事物是完美的,任何東西都有兩面性,有得必有失,那麼在選擇微服務在解決了快速響應和彈性伸縮的問題同時,它又給我們帶來了什麼問題?個人總結如下:
- 分散式系統的複雜性
- 部署,測試和監控的成本問題
- 分散式事務和CAP的相關問題
系統應用由原來的單體變成幾十到幾百個不同的工程,會所產生例如包括服務間的依賴,服務如何拆封,內部介面規範,資料傳遞等等問題,尤其是服務拆分,需要團隊熟悉業務流程,懂得取捨,要保證拆分的粒度服務既符合“高內聚,低耦合”的基本原則,還要兼顧業務的發展以及公司的願景,要還要說服團隊成員為之努力,並且積極投入,在多方中間取得平衡。
對於分散式系統,部署,測試和監控都需要大量的中介軟體來支撐,而且中介軟體本身也要維護,原先單體應用很簡單的事務問題 ,轉到分散式環境就變得很複雜,分散式事務是採用簡單的重試+補償機制,還是採用二階段提交協議等強一致性方法來解決,就要取決對業務場景的熟悉加上反覆的權衡了,相同問題還包括對 CAP 模型的權衡,總之微服務對團隊整體的技術棧水平整體要求更高
三:使用微服務應該遵循哪些原則?
古人云:兵馬未動,糧草先行。建設微服務是需要建立長遠規劃,不是像寫CMS那樣建好資料庫表,然後就開始幹活,這樣十有八九是會失敗的。我們要進行微服務改造前,架構師要提前做好規劃,我們把這裡分為三步,前期階段,設計階段,技術階段
前期階段,大致要做好如下事情:
- 和多方充分溝通,確保能符合客戶和組織的需求,並且得到認同
- 和團隊溝通,讓隊友(開發/測試/運維)理解,並且積極投入
- 和業務部門溝通,指定版本計劃和上線時間
設計階段,參考Sam Newman 的著作《微服務設計》,單微服務必須要滿足以下的條件,才符合微服務的基本要求:
- 標準的 REST 風格介面(基於 HTTP 和 JSON 格式)
- 獨立部署,避免共享資料庫(避免因為資料庫而影響整個分散式系統)
- 業務上的高內聚,減少依賴(從設計上要避免服務過大或者太小)
龐大的分散式系統,需要強大基礎設施來支撐,微服務涉及哪些基礎設施?
- CI/CD和自動化(分散式系統幾乎不可能通過人工手動釋出)
- 虛擬化技術(要保證微服務執行環境隔離,目前行業主流的是使用 Docker 容器)
- 日誌聚合,全鏈路監控(高度可觀察和分析診斷問題)
說了那麼多,那什麼樣的情況下,你的團隊不適合建設微服務?(請勿對號入座)
總結
微服務設計其實是很早就有的設計思想,因為隨著虛擬化技術的崛起,微服務可以低成本的實現,所以也開始流行和興起。
微服務的內涵很深,其中就包括,自動化,去中心化,獨立性等等,其中細節無法用一篇文章概述清楚,我們在做技術選型或者方案的時候,儘可能多去了解技術的本身和起源再結合我們業務的特點,進行更好的選擇。
微服務入門這一篇就夠了
開篇
剛開始進入軟體行業時還是單體應用的時代,前後端分離的概念都還沒普及,開發的時候需要花大量的時間在“強大”的JSP上面,那時候SOA已經算是新技術了。現在,微服務已經大行其道,有哪個網際網路產品不說自己是微服務架構呢?
但是,對於微服務的理解每個人都不太一樣,這篇文章主要是聊一聊我對微服務的理解以及如何搭建經典的微服務架構,目的是梳理一下自己的一些想法,如果存在不同看法的歡迎指正!
什麼是微服務
首先,什麼是微服務呢?
單體應用
相對的,要理解什麼是微服務,那麼可以先理解什麼是單體應用,在沒有提出微服務的概念的“遠古”年代,一個軟體應用,往往會將應用所有功能都開發和打包在一起,那時候的一個B/S應用架構往往是這樣的:
B/S
但是,當用戶訪問量變大導致一臺伺服器無法支撐時怎麼辦呢?加伺服器加負載均衡,架構就變成這樣了:
B/S+負載均衡
後面發現把靜態檔案獨立出來,通過CDN等手段進行加速,可以提升應用的整體相應,單體應用的架構就變成:
B/S+前後端分離
上面3中架構都還是單體應用,只是在部署方面進行了優化,所以避免不了單體應用的根本的缺點:
- 程式碼臃腫,應用啟動時間長;(程式碼超過1G的專案都有!)
- 迴歸測試周期長,修復一個小小bug可能都需要對所有關鍵業務進行迴歸測試。
- 應用容錯性差,某個小小功能的程式錯誤可能導致整個系統宕機;
- 伸縮困難,單體應用擴充套件效能時只能整個應用進行擴充套件,造成計算資源浪費。
- 開發協作困難,一個大型應用系統,可能幾十個甚至上百個開發人員,大家都在維護一套程式碼的話,程式碼merge複雜度急劇增加。
微服務
我認為任何技術的演進都是有跡可循的,任何新技術的出現都是為了解決原有技術無法解決的需求,所以,微服務的出現就是因為原來單體應用架構已經無法滿足當前網際網路產品的技術需求。
在微服務架構之前還有一個概念:SOA(Service-Oriented Architecture)-面向服務的體系架構。我認為的SOA只是一個架構模型的方法論,並不是一個明確而嚴謹的架構標準,只是後面很多人將SOA與The Open Group的SOA參考模型等同了,認為嚴格按照TOG-SOA標準的才算真正的SOA架構。SOA就已經提出的面向服務的架構思想,所以微服務應該算是SOA的一種演進吧。
撇開架構先不說,什麼樣的服務才算微服務呢?
- 單一職責的。一個微服務應該都是單一職責的,這才是“微”的體現,一個微服務解決一個業務問題(注意是一個業務問題而不是一個介面)。
- 面向服務的。將自己的業務能力封裝並對外提供服務,這是繼承SOA的核心思想,一個微服務本身也可能使用到其它微服務的能力。
我覺得滿足以上兩點就可以認為典型的微服務。
微服務典型架構
微服務架構,核心是為了解決應用微服務化之後的服務治理問題。
應用微服務化之後,首先遇到的第一個問題就是服務發現問題,一個微服務如何發現其他微服務呢?最簡單的方式就是每個微服務裡面配置其他微服務的地址,但是當微服務數量眾多的時候,這樣做明顯不現實。所以需要使用到微服務架構中的一個最重要的元件:服務註冊中心,所有服務都註冊到服務註冊中心,同時也可以從服務註冊中心獲取當前可用的服務清單:
服務註冊中心解決服務發現問題後,接著需要解決微服務分散式部署帶來的第二個問題:服務配置管理的問題。當服務數量超過一定程度之後,如果需要在每個服務裡面分別維護每一個服務的配置檔案,運維人員估計要哭了。那麼,就需要用到微服務架構裡面第二個重要的元件:配置中心,微服務架構就變成下面這樣了:
配置中心以上應用內部的服務治理,當客戶端或外部應用呼叫服務的時候怎麼處理呢?服務A可能有多個節點,服務A、服務B和服務C的服務地址都不同,服務授權驗證在哪裡做?這時,就需要使用到服務閘道器提供統一的服務入口,最終形成典型微服務架構:
典型微服務架構
上面是一個典型的微服務架構,當然微服務的服務治理還涉及很多內容,比如:
- 通過熔斷、限流等機制保證高可用;
- 微服務之間呼叫的負載均衡;
- 分散式事務(2PC、3PC、TCC、LCN等);
- 服務呼叫鏈跟蹤等等。
微服務框架
目前國內企業使用的微服務框架主要是Spring Cloud和Dubbo(或者DubboX),但是Dubbo那兩年的停更嚴重打擊了開發人員對它的信心,Spring Cloud已經逐漸成為主流,比較兩個框架的優劣勢的文章在網上有很多,這裡就不重複了,選擇什麼框架還是按業務需求來吧,業務框架決定技術框架。
Spring Cloud全家桶提供了各種各樣的元件,基本可以覆蓋微服務的服務治理的方方面面,以下列出了Spring Cloud一些常用元件:
Spring Cloud常用元件
搭建典型微服務架構
本章節主要介紹如何基於Spring Cloud相關元件搭建一個典型的微服務架構。
首先,建立一個Maven父專案spring-cloud-examples
,用於管理專案依賴包版本。由於Spring Cloud元件很多,為保證不同元件之間的相容性,一般通過spring-cloud-dependencies
統一管理Spring Cloud元件版本,而非每個元件單獨引入。
pom.xml配置如下:
<!-- 繼承SpringBoot父專案,注意與SpringCloud版本的匹配 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<properties>
<spring.boot.version>2.1.4.RELEASE</spring.boot.version>
<spring.cloud.version>Greenwich.SR1</spring.cloud.version>
<lombok.version>1.18.8</lombok.version>
<maven.compiler.plugin.version>3.8.1</maven.compiler.plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
搭建服務配置中心
- 在
spring-cloud-examples
專案下建立一個子專案spring-cloud-example-config
,新增Spring Cloud Config Server端的相關依賴包:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
- 新增Spring Boot配置檔案
application.yml
,配置如下:
spring:
application:
name: spring-cloud-example-config
profiles:
active: native #啟用本地配置檔案
cloud:
config:
server:
native:
search-locations: classpath:/configs/ #配置檔案掃描目錄
server:
port: 8000 #服務埠
- 啟動類添加註解
@EnableConfigServer
通過啟用Config Server服務。
@SpringBootApplication
@EnableConfigServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
搭建服務註冊中心
- 在
spring-cloud-examples
專案下建立一個子專案spring-cloud-example-registry
,在pom.xml
中新增Eureka Server相關依賴包:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
- 在
spring-cloud-example-config
配置中心專案的src/main/resource/configs
目錄下新增一個服務配置檔案spring-cloud-example-registry.yml
,配置如下:
spring:
application:
name: spring-cloud-example-registry
# Eureka相關配置
eureka:
client:
register-with-eureka: false #不註冊服務
fetch-registry: false #不拉去服務清單
serviceUrl:
defaultZone: http://localhost:${server.port}/eureka/ #多個通過英文逗號分隔
server:
port: 8001
- 在
spring-cloud-example-registry
專案的src/main/resource/
目錄新增bootstrap.yml
配置檔案,配置如下:
spring:
cloud:
config:
name: spring-cloud-example-registry #配置檔名稱,多個通過逗號分隔
uri: http://localhost:8000 #Config Server服務地址
- 啟動類添加註解
@EnableEurekaServer
通過啟用Eureka Server服務。
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
搭建業務服務A
- 在
spring-cloud-examples
專案下建立一個業務服務A的子專案spring-cloud-example-biz-a
,在pom.xml
中新增以下依賴包:
<dependencies>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Eureka Client Starter -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Config Client Starter -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
- 在
spring-cloud-example-config
配置中心專案的src/main/resource/configs
目錄下新增一個服務配置檔案spring-cloud-example-biz-a.yml
,配置如下:
spring:
application:
name: spring-cloud-example-biz-a
server:
port: 8010
# Eureka相關配置
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8001/eureka/
instance:
lease-renewal-interval-in-seconds: 10 # 心跳時間,即服務續約間隔時間(預設為30s)
lease-expiration-duration-in-seconds: 60 # 發呆時間,即服務續約到期時間(預設為90s)
prefer-ip-address: true
instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
- 在
spring-cloud-example-biz-a
專案的src/main/resource/
目錄新增bootstrap.yml
配置檔案,配置如下:
spring:
cloud:
config:
name: spring-cloud-example-biz-a #配置檔名稱,多個通過逗號分隔
uri: http://localhost:8000 #Config Server服務地址
- 新增一個示例介面,程式碼參考:
@RestController
@RequestMapping("/hello")
public class HelloController {
/**
* 示例方法
*
* @return
*/
@GetMapping
public String sayHello() {
return "Hello,This is Biz-A Service.";
}
}
搭建業務服務B
參考上面業務服務A搭建另外一個業務服務B。
搭建服務閘道器
- 在
spring-cloud-examples
專案下建立一個業務服務A的子專案spring-cloud-example-gateway
,在pom.xml
中新增以下依賴包:
<dependencies>
<!-- zuul -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!-- Eureka Client Starter -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Config Client Starter -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
- 在
spring-cloud-example-config
配置中心專案的src/main/resource/configs
目錄下新增一個服務配置檔案spring-cloud-example-gateway.yml
,配置如下:
spring:
application:
name: spring-cloud-example-gateway
server:
port: 8002
# Eureka相關配置
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8001/eureka/
instance:
lease-renewal-interval-in-seconds: 10 # 心跳時間,即服務續約間隔時間(預設為30s)
lease-expiration-duration-in-seconds: 60 # 發呆時間,即服務續約到期時間(預設為90s)
prefer-ip-address: true
instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
- 在
spring-cloud-example-gateway
專案的src/main/resource/
目錄新增bootstrap.yml
配置檔案,配置如下:
spring:
cloud:
config:
name: spring-cloud-example-gateway #配置檔名稱,多個通過逗號分隔
uri: http://localhost:8000 #Config Server服務地址
- 啟動類添加註解
@EnableZuulProxy
通過啟用閘道器代理服務。
@SpringBootApplication
@EnableZuulProxy
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
啟動示例
-
啟動順序
spring-cloud-example-config
>>spring-cloud-example-eureka
>>spring-cloud-example-biz-a
/spring-cloud-example-biz-b
/spring-cloud-example-gateway
-
通過閘道器訪問服務A介面
服務A呼叫 -
通過閘道器訪問服務B介面
服務B呼叫
服務之間呼叫
- 在業務服務A中新增一個Feign Client Bean,參考程式碼如下:
@FeignClient(name = "spring-cloud-example-biz-b") # 指定服務名稱
public interface RemoteService {
/**
* 呼叫服務B的hello方法
*
* @return
*/
@GetMapping("/hello") #指定請求地址
String sayHello();
}
- 業務服務A示例介面類增加
call2b
介面,程式碼如下:
@RestController
@RequestMapping("/hello")
public class HelloController {
@Autowired
private RemoteService remoteService;
/**
* 示例方法
*
* @return
*/
@GetMapping
public String sayHello() {
return "Hello,This is Biz-A Service.";
}
/**
* 示例方法:呼叫服務B
*
* @return
*/
@GetMapping(path = "/call2b")
public String sayHello2B() {
return remoteService.sayHello();
}
}
- 重啟業務服務A,通過呼叫
/hello/call2b
介面:
服務之間呼叫
示例程式碼
下一代微服務
目前網上很多說是下一代微服務架構就是Service Mesh,Service Mesh主流框架有Linkerd和Istio,其中Istio有大廠加持所以呼聲更高。Service Mesh我接觸還不多,但是個人感覺並不一定能稱為下一代微服務架構,可能認為是服務治理的另外一種解決方案更合適,是否能夠取代當前的微服務架構還需要持續觀察。
作者:centychen
連結:https://www.jianshu.com/p/7293b148028f
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。