![](https://img2020.cnblogs.com/blog/610439/202003/610439-20200328185036314-1856960036.png) [TOC] 上週在 [[分散式服務\]海量網際網路服務設計的有損價值觀](https://www.cnblogs.com/QG-whz/p/12508292.html) 這篇文章中提到,與金融行業服務要求的強一致性不同,海量網際網路服務要求的是能夠扛住更高的qps,服務降級研究的問題是在伺服器資源有限的情況下,如何提供更大的訪問量,並保證系統穩定執行。 最近我搬了個房子,房東還沒來得及上面裝寬頻,所以在家裡我是用手機熱點上網的。熱點網速有限,看視訊的時候,為了視訊流程的播放,我將視訊從藍光調整到270P,雖然視訊清晰度很低,但是可以流暢播放,也基本滿足的需求。這就是降級。與此類似,我們的系統在應對一些突發情況的時候,也需要這樣的降級。 為此必須放棄掉某些東西,比如: - 資料的一致性,無需絕對的一致性,而保障最終一致即可; - 系統功能的降級,例如把一些不重要的功能,或是非關鍵路徑的功能暫時關閉; - 使用者體驗降級,分多種方案給使用者不同的體驗 ## 降級一致性 ### 快取的使用 在資料庫之上,新增一層快取是降低一致性的一種做法。 快取的訪問速度、包括可承載的訪問qps都要遠遠高於資料庫。使用者請求到達時優先訪問快取,如果快取命中,則直接返回快取中的資料。如果快取為空,則查詢資料庫,並且進行快取的更新。使用快取要注意的問題是: - 快取的更新機制,是失效過期被動更新,還是主動refresh,策略如何選擇; - 快取過期時間。快取最長可以儲存多長的時間? - 熱點問題,例如“商品庫存”這個值,在每次查詢商品列表的時候都會進行查詢,很容易就會成為熱點key。熱點key會造成redis叢集的流量傾斜。解決熱點key,可以將快取key進行hash打散。或是在快取之上再構建一層本地記憶體cache,例如使用Guava cache來做本地快取。 - 快取擊穿問題,當資料在資料庫中不存在時,如何避免使用者的請求每次都穿過快取打到資料庫上。 - 新增快取開關,當快取的資料有問題時,有沒有手段快速讓快取失效 或是 臨時關閉快取。 - ... 除了在資料庫之上新增快取之外,對下游的介面也可以新增快取。 例如某個介面提供了查詢資料的功能,這些資料並不會經常改變;或是業務上對資料變動的敏感度並不高,那麼這時候可以犧牲一點資料變動同步的要求,在對下游介面呼叫上增加一層快取。 上述的關於快取的問題,一些快取框架都提供了方便快捷的使用方式,例如JetCache框架。 ### 流程非同步化 通過將流程非同步化,將結果延遲給使用者,也能夠讓系統扛住更大的請求。 舉個例子,在打款這樣的環節中,如果所有的步驟都是同步的,那麼整個流程會非常長,並且通常來說,打款介面對qps都有限制, 例如限制一個平臺每秒只能有200的轉賬打款的qps。如果整個轉賬業務邏輯都是同步的,很有可能因為這些下游介面的限制(尤其是支付環節)導致流程中止,無法繼續往下走。 因此,當類似的場景有高qps出現時,就可以將流程中的同步邏輯用非同步來實現。例如告知使用者正在打款中,到賬資訊請等待通知等。使用者的請求可以使用訊息佇列先快取起來,再根據系統的消費能力慢慢消化掉。 ### 行為聚集化 同樣是利用非同步的思想,在某些特殊的場景下可以將使用者的請求聚合起來進行更新。 這個思想在很多中介軟體中都有,例如kafka中為了減少網路帶來的系統開銷,可以通過batch.size這個引數來設定批量提交的資料大小,當積壓的訊息達到這個值的時候就會統一發送。 也可以將這種思維使用在業務上。例如像一款線上種菜的遊戲,使用者有多塊土地可以種植作物,作物統一收成為積分。一般使用者在收穫的時候會點選多塊土地同時進行收穫,使用者積分賬戶屬於重要資料,可以每次收穫時都進行累計。但是其他關注使用者累計積分的非關鍵分支,則可以進行聚合後再統一累計。例如下游有一個任務服務,專門負責收集使用者的收集積分行為,讓使用者達成某個成就完成任務,這個服務也關心使用者積分的累計行為。這種情況下,可以不必使用者的每次行為都去呼叫任務服務,而是上游做聚集後統一發送。 具體做法 可以使用類似kafka延時佇列的元件,在使用者第一次收集積分時,上游服務便傳送一個延時2秒的訊息出去。上游服務本身會消費這個延時訊息,消費後將延時期間的累計值再傳給下游的任務服務。雖然使用者的成就累計會延遲2秒,但這種聚集行為的做法,會大大降低下游服務的壓力。 ## 體驗降級 ### 簡化流程 功能降級在不得已的一些情況下也可以使用,例如某個流程有非常多的校驗邏輯,舉個例子,還是使用上面QQ農場的例子。QQ農場使用者是可以互相偷菜的,偷菜的時候是否要去校驗偷取與被偷取使用者之間是否是好友關係。當好友關係校驗服務成為一個瓶頸時,能否將好友關係校驗去掉?而為了避免這樣做帶來的風險,可以同時啟用一個限制,就是限制在降級的期間,每個使用者僅能偷取其他使用者1次,避免有黑產使用者不斷偷取其他使用者的菜。 ### 分級體驗 當流量過大時,甚至可以只保留種、偷、收的基本功能,其他的模組暫時遮蔽掉入口,例如成就係統、任務系統等暫時下線。又如電商系統,在拉取商品列表的時候,將商品推薦模組移除掉,商品推薦屬於錦上添花的東西,去掉了也不影響使用者的正常使用。又如在拉取評論列表時,可以只拉取使用者的首次評論,追評資訊不再拉取,等等。 在類似相簿這種場景下,可以將體驗細分為正常訪問圖片、不預拉取圖片、用中圖代替大圖、用預設圖代替大圖、只允許訪問小圖或封面、不允許訪問相簿等多個層級,根據不同的資源狀況和場景為不同的使用者提供差異化服務。依據不同的服務壓力切換到不同的使用者體驗級別上。 ## 降級服務要點 首先要明確的是降級達到的目的是什麼,有些降級是降低對下游的壓力,有些降級是降低讀壓力,有些則是降低寫壓力。例如上文提到的加快取是降低讀壓力,行為聚集、非同步是降低寫壓力。不同的降級方案所針對的場景應該要明確。 第二是進行業務上的梳理,哪些是必須有的基本功能,例如農場的種菜;哪些是錦上添花的功能,例如商品詳情頁的“好物推薦”;哪些是可以犧牲的體驗,例如相簿的多個體驗分級,這些簡化達到的目的都需要梳理出來。 降級的時間段,有些系統存在流量尖刺的情況,例如商品兌換系統,0點可能是更新庫存的時候,使用者來兌換商品的請求會形成一個流量高峰。又例如QQ相簿,可能在晚上20:00 - 21:00是使用者瀏覽的一個高峰時間段。在不同的時間段,可以選擇不同的降級方案。 降級也要注意和前端的配合,例如商品搶購列表 高峰時間段降級,不展示具體的剩餘數量,只展示“商品搶購中”的文案,需要前端配合一起做降級。 降級可以做到自動化、半自動化、或完全的手動。應該預留手動調整的能力,避免系統會誤判,緊急情況下可以人工介入操作。因為降級的功能不總是會真正進行,所以方案都要事先預