1. 程式人生 > >針對高併發系統的解決思路與方案

針對高併發系統的解決思路與方案

總體上:

開濤大神在部落格中說過:在開發高併發系統時有三把利器用來保護系統:快取、降級和限流

1.擴容

根據業務系統的型別,考慮不同的針對在資料庫方面的擴容:

2.快取(特別重要)

快取設定的地方

手段

主要是Redis、CDN、瀏覽器等,其次可能一些問題

2.3可能存在的問題

2.3.1一致性

快取一致性的話,主要可能考慮到以下幾種可能導致一致性問題:

2.3.2快取併發

由於併發時大量請求瞬間湧入,那麼在建立或修改快取時(還沒完成),就可能已經打到DB或老快取上了,造成雪崩或快取一致性問題

解決方案:

鎖機制

2.3.3快取穿透

快取穿透是指查詢一個一定不存在的資料,由於快取是不命中時被動寫的,如果從儲存層查不到資料則不寫入快取,這將導致這個

不存在的資料每次請求都要到儲存層去查詢,失去了快取的意義。在流量大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。

解決方案:

最常見的則是採用布隆過濾器,將所有可能存在的資料雜湊到一個足夠大的bitmap中,一個一定不存在的資料會被這個bitmap攔截掉,從而避免了對底層儲存系統的查詢壓力。另外也有一個更為簡單粗暴的方法(我們採用的就是這種),如果一個查詢返回的資料為空(不管是數 據不存在,還是系統故障),我們仍然把這個空結果進行快取,但它的過期時間會很短,最長不超過五分鐘。

2.3.4雪崩

快取雪崩是指在我們設定快取時採用了相同的過期時間,導致快取在某一時刻同時失效,請求全部轉發到

DBDB瞬時壓力過重雪崩

解決方案

大多數系統設計者考慮用加鎖或者佇列的方式保證快取的單線程(程序)寫,從而避免失效時大量的併發請求落到底層儲存系統上。這裡分享一個簡單方案就時講快取失效時間分散開,比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鐘隨機,這樣每一個快取的過期時間的重複率就會降低,就很難引發集體失效的事件。

3.訊息佇列

做到了大量請求非同步與解耦,比如下單與簡訊驗證碼請求 的處理,及早返回處理。

比如遠端呼叫較慢,可以用訊息佇列攢一堆去完成呼叫。

基於訊息的模型,等通知即可,而不是處理(那是RPC),以及索引庫更新,只是一個通知,而不是強完成(重量級),只是通知而不是職責所在(比如上游系統對下的通知)。

僅僅最終一致性。

需求場景

及一些非核心業務或者多個物件、資料量小時,而

相比RPC框架(dubbo等),RPC場景是需要 強一致性、對延遲敏感、注重結果立馬返回、重量級

4.應用拆分

SOA 微服務等

個人看法,原則是:

1.業務是否適合拆分,拆分後耦合度不能高

2.是否需要拆分,拆分是為了高併發高可用,是否解決了高併發熱點問題?

5.應用限流

比如股票系統中,收盤後,你有幾百萬資料需要入庫了,如果不做限流,瞬間將會把頻寬全部打滿,其它服務暫時GG或者主從延遲很大等等問題。

限流的演算法

常見的限流演算法有:計數器、漏桶和令牌桶演算法。

計數器

計數器是最簡單粗暴的演算法。比如某個服務最多隻能每秒鐘處理100個請求。我們可以設定一個1秒鐘的滑動視窗,視窗中有10個格子,每個格子100毫秒,每100毫秒移動一次,每次移動都需要記錄當前服務請求的次數。記憶體中需要儲存10次的次數。可以用資料結構LinkedList來實現。格子每次移動的時候判斷一次,當前訪問次數和LinkedList中最後一個相差是否超過100,如果超過就需要限流了。

很明顯,當滑動視窗的格子劃分的越多,那麼滑動視窗的滾動就越平滑,限流的統計就會越精確。

漏桶演算法

漏桶演算法即leaky bucket是一種非常常用的限流演算法,可以用來實現流量整形(Traffic Shaping)和流量控制(Traffic Policing)。貼了一張維基百科上示意圖幫助大家理解:

漏桶演算法的主要概念如下:

  • 一個固定容量的漏桶,按照常量固定速率流出水滴;
  • 如果桶是空的,則不需流出水滴;
  • 可以以任意速率流入水滴到漏桶;
  • 如果流入水滴超出了桶的容量,則流入的水滴溢位了(被丟棄),而漏桶容量是不變的。

漏桶演算法比較好實現,在單機系統中可以使用佇列來實現(.NetTPL DataFlow可以較好的處理類似的問題,你可以在這裡找到相關的介紹),在分散式環境中訊息中介軟體或者Redis都是可選的方案。

令牌桶演算法

令牌桶演算法是一個存放固定容量令牌(token)的桶,按照固定速率往桶裡新增令牌。令牌桶演算法基本可以用下面的幾個概念來描述:

  • 令牌將按照固定的速率被放入令牌桶中。比如每秒放10個。
  • 桶中最多存放b個令牌,當桶滿時,新新增的令牌被丟棄或拒絕。
  • 當一個n個位元組大小的資料包到達,將從桶中刪除n個令牌,接著資料包被髮送到網路上。
  • 如果桶中的令牌不足n個,則不會刪除令牌,且該資料包將被限流(要麼丟棄,要麼緩衝區等待)。

如下圖:

令牌演算法是根據放令牌的速率去控制輸出的速率,也就是上圖的to network的速率。to network我們可以理解為訊息的處理程式,執行某段業務或者呼叫某個RPC

應用級限流

限流總併發/連線/請求數

對於一個應用系統來說一定會有極限併發/請求數,即總有一個TPS/QPS閥值,如果超了閥值則系統就會不響應使用者請求或響應的非常慢,因此我們最好進行過載保護,防止大量請求湧入擊垮系統。

如果使用過Tomcat,其Connector其中一種配置有如下幾個引數:

acceptCount:如果Tomcat的執行緒都忙於響應,新來的連線會進入佇列排隊,如果超出排隊大小,則拒絕連線;

maxConnections:瞬時最大連線數,超出的會排隊等待;

maxThreadsTomcat能啟動用來處理請求的最大執行緒數,如果請求處理量一直遠遠大於最大執行緒數則可能會僵死。

分散式限流

分散式限流最關鍵的是要將限流服務做成原子化,而解決方案可以使使用redis+lua或者nginx+lua技術進行實現,通過這兩種技術可以實現的高併發和高效能。

使用redis+lua實現時間窗內某個介面的請求數限流,實現了該功能後可以改造為限流總併發/請求數和限制總資源數。Lua本身就是一種程式語言,也可以使用它實現複雜的令牌桶或漏桶演算法。

6.服務降級與熔斷

1.服務降級

當伺服器壓力劇增的情況下,根據當前業務情況及流量對一些服務和頁面有策略的降級,以此釋放伺服器資源以保證核心任務的正常執行

降級後的處理可以設定一些預設的頁面或返回,如:

  • 服務介面拒絕服務:頁面能訪問,但是新增刪除提示伺服器繁忙。頁面內容也可在VarnishCDN內獲取。
  • 頁面拒絕服務:頁面提示由於服務繁忙此服務暫停。跳轉到varnishnginx的一個靜態頁面。
  • 延遲持久化:頁面訪問照常,但是涉及記錄變更,會提示稍晚能看到結果,將資料記錄到非同步佇列或log,服務恢復後執行。
  • 隨機拒絕服務:服務介面隨機拒絕服務,讓使用者重試,目前較少有人採用。因為使用者體驗不佳。

2.服務熔斷

服務熔斷一般是指軟體系統中,由於某些原因使得服務出現了過載現象,為防止造成整個系統故障,從而採用的一種保護措施,所以很多地方把熔斷亦稱為過載保護。

3.對比區別和共性

類似性的:

  1. 目的很一致,都是從可用性可靠性著想,為防止系統的整體緩慢甚至崩潰,採用的技術手段;
  2. 最終表現類似,對於兩者來說,最終讓使用者體驗到的是某些功能暫時不可達或不可用;
  3. 粒度一般都是服務級別,當然,業界也有不少更細粒度的做法,比如做到資料持久層(允許查詢,不允許增刪改);
  4. 自治性要求很高,熔斷模式一般都是服務基於策略的自動觸發,降級雖說可人工干預,但在微服務架構下,完全靠人顯然不可能,開關預置、配置中心都是必要手段;

而兩者的區別也是明顯的:

  1. 觸發原因不太一樣,服務熔斷一般是某個服務(下游服務)故障引起,而服務降級一般是從整體負荷考慮;
  2. 管理目標的層次不太一樣,熔斷其實是一個框架級的處理,每個微服務都需要(無層級之分),而降級一般需要對業務有層級之分(比如降級一般是從最外圍服務開始)
  3. 實現方式不太一樣

7.資料庫分庫、分表

出現以下情況時開始考慮

以下為資料庫優化,參考之前寫的MySQL優化部分。

8.高可用-方案