高併發系統設計思考筆記

一、性能度量的指標

如何衡量系統接口的響應時間?

  • 平均值
    平均值是把統計時間段內所有請求的響應時間數據相加,再除以總請求數。平均值的敏感度差

  • 最大值
    統計時間段內所有請求響應時間最長的值,最大值過於敏感

  • 分位值
    把統計時間段內請求的響應時間從小到大排序,假如一共有100個請求,那麼排在第90位的響應時間就是90分位值。一般面向用戶的線上系統,看 tp999 或者 tp9999 的響應時間。(99.9%的請求的響應時間在 x 毫秒以內)
    分位值排除了偶發極慢請求對於數據的影響,能夠很好地反應這段時間的性能情況,分位值越大,對於慢請求的影響就越敏感。

二、高併發性能優化

要想提高qps(query per seconds),先看示例:假設一次請求任務耗時100ms,一個線程1秒鐘能處理10個請求任務。qps=(處理線程數)/(單次任務響應時間ms)/1000。由於單次任務的響應時間是以毫秒計,因此除以1000轉化爲秒。

  1. 提高處理線程數
    提高處理線程數可以提升 qps,但是一個系統的線程數不可能無限地增加。它受限於阿姆達爾定律(Amdahl’s law)。這可以通過壓測方式知道系統的核心線程數到多少時出現性能瓶頸。

  2. 降低單次任務的響應時間
    要完成一次請求執行完所有的業務邏輯的耗時,就是響應時間。而要降低響應時間,這主要靠各種優化,比如:引入緩存、減少系統間的RPC調用、優化代碼(減少加鎖次數、優化算法)等。這就需要對系統的業務邏輯比較熟悉。

三、高可用

  1. 核心系統一般需要保證4個9的可用性(年故障時間52分鐘)。

  2. 發現故障
    業務指標監控打點(業務邏輯失敗)、系統指標監控打點(緩存、數據庫訪問異常)。節點之間則是通過“heartbeat”心跳包檢測異常

  3. 處理故障
    對於業務系統而言,一般是無狀態的節點,可以無限地擴容。在引入灰度發佈和彈性伸縮後,可應對大部分的故障場景。業務系統依賴的底層存儲而言,是有狀態的節點,當QPS突增時,業務系統機器可藉助彈性伸縮無限地擴容,但是當底層存儲扛不住時,擴容就比較困難了(可能需要遷移數據、重新部署一套存儲……)此時可採取的手段有:限流、降級、調整超時時間的配置。
    對於業務系統之間的 RPC 調用而言,一般會有 thrift 線程池,客戶端調用服務端時,會有一個超時時間,若超時時間設置得不合理(比如默認值30秒),當調用的下游服務出現慢查詢時,這些慢請求會佔用客戶端 worker 線程,從而導致調用方沒有 worker 線程來處理其他請求了,而如果設置合理的超時時間(比如200ms),那麼 200ms 之後請求超時,客戶端 worker 線程就釋放了,從而能夠處理其他請求。
    總結一下:當出現QPS流量突增時,業務系統開始出現一些超時請求的故障了,第一件事就是先擴容。如果擴容達到了底層存儲再也扛不住時(默認下游服務能扛住流量。在真實的線上系統中,如果上游系統擴容了能扛住高QPS但是調用的下游服務有可能扛不住高QPS,就需要評估是否保護下游?),這時候就要開始限流、降級了。與此同時,聯繫DBA申請更多的存儲資源。如果擴容未生效,這時候看是否能夠適當調大一點超時時間(有風險,因此流量是不斷增長的,更大的超時時間可能會佔用更多的處理線程,從而導致其他請求無線程可用),需要確保有足夠的可用線程且超時時間是合理的。因此,更保險的做法是限流、降級。

  4. failover
    業務系統(無狀態)的 failover 就是根據配置的 qps 或者 cpu 使用率等指標自動觸發彈性擴容。有狀態的系統的 failover 比較複雜,因此有狀態的節點一般有 master 和 slave 之分。通過心跳檢測到 master 宕機後,需要發起選主,選主需要保證其餘節點一致認可 master(需要一致性算法 raft/paxos),然後由新的 master 來負責數據同步並恢復故障。

  5. 路由與負載均衡
    服務之間通過 RPC 調用時,需要選擇一臺合適的下游機器將請求發送過去。如何選擇?
    路由是從下游節點集合中篩選出符合要求的一部分節點;負載均衡是從符合要求的節點中選擇出一個節點。通常是先執行路由,再執行負載均衡,路由的輸出是負載均衡的輸入。
    路由解決:如何從下游選擇出一組機器,作爲請求候選機器。常見的路由策略有:同機房優先、同地域/城市優先。
    負載均衡解決:從候選集機器中,選擇一臺,將請求發送過去。常見的負載均衡策略有:按權隨機負載(每個機器有個權重,權重高的分配到的請求多,權重一樣時,則隨機)、RoundRobin負載。

四、讀寫分離與分庫分表

讀寫分離

訪問量變大的情況下,寫請求走主庫,從請求走讀庫。

  1. 負載均衡,讀能力水平擴展
  2. 避免單點故障
  3. 需要對SQL進行判斷,如果是 select 走從庫,是 update/insert/delete 走主庫
  4. 主從同步延遲
  5. 事務問題,如果一個事務中同時包含了讀和寫,那麼讀不從走從庫,所有操作都得走主庫,避免跨庫事務
  6. 高可用性,新增slave節點,需要及時被 client 感知,將讀請求發送到此 slave;slave宕機,需要檢測出來並隔離,後續讀請求轉發到其他正常的 slave
  7. master 宕機,需要主從切換,將某個 slave 提升爲master,寫請求走新的 master

分庫分表

數據量變大的情況下,將單表的數據做拆分。

  1. 負載均衡,寫能力水平擴展
  2. 單表數據量太大,水平分區(sharding),將一張表的數據分配給N個表維護,有三種分法:只分表、只分庫、分庫分表。示例如下:
  3. SQL的增刪改需要分發到不同的DB上執行。假設有 user 大表,拆分成四張 user 子表:user_1、user_2、user_3、user_4

insert into user(id,name) values (1,"a"),(2,"b"), (3,"c"),(4,"d")

需要改寫成:

insert into user_1(id,name) values (1,"a")
insert into user_2(id,name) values (2,"b")
insert into user_3(id,name) values (3,"c")
insert into user_0(id,name) values  (4,"d")

經過分庫分表後,原來單表下執行的SQL需要:SQL解析、SQL路由、SQL改寫、SQL執行、結果集合並,最終得到執行結果。
4. 分佈式事務
分庫分表後不可避免地遇到跨庫事務的問題,一般使用“柔性事務”來解決。
5. 分佈式id
不能再使用mysql的自增主鍵,需要分佈式唯一ID生成器,參考:Snowflake

數據庫中間件

  1. 數據庫代理
    在DB和應用程序之間單獨部署數據庫代理。對於應用而言通過一個普通的數據源(c3p0、druid、dbcp等)與代理服務器建立連接,然後由代理服務訪問DB

  2. 數據源代理

五、NoSQL 數據庫

HBase

淘寶 Tair

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章