采用微服務架構后,當分布式系統到達一定量級時,每個環境都可能出錯,因此在系統設計時應該考慮如何減輕故障的影響,如何從故障中快速恢復。一般從以下兩點來考察系統的穩定性:
- 高可用:當前服務依賴的下游服務性能降低或者失敗時,該服務怎么相應,是快速失敗還是重試?大促時如何應對瞬間涌入的流量?
- 高並發:底層服務如何保證服務的吞吐量?如何提高消費者的處理速度?
高可用
限流
限流算法
計數器法:該算法維護一個counter,規定在單位時間內counter的大小不能超過最大值,每隔固定時間就將counter的值置為零。
漏桶算法:水(請求)先進入到漏桶里,漏桶以一定的速度出水,當水流入速度過大會直接溢出(拒絕服務),可以看出漏桶算法能強行限制數據的傳輸速率
令牌桶算法:一個存放固定容量令牌的桶,按照固定速率(每秒/或者可以自定義時間)往桶里添加令牌,然后每次獲取一個令牌,當桶里沒有令牌可取時,則拒絕服務。令牌桶分為2個動作,動作1(固定速率往桶中存入令牌)、動作2(客戶端如果想訪問請求,先從桶中獲取token)
限流實踐
Google開源工具包Guava提供了限流工具類RateLimiter,該類基於令牌桶算法(Token Bucket)來完成限流,非常易於使用.RateLimiter經常用於限制對一些物理資源或者邏輯資源的訪問速率.它支持兩種獲取permits接口,一種是如果拿不到立刻返回false,一種會阻塞等待一段時間看能不能拿到.
RateLimiter設計思路:RateLimiter的主要功能就是通過限制請求流入的速度來提高穩定的服務速度。實現QPS速率的最簡單的方式就是記住上一次請求的最后授權時間,然后保證1/QPS秒內不允許請求進入.比如QPS=5,如果我們保證最后一個被授權請求之后的200ms的時間內沒有請求被授權,那么我們就達到了預期的速率.如果一個請求現在過來但是最后一個被授權請求是在100ms之前,那么我們就要求當前這個請求等待100ms.按照這個思路,請求15個新令牌(許可證)就需要3秒。如果RateLimiter的一個被授權請求之前很長一段時間沒有被使用會怎么樣?這個RateLimiter會立馬忘記過去這一段時間的利用不足,而只記得剛剛的請求。
斷路器
Hystrix[hɪst'rɪks]由Netflix開源的一個延遲和容錯庫,用於隔離訪問遠程系統、服務或者第三方庫,防止級聯失敗,從而提升系統的可用性、容錯性與局部應用的彈性,是一個實現了超時機制和斷路器模式的工具類庫。
Hystrix主要提供4個功能:斷路器、隔離機制、請求聚合和請求緩存。
- 斷路器(Circuit breaker)
Hystrix Command請求后端服務失敗數量超過一定比例(默認50%), 斷路器會切換到開路狀態(Open). 這時所有請求會直接失敗而不會發送到后端服務. 斷路器保持在開路狀態一段時間后(默認5秒), 自動切換到半開路狀態(HALF-OPEN). 這時會判斷下一次請求的返回情況, 如果請求成功, 斷路器切回閉路狀態(CLOSED), 否則重新切換到開路狀態(OPEN).
- 隔離機制(Bulkheads)
線程池隔離模式:使用一個線程池來存儲當前的請求,線程池對請求作處理,設置任務返回處理超時時間,堆積的請求堆積入線程池隊列。這種方式需要為每個依賴的服務申請線程池,有一定的資源消耗,好處是可以應對突發流量(流量洪峰來臨時,處理不完可將數據存儲到線程池隊里慢慢處理)
信號量隔離模式:使用一個原子計數器(或信號量)來記錄當前有多少個線程在運行,請求來先判斷計數器的數值,若超過設置的最大線程個數則丟棄該類型的新請求,若不超過則執行計數操作請求來計數器+1,請求返回計數器-1。這種方式是嚴格的控制線程且立即返回模式,無法應對突發流量(流量洪峰來臨時,處理的線程超過數量,其他的請求會直接返回,不繼續去請求依賴的服務)
- 請求聚合
使用HystrixCollapser將前端的多個請求聚合成為一個請求發送到后端
- 請求緩存
HystrixCommand和HystrixObservableCommand實現了對請求的緩存,假如在某個上下文中有多個同時到達的相同參數的查詢,利用請求緩存功能,可以減少對后端系統的壓力。
超時與重試
在微服務系統中,如果上游應用沒有設置合理的超時和重試機制,則會造成請求響應變慢,慢請求會積壓並耗盡系統資源。超時機制應該和限流、斷路器配合使用,最終實現微服務系統的穩定性。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
高並發
幾種常見的高並發策略如下:
- 異步:提高業務過程中可異步部分的占比,提高異步部分的執行效率
- 緩存:將頻繁訪問的數據存儲在離業務處理邏輯更近的地方
- 池化:對於創建起來比較消耗資源的對象進行緩存
異步
按照異步操作出現的位置,可用分為兩類:在JVM內部,使用異步線程池或者異步回調機制;在JVM外部,可用使用消息隊列、Redis隊列等中間件
異步線程池
Java中可用通過Executors和ThreadPoolExecutor方式創建線程池,通過Executors可用快速創建四種常見的線程池,但這種方式在實際使用中並不推薦,因為這種方式創建出來的線程池的可控性較差(FixedThreadPool和SingleThreadPool:允許的請求隊列長度為Integer.MAX_VALUE,可能對堆積大量請求,從而導致OOM;CacheThreadPool和ScheduledThreadPool:允許創建線程數量為Integer.MAX_VALUE,可能創建大量線程,從而導致OOM)。而通過ThreadPoolExecutor的方式去創建,可用讓開發人員更明確線程池的運行規則,避免資源耗盡的風險。同時可用實現自定義的拒絕策略,從而打印告警日志,並根據日志監控線程池的運行情況,在發生異常時及時處理。
異步回調機制
異步回調與同步調用的不同之處在於,請求發起方不需要等待服務方的響應返回,可用先去做別的業務。接口請求返回后會自動調用預先定義的回調函數,進行后續的業務處理。
消息隊列
消息隊列是系統架構層面的異步策略,應用場景很廣泛,如削峰填谷。典型應用就是優惠券發放和電商秒殺系統
緩存
在分布式系統中,緩存無處不在。從緩存靜態資源的CDN,到緩存http請求的nginx,從瀏覽器或App客戶端的緩存,到服務端到數據存儲的緩存,不一而足。常見的分布式緩存中間件有Redis、Memcache等。在分布式系統中使用緩存時,還需要處理好緩存穿透、緩存雪崩、大value緩存監測、熱點緩存等問題。
緩存穿透:一般的緩存系統都是按照key去緩存查詢的,如果不存在則去后端系統查找。如果key對應的value一定不存在,並且對該key的並發請求量很大,就會對后端系統造成很大的壓力。
緩存雪崩:當緩存服務器重啟或者大量緩存集中在某一個時間段消失,在消失的這段時間,也會對后端系統帶來很大壓力