1. 程式人生 > >高併發利器之限流

高併發利器之限流

限流實踐

高併發系統三把利器:快取、降級和限流
限流大家多少都有所瞭解,就是限制當前請求流量、數量,避免系統被蜂擁而至的流量瞬間擊垮,剛好最近手上業務系統做了限流,順便整理出來。

我們的業務場景

  • 雲變數和雲列表,使用者線上使用的程式變數和陣列,一些作品執行人數過多或者作者濫用(for迴圈中修改變數或陣列),大量請求訊息落到kafka,而我們消費機器能力有限,造成訊息堵塞。直接的影響就是業務不能正常使用,頻繁fullgc。
  • 無論什麼系統,業務上的優化往往更能立竿見影,且成本較低。這裡不做詳述,比較偏業務,主要思路是儘量減少落入kafka中的無效訊息,合併重複訊息。

限流方式

  • 限制總併發數(比如資料庫連線池、執行緒池)
  • 限制瞬時併發數(如nginx的limit_conn模組,用來限制瞬時併發連線數)
  • 限制時間視窗內的平均速率(如Guava的RateLimiter、nginx的limit_req模組,限制每秒的平均速率)
  • 限制遠端介面呼叫速率
  • 限制MQ的消費速率。
  • 可以根據網路連線數、網路流量、CPU或記憶體負載等來限流

限流演算法

Google大法了相關資料,總結常用的限流演算法如下:

  • 計數器
    • 通過一個計數器來記錄一定時間內某個介面的訪問數量,超過閾值則不允許繼續訪問,或者後續的請求放入佇列,計數器到下一個時間段清零,這裡缺點是,假設設定每3秒100,第3秒99個請求數,然後到了下一個時間段,計數被清零,第4秒又來了99個請求,實際上3-4兩秒時間超出了限制的100個請求,但這種情況較少。所有有了下面的時間視窗讓它更平滑
  • 時間視窗
    • 建立時間視窗,100個格子,每個格子為100毫秒,每100毫秒滑動一格,判斷當前執行次數和前一次執行次數差是否超出閾值,沒有超過記錄當前次數,超過證明超出了限流次數,視窗粒度越細則越平滑
  • 漏桶演算法
    • 把請求固定丟入‘漏桶’(佇列),執行緒以固定頻率進行消費處理,這樣對應用來說消費是固定的壓力,超出漏桶容量的請求將被丟棄
  • 令牌桶限流
    • 基本思路為每秒生成固定數量令牌置入桶內,真正業務操作需要從桶中獲取令牌才能執行,沒有令牌拋棄請求,相對漏桶方式,更靈活,可以設定令牌生成的速度,獲取令牌的速度和個數。允許一些突發的流量(如果當前機器可以支撐的流量(令牌數)較多則可以更多的獲取令牌進行執行)

可選方案

  • Guava 提供的令牌桶實現RateLimiter,但只支援單機的限流
  • nginx 連線數限流模組ngx_http_limit_conn_module和漏桶演算法實現的請求限流模組ngx_http_limit_req_module
  • redis+lua 指令碼實現
  • ratelimitj開源框架(提供了基於redis、hazelcast、inmemory版本的實現方案)
  • 阿里巴巴開源的 Sentinel(限流、熔斷降級、塑形、系統負載保護)

落地方案

  • 一般我們這裡都是叢集環境,現在最早已經使用了guava RateLimiter,這裡系統也已經使用了redis,所以選擇了redis進行了實現

  • redis+lua

    • redis可以呼叫lua指令碼,lua程式碼將會被當成一個命令去執行,lua指令碼執行完成redis才會繼續執行其他命令,這樣保證了併發環境下可以正確的進行執行限流判斷操作。

    • lua指令碼(基本為記錄對應請求對應key計數是否超出限制數量)

      -- ratelimit 物件
      RateLimit = { acquire = false, key = nil, limit = nil, count = 0 }
      function RateLimit:new(o, acquire, key, limit, count)
          o = o or {}
          setmetatable(o, self)
          self.__index = self
          self.acquire = acquire or false
          self.key = key or nil
          self.limit = limit or 0
          self.count = count or 0;
          return o
      end
      
      -- 獲取執行許可權,超出流量控制則拒絕
      local key = "rate.limit:" .. KEYS[1]
      local limit = tonumber(KEYS[2])
      local expire_time = KEYS[3]
      -- 判斷key是否存在
      local is_exists = redis.call("EXTSTS", key);
      -- 如果key存在則呼叫inscr操作key
      -- 如果值大於limit數量返回0,否則返回1
      if is_exists == 1 then
          local count = redis.call("INSCR", key)
          if redis.call("INSCR", key) > limit then
              return Rectangle:new(nil, false, key, limit, count)
          else
              return Rectangle:new(nil, true, key, limit, count)
          end
          -- 如果不存在key,則呼叫set命令設定key的值為1
          -- 設定key過期時間
      else
          redis.call("SET", key, 1)
          redis.call("EXPIRE", key, expire_time)
          return Rectangle:new(nil, true, key, limit, 1)
      end
      
    • 封裝java介面(這裡加了log,可以簡單監控下當前限流情況,對異常請求可以根據業務需要做一些人工介入)

      package com.netease.edu.scratch.cloud.limit;
      
      import java.util.Map;
      import java.util.concurrent.TimeUnit;
      
      /**
       * 限流
       *
       * @author zhangchanglu<[email protected]  corp.netease.com>
       * @since 2018/08/08 22:08.
       */
      public interface RateLimit {
          /**
           * 獲取執行許可權
           *
           * @param key    業務key
           * @param limit  限流大小
           * @param expire 限流重置時間
           * @return 流量情況
           */
          RateLimitVO acquire(String key, int limit, long expire, TimeUnit timeUnit);
      
          /**
           * 監控當前key流量情況
           *
           * @param key 業務key
           * @param limit 限制流量數
           * @return 流量情況
           */
          RateLimitVO log(String key, int limit);
          /**
           * 監控當前所有key流量情況
           * @param limit 限制流量數
           * @return 流量情況
           */
          Map<String,RateLimitVO> logAll(int limit);
      
          /**
           * 獲取儲存redis key
           * @param key 業務key
           * @return 業務key
           */
          String getKey(String key);
      
      }
      
      
    • 主要實現方法(這裡用的spring的redisTemplate,呼叫傳遞value沒辦法正常獲取,所以所有引數都是用了key進行了傳遞

      /**
           * 獲取執行許可權
           *
           * @param key    業務key
           * @param limit  限流大小
           * @param expire 限流重置時間
           * @return 是否在允許執行
           */
          public RateLimitVO acquire(String key, int limit, long expire, TimeUnit timeUnit) {
              addLog(key);
              key = getKey(key);
              RedisScript<Long> redisScript = new DefaultRedisScript<>(scriptStr, Long.class);
              Long count = redisTemplate.execute(redisScript, Lists.newArrayList(key, String.valueOf(limit), String.valueOf(timeUnit.toSeconds(expire))));
              RateLimitVO rateLimitVO = rateLimitHelper.getRateLimitVO(key, limit, count);
              log.info("當前流量情況:" + rateLimitVO);
              return rateLimitVO;
          }
      
  • 這裡我們用的是我們的rds,但跟dba溝通後,為了安全,限制了eval命令的呼叫,而redis呼叫lua指令碼是依賴eval命令的,所以這裡用使用程式碼進行了實現,思路一樣的只是藉助了分散式鎖。

存在的問題

  • 我們業務上也是粗略進行限流,並沒有精確進行限制流量,基本上避免大量無效和惡意請求即可
  • 這裡的限流其實對於整個系統來講,系統已經接受了大量的連線,依然會有壓力存在,只是減少了kafka、資料庫的壓力 ,所以對限流來說應該越靠前越好,使用nginx來進行限流應該能更好的保證系統的穩定性,這也是我們後續要考慮做的。

相關推薦

併發利器

限流實踐 高併發系統三把利器:快取、降級和限流 限流大家多少都有所瞭解,就是限制當前請求流量、數量,避免系統被蜂擁而至的流量瞬間擊垮,剛好最近手上業務系統做了限流,順便整理出來。 我們的業務場景 雲變數和雲列表,使用者線上使用的程式變數和陣列,一些作品執行人數過多或者作

聊聊併發系統特技(二)(轉)

上一篇《聊聊高併發系統限流特技-1》講了限流演算法、應用級限流、分散式限流;本篇將介紹接入層限流實現。 接入層限流 接入層通常指請求流量的入口,該層的主要目的有:負載均衡、非法請求過濾、請求聚合、快取、降級、限流、 A/B 測試、服務質量監控

併發系統特技

在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。快取的目的是提升系統訪問速度和增大系統能處理的容量,可謂是抗高併發流量的銀彈;而降級是當服務出問題或者影響到核心流程的效能則需要暫時遮蔽掉,待高峰或者問題解決後再開啟;而有些場景並不能用快取和降級來解決,比如稀缺資源(秒殺、搶購)、寫服務(如評論、

聊聊併發系統特技

在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。快取的目的是提升系統訪問速度和增大系統能處理的容量,可謂是抗高併發流量的銀彈;而降級是當服務出問題或者影響到核心流程的效能則需要暫時遮蔽掉,待高峰或者問題解決後再開啟;而有些場景並不能用快取和降級來解決,比如

併發系統特技:有了它,京東6.18如虎添翼!

轉載 ------ 2016-06-24 張開濤相關文章 在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。快取的目的是提升系統訪問速度和增大系統能處理的容量,可謂是抗高併發流量的銀彈;而降級是當服務出問題或者影響到核心流程的效能則需要暫時遮蔽掉,待高峰或者問

Java併發系統的策略

概要在大資料量高併發訪問時,經常會出現服務或介面面對暴漲的請求而不可用的情況,甚至引發連鎖反映導致整個系統崩潰。此時你需要使用的技術手段之一就是限流,當請求達到一定的併發數或速率,就進行等待、排隊、降級、拒絕服務等。在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。快取快取比較好理解,在大型高併發系

談談併發系統的

開濤大神在部落格中說過:在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。本文結合作者的一些經驗介紹限流的相關概念、演算法和常規的實現方式。快取快取比較好理解,在大型高併發系統中,如果沒有快取資料庫將分分鐘被爆,系統也會瞬間癱瘓。使用快取不單單能夠提升系統訪問速度、

可用架構降級

 一、服務等級協議  我們常說的N個9,就是對SLA的一個描述。 SLA全稱是ServiceLevel Agreement,翻譯為服務水平協議,也稱服務等級協議,它表明了公有云提供服務的等級以及質量。 例如阿里雲對外承諾的就是一個服務週期內叢集服務可用性不低於99.99%,如

併發處理介面

最近開發的搶購活動上線後發現了兩個比較明顯的問題,其一:活動一開始,介面訪問量劇增;其二:黑名單中增加了一大批黑名單使用者(或者說IP),這其中就包含了一些惡意使用者或機器人刷介面。 針對一些高併發的介面,限流是處理高併發的幾大利劍之一。一方面,限流可以防止介面被刷,造成不

並發系統特技

有一種 mic jedis .net cep 防止 方法 框架 ise 在開發高並發系統時有三把利器用來保護系統:緩存、降級和限流。緩存的目的是提升系統訪問速度和增大系統能處理的容量,可謂是抗高並發流量的銀彈;而降級是當服務出問題或者影響到核心流程的性能則需要暫時屏蔽掉,待

聊聊併發系統降級特技(轉)

在開發高併發系統時有三把利器用來保護系統:快取、降級和限流。之前已經有一些文章介紹過快取和限流了。本文將詳細聊聊降級。當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的效能時,仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵資料進行自動降級,也

Java併發程式設計synchronized關鍵字(二)

上一篇文章講了synchronized的部分關鍵要點,詳見:Java高併發程式設計之synchronized關鍵字(一) 本篇文章接著講synchronized的其他關鍵點。 在使用synchronized關鍵字的時候,不要以字串常量作為鎖定物件。看下面的例子: public class

Java併發程式設計synchronized關鍵字(一)

首先看一段簡單的程式碼: public class T001 { private int count = 0; private Object o = new Object(); public void m() { //任何執行緒要執行下面這段程式碼

您的快遞(併發伺服器poll和epoll)請簽收

  前言   之前已經介紹過select函式,請參考這篇部落格:https://www.cnblogs.com/liudw-0215/p/9661583.html,原理都是類似的,有時間先閱讀下那篇部落格,以便於理解這篇部落格。   一、poll函式   1、函式說明   原型:int poll(st

實戰java併發程式設計CountDownLatch原始碼分析

首先看第一個! CountDownLatch 使用場景 CountDownLatch類是常見的併發同步控制類,適用於某一執行緒的執行在其他多個執行緒執行完成之後,比如火箭發射前需要各項指標檢查,只有當各項指標檢查完才能發射,再比如解析多個excel文件,只有當

併發程式設計併發場景:秒殺(無鎖、排他鎖、樂觀鎖、redis快取的逐步演變)

環境: jdk1.8;spring boot2.0.2;Maven3.3 摘要說明: 在實際開發過程中往往會出現許多高併發場場景,秒殺,強紅包,搶優惠卷等; 其中: 秒殺場景的特點就是單位時間湧入使用者量極大,商品數少,且要保證不可超量銷售; 秒殺產品的本質就是減

實戰java併發程式設計ReentrantReadWriteLoc原始碼分析

前面分析了併發工具類CountDownLatch和CyclicBarrier,本文分享分析比較重要的ReentrantReadWriteLock。 使用場景 以前的同步方式需要對讀、寫操作進行同步,讀讀之間,讀寫之間,寫寫之間等;工程師們發現讀讀之間並不會影響資

spring cloud gateway

轉載請標明出處: www.fangzhipeng.com 本文出自方誌朋的部落格 在高併發的系統中,往往需要在系統中做限流,一方面是為了防止大量的請求使伺服器過載,導致服務不可用,另一方面是為了防止網路攻擊。 常見的限流方式,比如Hystrix適用執行緒池隔離,超過執行緒池的負載,走熔斷的邏輯。

併發網站解決策略

系統在正式上線後必將會面對大量使用者訪問,面對各種層級的高併發請求,因此我們會採用高效能的伺服器、高效能的資料庫、高效率的程式語言、高效能的Web容器等。但是這幾個方面,還無法從根本解決大型網站面臨的高負載和高併發問題。因此我們必須對此做出相應的策略和技術解決方案。 1. 負載均衡

Java併發程式設計第一階段,多執行緒基礎深入淺出

汪文君高併發程式設計第一階段01講-課程大綱及主要內容介紹 汪文君高併發程式設計第一階段02講-簡單介紹什麼是執行緒 汪文君高併發程式設計第一階段03講-建立並啟動執行緒 汪文君高併發程式設計第一階段04講-執行緒生命週期以及start方法原始碼剖析 汪文君高併發程式設計第