微服務架構的核心要點和實現原理解析

juoduomade發表於2018-08-28

摘要:本文中,我們將進一步理解微服務架構的核心要點和實現原理,為讀者的實踐提供微服務的設計模式,以期讓微服務在讀者正在工作的專案中起到積極的作用。

微服務架構中職能團隊的劃分

傳統單體架構將系統分成具有不同職責的層次,對應的專案管理也傾向於將大的團隊分成不同的職能團隊,主要包括:使用者互動UI團隊、後臺業務邏輯處理團隊與資料存取ORM團隊、DBA團隊等。每個團隊只對自己分層的職責負責,並對使用方提供元件服務質量保證。如果其中一個模組化元件需要升級、更新,那麼這個變更會涉及不同的分層團隊,即使升級和變更的改變很小,也需要進行跨團隊溝通:需求階段需要跨團隊溝通產品功能,設計階段需要跨團隊溝通設計方案,開發階段需要跨團隊溝通具體的介面定義,測試階段需要溝通業務迴歸等事宜,甚至上線都需要跨團隊溝通應用的上線順序。可見在傳統的整體架構下,後期的維護成本很高,出現事故的風險很大。

根據康威定律,團隊的交流機制應該與架構設計機制相對應。因此,在微服務架構下,職能團隊的劃分方法是我們首先要考慮的一個核心要素。

微服務時代的團隊溝通方式如圖1-1所示。

 

圖1-1

 

微服務架構按照業務的功能進行劃分,每個單一的業務功能叫作一個服務,每個服務對應一個獨立的職能團隊,團隊裡包含使用者互動UI設計師、後臺服務開發人員、DBA、運營和運維人員。

在傳統的整體架構中,軟體是有生命週期的,經歷需求分析、開發和測試,然後被交付給運維團隊,這時開發團隊將會解散,這是對軟體的一個“放手”。而在微服務架構中,提倡運維人員也是服務專案團隊的一員,倡導誰開發、誰維護,實施終生維護制度。

在業務服務的內部實現需要升級或者變更時,團隊內的各角色成員進行溝通即可,而不需要進行跨團隊溝通,這大大提高了溝通效率。只有服務之間的介面需要變更時才需要跨部門溝通,如果前期在服務之間的互動上定義了良好的介面,則介面變更的概率並不大,即使介面模式有變更,也可以通過一定的設計模式和規範來解決。

微服務的去中心化治理

筆者曾經在一個網際網路平臺上工作,平臺的決策者倡導建設API閘道器,所有外部服務和內部服務都由統一的API閘道器進行管理。在專案初期,中心化的API閘道器統一了所有API的入口,這看起來很規範,但從技術角度來看限制了API的多樣化。隨著業務的發展,API閘道器開始暴露問題,每個使用者請求經過機房時只要有服務之間的互動,則都會從API閘道器進行路由,服務上量以後,由於內部服務之間的互動都會疊加在API閘道器的呼叫上,所以在很大程度上放大了API閘道器的呼叫TPS,API閘道器很快就遇到了效能瓶頸。

這個案例是典型的微服務的反模式,微服務倡導去中心化的治理,不推薦每個微服務都使用相同的標準和技術來開發和使用服務。在微服務架構下可以使用C++開發一個服務,來對接Java開發的另外一個服務,對於異構系統之間的互動標準,通常可以使用工具來補償。開發者可以開發共用的工具,並分享給異構系統的開發者使用,來解決異構系統不一致的問題,例如:Thrift遠端呼叫框架使用中間語言(IDL)來定義介面,中間語言是獨立於任何語言的,並提供了工具來生成中間語言,以及在中間語言與具體語言之間的程式碼轉換。

微服務架構倡導去中心化的服務管理和治理,儘量不設定中心化的管理服務,最差也需要在中心化的管理服務當機時有替代方案和設計。在筆者工作的支付平臺服務化建設中,第1層SOA服務化採用Dubbo框架進行定製化,如果Dubbo服務化出現了大面積的崩潰,則服務化體系會切換到點對點的hessian遠端呼叫,這被稱為服務化降級,降級後點對點的hessian遠端呼叫時沒有中心化節點,整體上符合微服務的原理。

微服務的互動模式

本節介紹微服務之間互動的通用設計模式,這些設計模式對微服務之間的互動定義契約,服務的生產者和呼叫者都需要遵守這些契約,才能保證微服務不出問題。

1. 讀者容錯模式

讀者容錯模式(Tolerant Reader)指微服務化中服務提供者和消費者之間如何對介面的改變進行容錯。從字面上來講,消費者需要對提供者提供的功能進行相容性設計,尤其對服務提供者返回的內容進行相容,或者解決在服務提供者改變介面或者資料的格式的情況下,如何讓服務消費者正常執行。

任何一個產品在設計時都無法預見將來可能增加的所有需求,服務的開發者通常通過迭代及時地增加新功能,或者讓服務提供的API自然地演進。不過,服務提供者對外提供的介面的資料格式的改變、增加和刪除,都會導致服務的消費者不能正常工作。

因此,在服務消費者處理服務提供者返回的訊息的過程中,需要對服務返回的訊息進行過濾,只提取自己需要的內容,對多餘或者未知的內容採取拋棄的策略,而不是硬生生地拋錯處理。

在實現過程中不推薦使用嚴格的校驗策略,而是推薦使用寬鬆的校驗策略,即使服務消費者拿到的訊息報文發生了改變,程式也只需盡最大努力提取需要的資料,同時忽略不可識別的資料。只有在服務消費者完全不能識別接收到的訊息,或者無法通過識別的資訊繼續處理流程時,才能丟擲異常。

服務的消費者的容錯模式忽略了新的訊息項、可選的訊息項、未知的資料值及服務消費者不需要的資料項。

筆者當前在某個支付公司工作,公司裡幾乎每個業務都需要使用列舉型別,在微服務平臺下,筆者在研發流程規範中定義了一條列舉值使用規範:

在服務介面的定義中,引數可以使用列舉值,在返回值的DTO中禁止使用列舉值。

這條規範就是讀者容錯模式在實踐中的一個例項,之所以在引數中允許使用列舉值,是因為如果服務的提供者升級了介面,增加了列舉值,若服務的消費者並沒有感知,則服務的消費者得知新的列舉值就可以傳遞新的列舉值了;但是如果介面的返回DTO中使用了列舉值,並且因為某種原因增加了列舉值,則服務消費者在反序列化列舉時就會報錯,因此在返回值中我們應該使用字串等互相認可的型別,來做到雙方的互相相容,並實現讀者容錯模式。

2. 消費者驅動契約模式

消費者驅動契約模式用來定義服務化中服務之間互動介面改變的最佳規則。

服務契約分為:提供者契約、消費者契約及消費者驅動的契約,它從期望與約束的角度描述了服務提供者與服務消費者之間的聯動關係。

  • 提供者契約:是我們最常用的一種服務契約,顧名思義,提供者契約是以提供者為中心的,提供者提供了什麼功能和訊息格式,各消費者都會無條件地遵守這些約定,不論消費者實際需要多少功能,消費者接受了提供者契約時,都會根據服務提供者的規則來使用服務。

  • 消費者契約:是對某個消費者的需求進行更為精確的描述,在一次具體的服務互動場景下,代表消費者需要提供者提供功能中的哪部分資料。消費者契約可以被用來標識現有的提供者契約,也可以用來發現一個尚未明確的提供者契約。

  • 消費者驅動的契約:代表服務提供者向其所有當前消費者承諾遵守的約束。一旦各消費者把自己的具體期望告知提供者,則提供者無論在什麼時間和場景下,都不應該打破契約。

在現實的服務互動設計中,上面這三種契約是同時存在的,筆者所在的支付平臺裡,交易系統在完成一筆支付後,需要到賬務系統為商戶入賬,在這個過程中,服務契約表現如下。

  • 生產者契約:賬務系統提供Dubbo服務化介面,引數為商戶賬戶ID、入賬訂單號和入賬金額。

  • 消費者契約:賬務系統返回DTO,包含商戶賬戶ID、入賬訂單號、入賬金額、入賬時間、賬務流水號、入賬狀態等,而交易系統只需使用其中的入賬訂單號和入賬狀態。

  • 消費者驅動的契約:為了保證資金安全,交易系統作為入賬的發起者向賬務提出要求,需要賬務做冪等和濾重處理,對重複的入賬請求進行攔截;賬務系統在接受這個契約後,即使將來有任何改變,也不能打破這個限制,否則就會造成資金的損失,這在金融系統中是最嚴重的問題。

服務之間的互動需要使用的三種服務契約如圖1-2所示。

 

圖1-2

 

從圖1-2可以看到,服務提供者契約是服務提供者單方面定下的規則,而一個消費者契約會成為提供者契約的一部分,多個服務消費者可以對服務提供者提出約束,服務提供者需要在將來遵守服務消費者提出的契約,這就是消費者驅動的契約。

3. 去資料共享模式

與SOA服務化對比,微服務是去ESB匯流排、去中心化及分散式的;而SOA還是以ESB為核心實現遺留系統的整合,以及基於Web Service為標準實現的通用的面向服務的架構。在微服務領域,微服務之間的互動通過定義良好的介面來實現,不允許使用共享資料來實現。

在實踐的過程中,有些方案的設計使用快取或者資料庫作為兩個微服務之間的紐帶,在業務流程的處理過程中,為了處理簡單,前一個服務將中間結果存入資料庫或者快取,下一個服務從快取或者資料庫中拿出資料繼續處理。處理流程如圖1-3所示。

 

圖1-3

 

這種互動流程的缺點如下。

  • 使得微服務之間的互動除了介面契約,還存在資料儲存契約。

  • 上游的資料格式發生變化時,可能導致下游的處理邏輯出現問題。

  • 多個服務共享一個資源服務,對資源服務的運維難以劃清職責和界限。

  • 在做雙機房獨立部署時,需要考慮服務和資源的路由情況,跨機房的服務呼叫不能使用獨立的資源部署模式,因此難以實現服務自治。

因此,在設計微服務架構時,一定不要共享快取和資料庫等資源,也不要使用匯流排模式,服務之間的通訊和互動只能依賴定義良好的介面,通常使用RESTful樣式的API或者透明的RPC呼叫框架。

微服務的分解和組合模式

使用微服務架構劃分服務和團隊是微服務架構實施的重要一步,良好的劃分和拆分使系統達到鬆耦合和高內聚的效果,然後通過微服務的靈活組裝可以滿足上層的各種各樣的業務處理需求。

在微服務架構的需求分析和架構設計過程中,通常是用領域的動詞和名詞來劃分微服務的,例如,對於一個電商後臺系統,可以分解為訂單、商品、商品目錄、庫存、購物車、交易、支付、發票、物流等子系統,每個名詞和動詞都可以是一個微服務,將這幾個微服務組合在一起,就實現了電商平臺使用者購買商品的整個業務流。

這樣拆分以後,系統具有敏捷性、靈活性、可伸縮性等,拆分後有多個高度自治的微服務,那麼以什麼方式組合微服務呢?

1. 服務代理模式

服務代理模式是最簡單的服務組合模式,它根據業務的需求選擇呼叫後端的某個服務。在返回給使用端之前,代理可以對後端服務的輸出進行加工,也可以直接把後端服務的返回結果返回給使用端。

服務代理模式的架構如圖1-4所示。

 

圖1-4

 

在筆者工作的微服務化架構平臺下,經常會使用這種模式,典型的案例是做平滑的系統遷移,通常經歷如下4個階段。

  • 在新老系統上雙寫。

  • 遷移雙寫之前的歷史遺留資料。

  • 將讀請求切換到新系統。

  • 下調雙寫邏輯,只寫新系統。

服務代理模式常常應用到第3步,一般會對讀請求切換設計一個開關,開關開啟時查詢新系統,開關關閉時查詢老系統。

遷移案例中開關的邏輯如圖1-5所示。

 

圖1-5

 

2. 服務聚合模式

服務聚合模式是最常用的服務組合模式,它根據業務流程處理的需要,以一定的順序呼叫依賴的多個微服務,對依賴的微服務返回的資料進行組合、加工和轉換,最後以一定的形式返回給使用方。

這裡,每個被依賴的微服務都有自己的快取和資料庫,聚合服務本身可以有自己的資料儲存,包括快取和資料庫等,也可以是簡單的聚合,不需要持久化任何資料。

服務聚合模式的架構如圖1-6所示。

 

圖1-6

 

這裡體現了DRY(Don’t Repeat Yourself)原則的設計理念,在設計或者構造應用時,最大限度地重用了現有的實現。假如一塊業務邏輯由三個獨立的邏輯塊組成,每個獨立的邏輯塊可能有多個使用方,則DRY原則推薦將三個獨立的邏輯塊封裝成三個獨立執行的微服務,然後使用本節的服務聚合模式開發聚合服務,將三個獨立的邏輯塊聚合在一起提供給上層組合服務。這樣的設計原則有如下好處。

  • 三個獨立的子服務可以各自獨立開發、敏捷變更和部署。

  • 聚合服務封裝下層的業務處理服務,由三個獨立的子服務完成資料持久化等工作,專案結構清晰明瞭。

  • 三個獨立的子服務對於其他使用方仍然可以重用。

考慮到本節開頭的例子,在對微服務進行拆分時,將電商後臺系統大致拆分成訂單、商品、商品目錄、庫存、購物車、交易、支付、發票、物流等微服務,那麼電商平臺的前端應用就是後端各個微服務的一個最大的聚合服務,前端應用通過呼叫商品和商品目錄顯示商品列表,提供給使用者選擇商品的功能,使用者選擇商品後增加商品到購物車,在使用者從購物車結算時,呼叫交易系統完成交易和支付等。

電商前臺的聚合模式的案例架構如圖1-7所示。

 

 

圖1-7

 

另外,聚合服務也可以是一個純後臺服務,通過聚合對使用方輸出組合的服務,例如在上面的電商系統案例中,在使用者選擇結算後,系統呼叫交易,交易系統會呼叫庫存系統鎖庫存,然後建立交易訂單,引導使用者去支付,支付成功後扣減庫存,最後通過發票服務開具電子發票。

電商後臺交易服務的聚合模式架構如圖1-8所示。

 

 

圖1-8

 

3. 服務串聯模式

服務串聯模式類似於一個工作流,最前面的服務1負責接收請求和響應使用方,串聯服務後再與服務1互動,隨後服務1與服務2互動,最後,從服務2產生的結果經過服務1和串聯服務逐個處理後返回給使用方,如圖1-9所示。

 

 

圖1-9

 

服務串聯模式之間的呼叫通常使用同步的RESTful風格的遠端呼叫實現,注意,這種模式採用的是同步呼叫方式,在串聯服務沒有完成並返回之前,所有服務都會阻塞和等待,一個請求會佔用一個執行緒來處理,因此在這種模式下不建議服務的層級太多,如果能用服務聚合模式代替,則優先使用服務聚合模式,而不是使用這種服務串聯模式。

相對於服務聚合模式,服務串聯模式有一個優點,即串聯鏈路上再增加一個節點時,只要不是在串聯服務的正後面增加,那麼串聯服務是無感知的。

在串聯服務中呼叫鏈的最後端增加服務無感知的架構如圖1-10所示。

 

 

圖1-10

 

在上面提及的電商案例中,UI前端應用呼叫交易,交易呼叫商品庫存系統鎖定庫存和扣減庫存,使用的就是服務串聯模式。

服務串聯模式案例的架構如圖1-11所示。

 

圖1-11

 

4. 服務分支模式

服務分支模式是服務代理模式、服務聚合模式和服務串聯模式相結合的產物。

分支服務可以擁有自己的資料庫儲存,呼叫多個後端的服務或者服務串聯鏈,然後將結果進行組合處理再返回給客戶端。分支服務也可以使用代理模式,簡單地呼叫後端的某個服務或者服務鏈,然後將返回的資料直接返回給使用方。

服務分支模式的架構如圖1-12所示。

 

圖1-12

 

在實際的業務平臺建設中,由於業務的複雜性,抽象的微服務可能有多層的依賴關係,依賴關係並不會太簡單,經常呈現樹形的分支結構。

以電商平臺的支付服務架構為例,如圖1-13所示。

 

圖1-13

 

支付服務對接兩個外部的支付閘道器,都要經過各自的支付渠道閘道器,同時支援賬戶餘額支付,這個支付服務其實就是一個分支模式,在實際專案中這種服務分支模式很多。

筆者在構建支付平臺時,由於大量地使用了服務分支模式,所以發現了一個比較有趣的現象,如下所述。

假設有一個基礎服務,在服務分支模式的多個層次中對基礎服務都有依賴,那麼當基礎服務的一臺機器當機時,假設基礎服務有8臺機器,則最後受影響的流量並不是1/8。假設基礎服務6共有8臺機器,服務1、服務3和服務5組成某服務的一個呼叫鏈,則呼叫鏈過程中會多次呼叫基礎服務6。

具體服務的呼叫鏈示意圖如圖1-14所示。

 

圖1-14

 

某天,基礎服務6的8臺機器中的1臺當機,按照常理,大家都認為隻影響其中1/8的流量,而統計結果顯示影響的業務結果竟然大於1/8。

仔細思考,造成這個結果的原因是呼叫鏈上有多個層次重複呼叫了基礎服務,導致基礎服務掛掉時影響的流量有累加效果,具體計算如下。

假設進入系統的流量為n,呼叫鏈從服務3開始呼叫服務6,服務3有1/8的流量失敗,這時剩下的成功的流量為7/8 ×n,剩下的成功的流量繼續走到服務5,服務5再次呼叫服務6,又有1/8的流量失敗,剩下7/8 × 7/8× n。

假設基礎服務資源池中的機器個數為i,一次掛掉的機器個數為j,一個呼叫鏈中呼叫x次基礎服務,那麼正確處理的流量的計算公式為:

假設允許的可用性波動率為a,求出底層服務一次當機1臺時最少應該配置的機器數為:

對公式進行轉換:

由於一次只允許一臺機器當機:

所以得出需要設定的機器數量i為:

對於上面的案例,每次最多允許基礎服務6當機1臺,在這種情況下需要保持可用性的波動率小於25%,一共有兩層服務依賴基礎服務6,通過上述公式計算得出:

 

i > 7.5

 

結果,至少為服務6部署9臺機器,這樣在1臺機器當機時,對可用性的波動性影響控制在25%以內。

由於分支模式放大了服務的依賴關係,因此在現實的微服務設計中儘量保持服務呼叫級別的簡單,在使用服務組合和服務代理模式時,不要使用服務串聯模式和服務分支模式,以保持服務依賴關係的清晰明瞭,這也減少了日後維護的工作量。

5. 服務非同步訊息模式

前面的所有服務組合模式都使用同步的RESTful風格的同步呼叫來實現,同步呼叫模式在呼叫的過程中會阻塞執行緒,如果服務提供方遲遲沒有返回,則服務消費方會一直阻塞,在嚴重情況下會撐滿服務的執行緒池,出現雪崩效應。

因此,在構建微服務架構系統時,通常會梳理核心系統的最小化服務集合,這些核心的系統服務使用同步呼叫,而其他核心鏈路以外的服務可以使用非同步訊息佇列進行非同步化。

服務非同步訊息模式的架構如圖1-15所示。

 

圖1-15

 

在圖1-15中,聚合服務同步呼叫服務1和服務2,而服務2通過訊息佇列將非同步訊息傳遞給服務3和服務4。

典型的案例就是在電商系統中,交易完成後向物流系統發起訊息通知,通知物流系統發貨,如圖1-16所示。

 

圖1-16

 

6. 服務共享資料模式

服務共享資料模式其實是反模式,在1.3.3節中提出了去資料共享模式,由於去掉了資料共享,所以僅僅通過服務之間良好定義的介面進行互動和通訊,使得每個服務都是自治的,服務本身和服務的團隊包含全形色棧的技術和運營人員,這些人都是專業的人做專業的事,使溝通在團隊內部解決,因此可以使效率最大化。

服務共享資料模式的架構如圖1-17所示。

 

圖1-17

 

然而,在下面兩種場景下,我們仍然需要資料共享模式。

  • 單元化架構

一些平臺由於對效能有較高的要求,所以採用微服務化將服務進行拆分,通過網路服務進行通訊,儘管網路通訊的頻寬已經很寬,但是還會有效能方面的損耗,在這種場景下,可以讓不同的微服務共享一些資源,例如:快取、資料庫等,甚至可以將快取和資料在物理拓撲上與微服務部署在一個物理機中,最大限度地減少網路通訊帶來的效能損耗,我們將這種方法稱為“單元化架構”。

單元化架構的示意圖如圖1-18所示。

 

圖1-18

 

  • 遺留的整體服務

對於歷史遺留的傳統單體服務,我們在重構微服務的過程中,發現單體服務依賴的資料庫表耦合在一起,對其拆分需要進行反規範化的處理,可能會造成資料一致性問題,在沒有對其完全理解和有把握的前提下,會選擇保持現狀,讓不同的微服務暫時共享資料儲存。

除了上面提到的兩個場景,任何場景都不能使用服務資料共享模式。

微服務的容錯模式

在使用了微服務架構以後,整體的業務流程被拆分成小的微服務,並組合在一起對外提供服務,微服務之間使用輕量級的網路協議通訊,通常是RESTful風格的遠端呼叫。由於服務與服務的呼叫不再是程式內的呼叫,而是通過網路進行的遠端呼叫,眾所周知,網路通訊是不穩定、不可靠的,一個服務依賴的服務可能出錯、超時或者當機,如果沒有及時發現和隔離問題,或者在設計中沒有考慮如何應對這樣的問題,那麼很可能在短時間內服務的執行緒池中的執行緒被用滿、資源耗盡,導致出現雪崩效應。本節針對微服務架構中可能遇到的這些問題,講解應該採取哪些措施和方案來解決。

1. 艙壁隔離模式

這裡用航船的設計比喻艙壁隔離模式,若一艘航船遇到了意外事故,其中一個船艙進了水,則我們希望這個船艙和其他船艙是隔離的,希望其他船艙可以不進水,不受影響。在微服務架構中,這主要體現在如下兩個方面。

1)微服務容器分組

筆者所在的支付平臺應用了微服務,將微服務的每個節點的服務池分為三組:準生產環境、灰度環境和生產環境。準生產環境供內側使用;灰度環境會跑一些普通商戶的流量;大部分生產流量和VIP商戶的流量則跑在生產環境中。這樣,在一次比較大的重構過程中,我們就可以充分利用灰度環境的隔離性進行預驗證,用普通商戶的流量驗證重構沒有問題後,再上生產環境。

另外一個案例是一些社交平臺將名人的自媒體流量全部路由到服務的核心池子中,而將普通使用者的流量路由到另外一個服務池子中,有效隔離了普通使用者和重要使用者的負載。

其服務分組如圖1-19所示。

 

圖1-19

 

2)執行緒池隔離

在微服務架構實施的過程中,我們不一定將每個服務拆分到微小的力度,這取決於職能團隊和財務的狀況,我們一般會將同一類功能劃分在一個微服務中,儘量避免微服務過細而導致成本增加,適可而止。

這樣就會導致多個功能混合部署在一個微服務例項中,這些微服務的不同功能通常使用同一個執行緒池,導致一個功能流量增加時耗盡執行緒池的執行緒,而阻塞其他功能的服務。

執行緒池隔離如圖1-20所示。

 

圖1-20

2. 熔斷模式

可以用家裡的電路保險開關來比喻熔斷模式,如果家裡的用電量過大,則電路保險開關就會自動跳閘,這時需要人工找到用電量過大的電器來解決問題,然後開啟電路保險開關。在這個過程中,電路保險開關起到保護整個家庭電路系統的作用。

對於微服務系統也一樣,當服務的輸入負載迅速增加時,如果沒有有效的措施對負載進行熔斷,則會使服務迅速被壓垮,服務被壓垮會導致依賴的服務都被壓垮,出現雪崩效應,因此,可通過模擬家庭的電路保險開關,在微服務架構中實現熔斷模式。

微服務化的熔斷模式的狀態流轉如圖1-21所示。

 

 

圖1-21

 

3. 限流模式

服務的容量和效能是有限的,在第3章中會介紹如何在架構設計過程中評估服務的最大效能和容量,然而,即使我們在設計階段考慮到了效能壓力的問題,並從設計和部署上解決了這些問題,但是業務量是隨著時間的推移而增長的,突然上量對於一個飛速發展的平臺來說是很常見的事情。

針對服務突然上量,我們必須有限流機制,限流機制一般會控制訪問的併發量,例如每秒允許處理的併發使用者數及查詢量、請求量等。

有如下幾種主流的方法實現限流。

1)計數器

通過原子變數計算單位時間內的訪問次數,如果超出某個閾值,則拒絕後續的請求,等到下一個單位時間再重新計數。

在計數器的實現方法中通常定義了一個迴圈陣列(見圖1-22),例如:定義5個元素的環形陣列,計數週期為1s,可以記錄4s內的訪問量,其中有1個元素為當前時間點的標誌,通常來說每秒程式都會將前面3s的訪問量列印到日誌,供統計分析。

 

圖1-22

 

我們將時間的秒數除以陣列元素的個數5,然後取模,對映到環形陣列裡的資料元素,假如當前時間是1 000 000 002s,那麼對應當前時間的環形陣列裡的第3個元素,下標為2。

此時的陣列元素的資料如圖1-23所示。

 

圖1-23

 

在圖1-23中,當前時間為1 000 000 002s,對應的計數器在第3個元素,下標為2,當前請求是在這個時間週期內的第1個訪問請求,程式首先需要對後一個元素即第4個元素,也就是下標為3的元素清零;在1 000 000 002s內,任何一個請求如果發現下標為3的元素不為0,則都會將原子變數3清零,並記錄清零的時間。

這時程式可以對第3個元素即下標為2的元素,進行累加並判斷是否達到閾值,如果達到閾值,則拒絕請求,否則請求通過;同時,列印本次及之前3秒的資料訪問量,列印結果如下。

當前:1次,前1s:302次,前2s:201次,前3s:518次

然而,如果當前秒一直沒有請求量,下一秒的計數器始終不能清零,則下一秒的請求到達後要首先清零再使用,並更新清零時間。

在下一秒的請求到達後,若檢查到當前秒對應的原子變數計數器不為0,而且最後的清零時間不是上一秒,則先對當前秒的計數器清零,再進行累加操作,這避免發生上一秒無請求的場景,或者上一秒的請求由於執行緒排程延遲而沒有清零下一秒的場景,後面這種場景發生的概率較小。

另外一種實現計數器的簡單方法是單獨啟動一個執行緒,每隔一定的時間間隔執行對下一秒的原子變數計數器清零操作,這個時間間隔必須小於計數時間間隔。

2)令牌筒

令牌筒是一個流行的實現限流的技術方案,它通過一個執行緒在單位時間內生產固定數量的令牌,然後把令牌放入佇列,每次請求呼叫需要從桶中拿取一個令牌,拿到令牌後才有資格執行請求呼叫,否則只能等待拿到令牌再執行,或者直接丟棄。

令牌筒的結構如圖1-24所示。

 

 

圖1-24

 

3)訊號量

限流類似於生活中的漏洞,無論倒入多少油,下面有漏管的流量是有限的,實際上我們在應用層使用的訊號量也可以實現限流。 
使用訊號量的示例如下:

public class SemaphoreExample {     private ExecutorService exec = Executors.newCachedThreadPool();     public static void main(String[] args) {         final Semaphore sem = new Semaphore(5);         for (int index = 0; index < 20; index++) {             Runnable run = new Runnable() {                 public void run() {                     try {                         // 獲得許可                         sem.acquire();                         // 同時只有5個請求可以到達這裡 Thread.sleep((long) (Math.random()));                         // 釋放許可                         sem.release();                         System.out.println("剩餘許可:" + sem.availablePermits());                     } catch (InterruptedException e) {                         e.printStackTrace();                     }                 }             };             exec.execute(run);         }         exec.shutdown(); } }複製程式碼

4. 失效轉移模式

若微服務架構中發生了熔斷和限流,則該如何處理被拒絕的請求呢?解決這個問題的模式叫作失效轉移模式,通常分為下面幾種。

  • 採用快速失敗的策略,直接返回使用方錯誤,讓使用方知道發生了問題並自行決定後續處理。

  • 是否有備份服務,如果有備份服務,則迅速切換到備份服務。

  • 失敗的服務有可能是某臺機器有問題,而不是所有機器有問題,例如OOM問題,在這種情況下適合使用failover策略,採用重試的方法來解決,但是這種方法要求服務提供者的服務實現了冪等性。

  • 說到這裡順便給大家推薦一個Java方面的交流群650385180,裡面會分享一些資深架構師錄製的視訊錄影:有Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能優化這些成為架構師必備的知識體系。還能領取免費的學習資源,。相信對於已經工作和遇到技術瓶頸的朋友,在這個群裡會有你需要的內容。

 

微服務的粒度

在服務化系統或者微服務架構中,我們如何拆分服務才是最合理的?服務拆分到什麼樣的粒度最合適?

按照微服務的初衷,服務要按照業務的功能進行拆分,直到每個服務的功能和職責單一,甚至不可再拆分為止,以至於每個服務都能獨立部署,擴容和縮容方便,能夠有效地提高利用率。拆得越細,服務的耦合度越小,內聚性越好,越適合敏捷釋出和上線。

然而,拆得太細會導致系統的服務數量較多,相互依賴的關係較複雜,更重要的是根據康威定律,團隊要響應系統的架構,每個微服務都要有相應的獨立、自治的團隊來維護,這也是一個不切實際的想法。

因此,這裡倡導對微服務的拆分適可而止,原則是拆分到可以讓使用方自由地編排底層的子服務來獲得相應的組合服務即可,同時要考慮團隊的建設及人員的數量和分配等。

有的公司把每個介面包裝成一個工程,或者把每一次JDBC呼叫包裝成一個工程,然後號稱是“微服務”,最後有成百上千的微服務專案,這是不合理的。當然,有的公司把一套介面完成的一個粗粒度的流程耦合在一個專案中,導致上層服務想要使用這套介面中某個單獨的服務時,由於這個服務與其他邏輯耦合在一起,所以需要在流程中做定製化才能實現使用方使用部分服務的需求,這也是不合理的,原因是服務粒度太粗。

總之,拆分的粒度太細和太粗都是不合理的,根據業務需要,能夠滿足上層服務對底層服務自由編排並獲得更多的業務功能即可,並需要適合團隊的建設和佈局。

最後傳播一個重要的訊息,雲端計算現已白菜價,2018年雲伺服器低至不到300元/年。這裡有一份雲端計算優惠活動列表,現在上車還來得及!


轉自http://blog.51cto.com/13676067/2164408

相關文章