當前位置:
首頁 > 最新 > 談談我對微服務的理解

談談我對微服務的理解

一、概述

微服務是個說的挺長時間的概念,也是比較成熟的技術體系。像Spring Cloud,甚至提供了微服務所需要的全套框架,包括註冊中心(Eureka)、配置中心(Config)、斷路器(Hytrix)、API網關(Zuul)等組件。在這篇文章里,我從個人的經驗和實踐出發,談談自己對微服務及其內涵的理解。

二、微服務與SOA/ESB的異同

微服務與更早就起來的SOA是什麼關係? 個人覺得如果從概念上來說,微服務和SOA都是一回事,強調把整個系統,按照多個服務的方式去組合及通信,而不是揉合在一起,但它們的內涵有很大的區別。

SOA誕生在早期企業級的應用,其業務複雜、技術體系多樣,SOA強調的是各個服務之間,尤其是異構系統、遺留系統之間,建立起一套統一的協議和通信(SOAP),以及定址服務(UDDI),它的側重點在集成和兼容;與SOA同期的另一種概念ESB(企業匯流排),強調通過一根匯流排服務,把所有服務串聯起來,由ESB匯流排來屏蔽各種不同業務系統自身業務/語言/協議的特殊性,各服務以一種統一的方式,與匯流排相連,從而降低接入成本。

這兩種概念,我感覺在國內沒有太發展起來。一是國內的軟體起步相對較晚,系統的整體複雜度——多廠商、多語言/技術棧、歷史遺留系統的問題,還不算突出。而對於公司內部的產品系,又沒有必要使用SOA、UDDI來做複雜的集成。隨著互聯網的興起和用戶量的迅速爆發,企業自身的產品的微服務化的需求,快速發展起來,而與此同時SOA這種以XML為基礎的SOAP協議、以定址為主要作用的UDDI,不能使用互聯網產品的發展——SOAP的XML協議內容太多,造成性能明顯下降;HTTP協議的效率不如RPC;UDDI只有定址,缺少服務治理等功能。

在此種大背景下,以服務切分+服務註冊+服務治理+限流降級+RPC+監控等為主要內涵的微服務,就快速發展起來的。國內的阿里巴巴走在前列,以Dubbo為代表在國內互聯網企業中得到廣泛應用;隨後Spring官方發布Spring Cloud,揉合了一系列自研或其他企業捐贈的開源項目,發布微服務領域的Spring Cloud產品。各自都有各自的優勢和劣勢,而隨著這些年來,微服務的繼續下沉(sidecar和service mesh)到基礎設施層,給微服務的治理帶來了新的方向。

三、微服務的關鍵特性

粒度

服務的粒度,切分到多大算合適? 太粗的話,這服務就涵蓋過多的業務邏輯,從而難維護、易出錯;太細了,就會搞出很多的工程,造成很大的工程維護和通信成本。

有一種說法是跟組織機構匹配(康威定律:團隊的交流機制應該和組織機構相匹配)——如果某個應用,需要多個組織之間一起修改,那麼這個應用很可能就太粗了,需要拆分;如果一個小組的項目數量太多以致維護困難,那可能就需要適當合併。

這裡其實是有一個容易被忽視的地方——是先有架構再有團隊,還是先有團隊再有架構。如果是前者,那系統架構怎麼定就是首要問題;而如果是後者,則團隊怎麼定就是首要問題。無論是團隊怎麼定、還是架構怎麼定,這都是跟著業務的發展而發展的,可以說都是業務的衍生髮展而來。所以切分服務,首要做的還是業務——業務切分決定了服務切分、業務切分也決定了團隊組織。

業務切分有兩種簡單辦法:

參照業內同類公司的劃分:比如電商,業內比較成熟的:支付、庫存、訂單、搜索、用戶等;

將自身業務的主要信息流畫出來,先找出其中的名詞或動名詞,它就可能是個服務

eg:在我們的線上貸款業務中,典型的user case是這樣:

用戶導入幾項金融資料數據

系統根據信息清洗出部分衍生變數

系統跑欺詐規則

系統計算授信,給出額度

用戶試算得到月費率和利息

系統人工信審

系統放款

到還款日時用戶還款或者我們系統主動扣款

將其中的名詞整理出來,整理流程大概就是如下圖

這些都是候選服務。根據其複雜度和相關性,做適當的拆解和合併,形成了如下幾個子系統及服務

治理範圍

從服務的角度來看,對外公開的是契約——即我們系統提供哪些特性,而內部演算法/數據都應該隱藏起來,這在「是否可以訪問資料庫」這點上體現地很明顯,也是在工作實踐中的最大分歧。

從隱藏細節來說,資料庫應該是封閉在內部的,但在工作實踐中,尤其是創業初期,所有的資料庫表可能都是在一個庫里,應用可能會連任何一個表,這就造成了後期服務化的難度。更困難的是,同一張表的欄位,可能會屬於各個不同的應用。看下面這個User表

開始的時候,User表只包含了完全業務無關的屬性,但隨著系統的發展,一些和業務相關的欄位(上圖紅色部分)逐漸地被加進來——這也不完全是決策時犯的錯誤,而是本身這屬性是否和業務有關,也不是很容易界定。所以逐漸會發現,很多系統都會依賴這張表,從而交織難以拆分。但隨著規模的發展,對數據的隱藏是必然要做的事,否則後患會越來越大。在服務化的過程中,就應該順帶把「數據的隱藏」也一塊做掉。

在我們的實踐中,服務化的過程、包括數據自治,大約是這樣的步驟(以「用戶中心」應用為例):

創建新應用UserCenter,梳理清楚其的業務邊界和所涉及的數據表;

收集和分析其他系統對這些數據表的需求,並在UserCenter中開發介面,以備上游系統調用;

逐漸改造上游系統,使其由原先的讀取資料庫,修改為調用UserCenter介面。由於有多個上游系統和功能需要改在,因此這個階段會比較長,上游系統在這個時間周期內,也會「訪問介面服務」和「直接訪問資料庫」這兩種形態並存;

檢查並確認上游系統都改造完畢上線,此時理論上應該沒有上游系統直接讀取UserCenter的表了,都通過介面了,此時準備遷移UserCenter的表數據;

建立New UserCenter DB,並通過DB同步機制,實時地將UserCenter的表數據由老庫同步到新庫。在新庫同步完成之前,UserCenter的應用仍然使用的是老庫里的表;

新庫同步完畢,UserCenter應用切換到新庫,此時所有的新數據都會進新庫,而老庫理論上是不用了;

斷開新老庫的同步鏈,同時rename老庫的表(先不刪,同時在rename前一定要斷開同步鏈,否則新庫也會被同步rename掉了)。如果此時萬一有某個系統的功能,在之前的系統改造/測試中遺漏了沒被發現,仍然是直接讀取的資料庫表,那麼這時候就會報錯(因為表名被rename掉了,找不到了)。此時就是個恢復窗口,趕緊把表rename回來,減少損失,然後再繼續處理。這也是前面千萬不能直接把老表刪除的原因;

運行幾天沒問題之後,再把老庫的表刪除,整個服務化過程結束;

服務組合

在微服務之後,各個系統只對某一塊業務負責,那麼就有可能需要對服務做一些聚合。下面是常見的兩種模式:

這是聚合服務的模式,由web應用去負責聚合後端服務或做個性化處理,這是它的好處——可以根據自身的業務做任何組合和處理,而它的壞處也很明顯——對於不需要特殊處理的,也得過它一道。

這是後台服務自包含的模式。某個後台應用,依賴於其他服務,於是就將其他服務的相關調用都處理完了,或者這麼理解——後台服務也有多個層次:庫存服務、支付服務、發票服務是最底層的,交易服務是更上層一些的共享服務,從而達到封裝細粒度服務的目的,與此同時,它的個性化也就喪失了。假如有個交易,是不需要發票服務的,那麼這種模式就不是太靈活。

從我個人的經驗來看,我是傾向於聚合服務這種模式。每個前端應用,還都是應該有個自己的後台服務,去完成很多小的功能、以及聚合。而對於不需要App-Server處理、直接使用後台服務的,應該能夠通過gateway直接調用,而不需要App-Server來做代理轉發。

容錯

容錯的目的就是在出現問題的時候,仍然能夠正常提供服務,其具體表現形式有這麼幾種:

當調用下游服務B出錯的時候,可以在安全的情況下考慮重試;

當調用下游服務B出錯的時候,可以調用替代服務B";

當調用下游服務B出錯的時候,是否可以返回某個默認值、或者返回最近一次的值?

當調用下游服務B超時的時候,如果超時請求達到一定數量,則需要熔斷,以保證自身其他服務能正常提供服務,而不會被拖垮;

當調用下游服務B出錯的時候,能否以非同步+定時任務補償的方式代替?

上面這些特性,有些是通過RPC框架來實現(重試)、有些是應用控制(調用替代服務、非同步+定時補償)、有些可以通過Hytrix這樣的斷路保護框架來實現。

限流

限流主要有兩種演算法:令牌桶演算法和漏桶演算法。

對於很多應用場景來說,除了要求能夠限制數據的平均傳輸速率外,還要求允許某種程度的突發傳輸。這時候漏桶演算法可能就不合適了,令牌桶演算法更為適合。如下圖所示,令牌桶演算法的原理是系統會以一個恆定的速度往桶里放入令牌,而如果請求需要被處理,則需要先從桶里獲取一個令牌,當桶里沒有令牌可取時,則拒絕服務。

從互聯網實踐的角度看,我覺得這兩種方法都不是很理想。主要原因是看我們怎麼理解流量控制這個事情。在互聯網領域,系統的最大處理能力,是一個比較核心的指標。假設系統(或某介面服務)只能同時支撐10000個請求同時處理(這也是非常容易模擬測試的),那麼它所關心的,就是在任何一個時間點的執行中任務,是否超過了10000,而這是令牌演算法和漏桶演算法都提供不了的。

令牌演算法:單位時間內生產的令牌是固定的,而令牌桶就相當於一個蓄水池。如果令牌不被馬上用完,令牌桶可以存儲一部分。它是從請求數(開始處理的)的角度,隨著蓄水池中的令牌多少,而相應請求多或者少。它不能衡量當前有多少請求正在進行中。且蓄水池的大小,並不是它自己可以說的算,雖然它有令牌,也還需要系統能處理才可以。

漏桶演算法:單位時間內可以處理的請求是固定的,持續恆定,沒有令牌桶來做蓄水池。它同樣是從請求的角度出來,無法衡量「當前的並行處理任務」。

所以這兩種演算法,我認為它都不是從精確的系統承載量角度出發,更像是一些預估或外界因素所引發的流控——比如該系統1分鐘只允許處理100個請求,以一種比較粗略的方式、來保護系統不過載;該系統依賴的第三方不能超過每天XX次的請求量;

從請求出發的角度,還有令牌演算法的優化版——滑窗模式。以X個滑窗作為一個周期,比如1秒作為1個窗口,3個窗口作為一個周期,在這個周期內令牌蓄水池。3秒到了則在排隊等待令牌的請求都置拒絕。這樣防止在流量阻塞的時候,隨著時間推移,很多用戶已經等不及離開了,而他們的請求還在這裡排隊,導致最新用戶的請求無法獲得令牌。

而如果從系統承載力的角度,既能最大發揮系統能力,又不會過載,個人認為最好的方法「響應模式——在訪問開始前,計數器+1;訪問結束,計數器-1;保證計數器不超過閥值,也就是當前系統正在處理的任務,不超過閥值」。

從另一個角度看,令牌演算法和漏桶演算法被很多框架完好支持,比如Nginx,這樣對於大部分介面,尤其是處於安全形度考慮的限流,就是個很好的策略。在Nginx中配下就可以了,不需要業務去衡量自身負載、再開發相應代碼。所以這也是種取捨——如果要快速覆蓋、尤其在產品初期,盡量地保護自己的系統,尤其是安全原因,那麼令牌或漏桶演算法是很好的選擇;如果針對一些核心介面,希望能在保護自己系統的同時、盡量多地發揮系統潛力,那麼就開發「響應模式」是更好的選擇。

一致性

CAP(Consistence、Avaliable、Partition)理論是很熟悉的理論——在同一時間CAP不能全部滿足、只能滿足其中兩個。而由於分散式系統的特點,Partition是必須要滿足的,所以只能要麼CP、要麼AP,即要麼系統可用,但數據可能不一致;要麼數據一致,但系統不可用。

這裡對「為什麼Partition必須要滿足」解釋一下。CAP主要是針對有狀態、即有數據的,典型形態就是存儲類產品。因為數據才有一致性、分區同步這樣的場景。在存儲類產品中,為了避免單點故障,都是需要主從結構、或者集群結構,這就勢必有相互之間的數據複製——主從的話,是主向從複製;集群的話,是多副本複製。這就必然涉及到網路通信,而網路我們說不是非常穩定的,「滿足Partition」就是在網路不穩定的時候,比如主和從網路短時不通了,或者從機crash/重啟了,這時候產品還能夠正常提供服務。這就是「Partition必須要滿足」的原因,否則就有較大的單點風險。

既然P必須要滿足,則只能選AP或者CP了。就互聯網企業來說,保證服務可用性更為重要,所以AP往往是主流選擇。在我的經驗中,金融財務相關領域可能會用到AP這樣的強一致,這往往是通過有ACID特性的RDMS(Oracle、MySQL)來實現的。

前面我們提到,分散式的服務治理,數據被隱藏到服務內部了,那麼對數據的修改就由原先的直接操作變成了介面調用,原先可能可以通過Transaction來實現多表更新的ACID,現在實現不了了,那在保證AP的同時,Consistence怎麼辦呢? 此時BASE理論也就應運而生。

BASE(Base Available、Soft State、Eventually Consistence)——基本可用、暫時不一致、最終一致。短時間內數據不一致,可能會造成一定的臟讀,但最終會達成一致,而達成一致的速度窗口,也就是個比較重要的指標。Paxos和Raft演算法是兩個主流的最終一致性的演算法。從BASE的定義來看,對於準確性高度敏感的金融財務領域,可能就不合適。

Paxos和Raft演算法,這一般是存儲產品自身如何去協調各個節點的狀態和版本,以完成同步。而在微服務領域中,我們面對的是各個RPC或http通信的不同類的應用服務,那麼它們怎麼做到最終一致? 主要策略有兩個:撤銷、補償。前者是努力恢復到操作前的一致狀態,後者是努力保證成功、達到操作後的一致狀態。看下圖,Server的某個業務操作中,要分別調用Service-X、Service-Y、Service-Z三個服務,才能完成。此時如果調用Service-Z的過程中出現錯誤了,怎麼保證最終一致性?

按照上面的撤銷或者補償,就有兩個策略:

撤銷:Service-X和Service-Y提供反向的撤銷介面。如果調用Service-Z失敗,則調用Service-X和Service-Y的反向撤銷介面,以恢復到操作前的狀態。如果撤銷的過程中失敗? 呵呵,那又要補償了。

補償:Service-X和Service-Y都執行成功了,那麼Service-Z調用失敗,在「確保Service-Z只要恢復正常、必然能執行成功的前提下(無論是系統自動還是人工)」,通過定時任務重試或者MQ的機制,補償重試,再不行人工處理,直到Service-Z成功,以達到都成功的狀態。這裡「確保Service-Z必然能執行成功」非常重要。以賬戶轉賬舉例,如Service-Z的操作是從賬戶上扣100元,但他的餘額只有10元,那無論怎麼重試、人工,都是不可能成功的,也就不可能達到最終都操作成功的一致性狀態,此時要麼提前校驗、鎖定,要麼就採用前面的撤銷的思路。

在實際的實踐中,除了類似Service-Z的環節失敗,還有入庫失敗、網路通信失敗、發送MQ失敗等各種可能失敗的環節,我在後面一篇《功夫貸的支付服務,是怎麼實現最終一致性的功夫貸的支付服務,是怎麼實現最終一致性的》這篇文章里,拿一個具體的case詳細地介紹了它的實現,供參考。要實現一個嚴謹的最終一致性,還是比較複雜的,所幸在全系統中,真正要保證絕對最終一致性的問題,功能點相對較少。

三、容量評估/測試

容量評估後續會專門拿個case來介紹實踐,我們主要是強調拿線上環節通過路由、監控等策略,在線測試評估容量。搭建性能測試環境,在現在已經是相對落後的手段。

四、支撐系統

對於如今的互聯網系統來說,越來越複雜,支撐系統必不可少,每一章都可以單獨列文章來分析其原理,這裡只列出些目錄。

業務監控系統:如訂單量、轉化率、各類報表等,以及相應的預警子模塊

日誌系統:哪怕就是ELK,那也是有三個組件,數據量大、工作量不小的

分散式調用鏈系統:跟蹤請求在全系統中的去向、以及快速定位出問題的地方

線上技術指標監控系統:傳統的CPU、Memory、Disk這個很多產品都提供,但還不夠,應用內的情況,我們也要知道,主要包括:線程池使用情況、GC的頻率和時間、線程棧

技術指標監控系統:Error率、Latency、Exception統計等

運維支撐系統:資源管理、容器化、部署、灰度、可用性指標等

測試平台:介面測試、MQ測試、集成測試、性能測試、測試環境構建、持續集成


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 全球大搜羅 的精彩文章:

歪,我喜歡你呀
樹樁盆景造型以柔塑剛技藝漫談

TAG:全球大搜羅 |