每個程序員都有一個成為架構師的夢想,奈何手裡無槍無法點燃心中奇夢。本系列文章分享如何讓你手裡有槍,隻要努力,技術的夢想一定能實現,這應該是衆多夢想中離地表最近的一個。
導緻接口級故障的原因接口級故障的典型表現就是系統并沒有宕機,網絡也沒有中斷,但業務卻出現問題了。例如,業務響應緩慢、大量訪問超時、大量訪問出現異常(給用戶彈出提示“無法連接數據庫”),這類問題的主要原因在于系統壓力太大、負載太高,導緻無法快速處理業務請求,由此引發更多的後續問題。例如,最常見的數據庫慢查詢将數據庫的服務器資源耗盡,導緻讀寫超時,業務讀寫數據庫時要麼無法連接數據庫、要麼超時,最終用戶看到的現象就是訪問很慢,一會訪問抛出異常,一會訪問又是正常結果。
導緻接口級故障的原因一般有下面幾種:
- 内部原因程序bug導緻死循環,某個接口導緻數據庫慢查詢,程序邏輯不完善導緻耗盡内存等。
- 外部原因黑客攻擊、促銷或者搶購引入了超出平時幾倍甚至幾十倍的用戶,第三方系統大量請求,第三方系統響應緩慢等。
解決接口級故障的核心思想和異地多活基本類似:優先保證核心業務和優先保證絕大部分用戶。
降級降級指系統将某些業務或者接口的功能降低,可以是隻提供部分功能,也可以是完全停掉所有功能。例如,論壇可以降級為隻能看帖子,不能發帖子;也可以降級為隻能看帖子和評論,不能發評論;而App的日志上傳接口,可以完全停掉一段時間,這段時間内App都不能上傳日志。
降級的核心思想就是丢車保帥,優先保證核心業務。例如,對于論壇來說,90%的流量是看帖子,那我們就優先保證看帖的功能;對于一個App來說,日志上傳接口隻是一個輔助的功能,故障時完全可以停掉。
常見的實現降級的方式有:
- 系統後門降級簡單來說,就是系統預留了後門用于降級操作。例如,系統提供一個降級URL,當訪問這個URL時,就相當于執行降級指令,具體的降級指令通過URL的參數傳入即可。這種方案有一定的安全隐患,所以也會在URL中加入密碼這類安全措施。系統後門降級的方式實現成本低,但主要缺點是如果服務器數量多,需要一台一台去操作,效率比較低,這在故障處理争分奪秒的場景下是比較浪費時間的。
- 獨立降級系統為了解決系統後門降級方式的缺點,将降級操作獨立到一個單獨的系統中,可以實現複雜的權限管理、批量操作等功能。其基本架構如下:
熔斷和降級是兩個比較容易混淆的概念,因為單純從名字上看好像都有禁止某個功能的意思,但其實内在含義是不同的,原因在于降級的目的是應對系統自身的故障,而熔斷的目的是應對依賴的外部系統故障的情況。
假設一個這樣的場景:A服務的X功能依賴B服務的某個接口,當B服務的接口響應很慢的時候,A服務的X功能響應肯定也會被拖慢,進一步導緻A服務的線程都被卡在X功能處理上,此時A服務的其他功能都會被卡住或者響應非常慢。這時就需要熔斷機制了,即:A服務不再請求B服務的這個接口,A服務内部隻要發現是請求B服務的這個接口就立即返回錯誤,從而避免A服務整個被拖慢甚至拖死。
熔斷機制實現的關鍵是需要有一個統一的API調用層,由API調用層來進行采樣或者統計,如果接口調用散落在代碼各處就沒法進行統一處理了。熔斷機制實現的另外一個關鍵是阈值的設計,例如1分鐘内30%的請求響應時間超過1秒就熔斷,這個策略中的“1分鐘”“30%”“1秒”都對最終的熔斷效果有影響。實踐中一般都是先根據分析确定阈值,然後上線觀察效果,再進行調優。
限流降級是從系統功能優先級的角度考慮如何應對故障,而限流則是從用戶訪問壓力的角度來考慮如何應對故障。限流指隻允許系統能夠承受的訪問量進來,超出系統訪問能力的請求将被丢棄。
雖然“丢棄”這個詞聽起來讓人不太舒服,但保證一部分請求能夠正常響應,總比全部請求都不能響應要好得多。限流一般都是系統内實現的,常見的限流方式可以分為兩類:基于請求限流和基于資源限流
- 基于請求限流基于請求限流指從外部訪問的請求角度考慮限流,常見的方式有:限制總量、限制時間量。限制總量的方式是限制某個指标的累積上限,常見的是限制當前系統服務的用戶總量,例如某個直播間限制總用戶數上限為100萬,超過100萬後新的用戶無法進入;某個搶購活動商品數量隻有100個,限制參與搶購的用戶上限為1萬個,1萬以後的用戶直接拒絕。限制時間量指限制一段時間内某個指标的上限,例如,1分鐘内隻允許10000個用戶訪問,每秒請求峰值最高為10萬。無論是限制總量還是限制時間量,共同的特點都是實現簡單,但在實踐中面臨的主要問題是比較難以找到合适的阈值,例如系統設定了1分鐘10000個用戶,但實際上6000個用戶的時候系統就扛不住了;也可能達到1分鐘10000用戶後,其實系統壓力還不大,但此時已經開始丢棄用戶訪問了。即使找到了合适的阈值,基于請求限流還面臨硬件相關的問題。例如一台32核的機器和64核的機器處理能力差别很大,阈值是不同的,可能有的技術人員以為簡單根據硬件指标進行數學運算就可以得出來,實際上這樣是不可行的,64核的機器比32核的機器,業務處理性能并不是2倍的關系,可能是1.5倍,甚至可能是1.1倍。為了找到合理的阈值,通常情況下可以采用性能壓測來确定阈值,但性能壓測也存在覆蓋場景有限的問題,可能出現某個性能壓測沒有覆蓋的功能導緻系統壓力很大;另外一種方式是逐步優化,即:先設定一個阈值然後上線觀察運行情況,發現不合理就調整阈值。基于上述的分析,根據阈值來限制訪問量的方式更多的适應于業務功能比較簡單的系統,例如負載均衡系統、網關系統、搶購系統等。
- 基于資源限流基于請求限流是從系統外部考慮的,而基于資源限流是從系統内部考慮的,即:找到系統内部影響性能的關鍵資源,對其使用上限進行限制。常見的内部資源有:連接數、文件句柄、線程數、請求隊列等。例如,采用Netty來實現服務器,每個進來的請求都先放入一個隊列,業務線程再從隊列讀取請求進行處理,隊列長度最大值為10000,隊列滿了就拒絕後面的請求;也可以根據CPU的負載或者占用率進行限流,當CPU的占用率超過80%的時候就開始拒絕新的請求。基于資源限流相比基于請求限流能夠更加有效地反映當前系統的壓力,但實踐中設計也面臨兩個主要的難點:如何确定關鍵資源,如何确定關鍵資源的阈值。通常情況下,這也是一個逐步調優的過程,即:設計的時候先根據推斷選擇某個關鍵資源和阈值,然後測試驗證,再上線觀察,如果發現不合理,再進行優化。
排隊實際上是限流的一個變種,限流是直接拒絕用戶,排隊是讓用戶等待一段時間,全世界最有名的排隊當屬12306網站排隊了。排隊雖然沒有直接拒絕用戶,但用戶等了很長時間後進入系統,體驗并不一定比限流好。由于排隊需要臨時緩存大量的業務請求,單個系統内部無法緩存這麼多數據,一般情況下,排隊需要用獨立的系統去實現,例如使用Kafka這類消息隊列來緩存用戶請求。下面是1号店的“雙11”秒殺排隊系統架構:
來分析下這個系統:
- 排隊模塊負責接收用戶的搶購請求,将請求以先入先出的方式保存下來。每一個參加秒殺活動的商品保存一個隊列,隊列的大小可以根據參與秒殺的商品數量(或加點餘量)自行定義。
- 調度模塊負責排隊模塊到服務模塊的動态調度,不斷檢查服務模塊,一旦處理能力有空閑,就從排隊隊列頭上把用戶訪問請求調入服務模塊,并負責向服務模塊分發請求。這裡調度模塊扮演一個中介的角色,但不隻是傳遞請求而已,它還擔負着調節系統處理能力的重任。我們可以根據服務模塊的實際處理能力,動态調節向排隊系統拉取請求的速度。
- 服務模塊負責調用真正業務來處理服務,并返回處理結果,調用排隊模塊的接口回寫業務處理結果。
- 登錄接口在流量特别大的時候,可以适當降級,較少參與人數,另外一點是登錄一般有有效期(尤其對于web客戶端),可以适當延長登錄有效期。
- 搶購接口采用隊列方式,無法支撐時,也可以适當限流。另外一點,秒殺一般還會有一個秒殺結果查詢的接口,也可以降級或者減小請求頻次。
- 支付接口,一般第三方支付對接入方有流量限制,可以提前申請擴大限制。支付接口依賴第三方進行熔斷處理,然後做好容錯措施,例如提示用戶10分鐘後來支付
- 支付一般不建議排隊,限流和降級,用戶體驗很不好,還不如前面登錄和搶購就限制,這是有心理學理論支撐的,用戶沒搶到前,如果搶不到他會認為自己運氣不好,但如果用戶搶到了沒法支付,他會覺得自己損失了,會觸發“損失厭惡”心理