大流量和高併發應對手段總結

大流量和高併發的常規應對手段

擴容、動靜分離、緩存、服務降級和限流。

限流的常用算法和實踐思路

  1. 目前主流的算法主要有三種:令牌桶算法、漏桶算法和計數器算法。
  2. 令牌桶算法:主要限制流量的流入速率,允許出現一定程度的突發流量。Nginx的限流模塊就是使用的這種算法實現的。
    • 每秒會有r個令牌按照固定速率放入桶中。
    • 桶的容量是固定不變的,如果桶滿了再放入令牌,則溢出。
    • 若桶中的可用令牌不足,則改請求會被進行限流處理(被拋棄或緩存)。
  3. 漏桶算法:主要限制流量的流出速率,並且流出速率是固定不變的
    • 可以以任意速率向桶中流入水滴。
    • 桶的容量是固定不變的,如果桶滿了則溢出。
    • 按照固定的速率從桶中流出水滴。
  4. Google的Guava也實現了基於令牌桶算法那樣的平均速率限流,RateLimiter抽象類。
  5. Nginx可以使用限流模塊在接入層實現令桶牌算法限流,
    • limit_zone 定義每個IP的session空間大小。
    • limit_zeq_zone定義每個IP每秒允許發起的請求數。
    • limit_conn 定義每個IP能夠發起的併發連接數。
    • limit_req 等待處理的請求隊列數量。
  6. 生產環境中的商品搶購可以使用計數器算法,具體不同的sku限流規則配置在配置中心內,支持動態更改。可搶購次數的扣減操作,既可以用redis,也可以用JVM。如果是集羣並且選擇用JVM,則要根據總併發數量除以集羣數量,得出單臺機器的併發數。(比如總併發數5000,集羣機器10臺,則每臺機器的併發爲5000/10=500)。

搶購商品高併發讀需求

對於一件搶購商品的流量來說,因爲key是同一個,所以流量必然會都引入到同一個redis緩存節點中,這時就容易出現單點故障。因此有下面兩種解決方式:
1. 在每個master節點都掛slave從節點,當主節點掛了可以自動頂上。
2. 多級Cache方案,多用LocalCache來過濾掉一部分流量。
- 本地緩存一般只緩存一些熱點商品數據,緩存內容一般是商品詳情和商品庫存。
- 本地緩存跟分佈式緩存的同步一般有兩種方式:一種是定時主動拉取更新策略。這種會存在一定時間的不一致,要視業務情況而定,例如庫存,暫時的不一致導致超賣,單到真正下單的時候還會再進行庫存的判斷,所以影響較小,可以接受。這種方式要注意關掉緩存的定時失效,防止當用戶流量突然過大,都到分佈式緩存中拉取數據;第二種方式是每次商品更新,都發佈一個消息,訂閱此消息的節點監聽到後再更新本地緩存的內容。

實時熱點自動發現方案

可以將交易系統產生的相關數據,以及在上游系統中埋點上報的相關數據異步寫入日誌系統中,然後通過實時熱點自動發現平臺對收集到的日誌數據做調用次數統計和熱點分析。數據符合熱點條件後,就立即通知交易系統做好熱點保護。

redis使用watch命令實現高併發搶購需求

  1. 一般高併發這裏,不用悲觀鎖,會迅速增加系統資源;而使用隊列,容易造成請求堆積,內存效果過快。所以一般使用樂觀鎖,可以用redis的watch命令實現。
  2. watch命令會監視給定的key,當exec時,如果監視的key從調用watch後發生過變化,則事務會失敗。注意watch的可以是對整個連接有效的,事務也一樣。如果連接斷開,監視和事務都會被自動清除。當然exec,discard,unwatch命令都會清除連接中的所有監視。

緩存雪崩,緩存穿透,緩存併發,緩存預熱,緩存算法

  1. 緩存雪崩:可能是因爲數據未加載到緩存中,或者緩存同一時間大面積的失效,從而導致所有請求都去查數據庫,導致數據庫CPU和內存負載過高,甚至宕機。解決思路:
    • 加鎖計數(即限制併發的數量,可以用semphore)或者起一定數量的隊列來避免緩存失效時大量請求併發到數據庫。但這種方式會降低吞吐量。
    • 分析用戶行爲,然後失效時間均勻分佈。或者在失效時間的基礎上再加1~5分鐘的隨機數。
    • 如果是某臺緩存服務器宕機,則考慮做主備。
  2. 緩存穿透:指用戶查詢數據,在數據庫沒有,自然在緩存中也不會有。這樣就導致用戶查詢的時候,在緩存中找不到,每次都要去數據庫中查詢。解決思路:
    • 如果查詢數據庫也爲空,直接設置一個默認值存放到緩存,這樣第二次到緩衝中獲取就有值了,而不會繼續訪問數據庫。設置一個過期時間或者當有值的時候將緩存中的值替換掉即可。
    • 可以給key設置一些格式規則,然後查詢之前先過濾掉不符合規則的Key。
  3. 緩存併發:如果網站併發訪問高,一個緩存如果失效,可能出現多個進程同時查詢DB,同時設置緩存的情況,如果併發確實很大,這也可能造成DB壓力過大,還有緩存頻繁更新的問題。解決思路:
    • 對緩存查詢加鎖,如果KEY不存在,就加鎖,然後查DB入緩存,然後解鎖;其他進程如果發現有鎖就等待,然後等解鎖後返回數據或者進入DB查詢。
  4. 緩存預熱:目的就是在系統上線前,將數據加載到緩存中。解決思路:
    • 數據量不大的話,在系統啓動的時候直接加載。
    • 自己寫個簡單的緩存預熱程序。
  5. 緩存算法:
    • FIFO算法:First in First out,先進先出。原則:一個數據最先進入緩存中,則應該最早淘汰掉。也就是說,當緩存滿的時候,應當把最先進入緩存的數據給淘汰掉。
    • LFU算法:Least Frequently Used,最不經常使用算法。
    • LRU算法:Least Recently Used,近期最少使用算法。
    • LRU和LFU的區別。LFU算法是根據在一段時間裏數據項被使用的次數選擇出最少使用的數據項,即根據使用次數的差異來決定。而LRU是根據使用時間的差異來決定的。

協程(纖程)Fiber

  1. 協程概念:一種用戶態的輕量級線程,其實就是單線程,指定執行整個函數中到一部分然後就先出去執行別的,等條件滿足時,協程下次更新幀到了再繼續往下執行。優點是無需線程上下文切換的開銷,充分開發了單CPU的能力,資源佔用低,適合高併發I/O。缺點也很明顯,就是沒辦法利用多CPU的優勢。
  2. 框架:Quasar,調度器使用ForkJoinPool來調度這些fiber。Fiber調度器FiberScheduler是一個高效的、work-stealing、多線程的調度器。
  3. 場景:服務A平時需要調用其他服務,但其他服務在併發高的時候延遲很嚴重。
    • 一開始可以用httpClient連接池+線程池來處理,但如果調用服務的時候延遲太高或者超時,則會導致服務A的吞吐量會特別差。原因主要是一般一個鏈接由一個線程來處理,是阻塞的,所以在線程池數有限的情況下,吞吐量肯定上不去。並且當所有線程都I/O阻塞的時候,會很浪費CPU資源,並且CPU會一直做無用的上下文切換。
    • 這時候可以考慮協程來替換。
  4. 參考文章:http://blog.csdn.net/qq910894904/article/details/41699541
    http://blog.csdn.net/hj7jay/article/details/51980038
    http://blog.csdn.net/zdy0_2004/article/details/51323583

ForkJoinPool線程池

  1. Fork/Join框架是Java7提供了的一個用於並行執行任務的框架,是一個把大任務分割成若干個小任務,最終彙總每個小任務結果後得到大任務結果的框架。
  2. 工作竊取(work-stealing)算法是Fork/Join框架最重要的特性。一般一個線程會對應一個任務隊列,當處理較快的線程處理完自己的任務之後,就會竊取另外一個處理比較慢的線程對應的任務,這時候會存在兩個線程同時處理一個隊列的情況,所以任務隊列一般使用雙端隊列,被竊取任務線程永遠從雙端隊列的頭部拿任務執行,而竊取任務的線程永遠從雙端隊列的尾部拿任務執行。優點是充分利用線程進行並行計算,並減少了線程間的競爭。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章