隊列+流量整形+限流策略


一.為什么要流量整形(削峰填谷)

流量沖擊(高並發情況下帶來的突發流量):

上游調用方(push)不限速,很可能會把下游壓垮   eg:上游發起下單操作,下游完成秒殺業務邏輯(庫存檢查,庫存枷鎖,余額檢查,余額枷鎖,訂單生成,余額扣減,庫存扣減,生成流水,余額解鎖,庫存解鎖)

上游業務簡單,每秒發起了10000個請求,下游業務復雜,每秒只能處理2000個請求,上游不限速的下單,導致下游系統被壓垮,引發雪崩。

常見的優化方案有兩種:
1)上游隊列緩沖(put阻塞),限速發送
2)下游隊列緩沖(定時或者批量拉取pull,可以起到削平流量),限速執行

如果上游發送流量過大,MQ提供拉模式確實可以起到下游自我保護的作用,會不會導致消息在MQ中堆積
答:下游MQ-client拉取消息,消息接收方能夠批量獲取消息,需要下游消息接收方進行優化(提供批處理,比如批量寫),否則整體吞吐量低,也會造成mq堆積

二.高並發系統保護策略

1.緩存

緩存不單單能夠提升系統訪問速度提高並發訪問量,也是保護數據庫、保護系統的有效方式。大型網站一般主要是“讀”,先走DB再走緩存。在大型“寫”系統中,先走緩存,再走DB,對DB進行批處理操作。(累積一些數據,批量寫入;內存里面的緩存隊列,mq像是一種緩存隊列)

2.降級:

根據服務器壓力,指定某些服務或者頁面的級別(需求不同,降級策略不同),以此釋放服務器資源,保證核心任務的正常運行

根據服務方式:可以拒接服務,可以延遲服務,也有時候可以隨機服務。

根據服務范圍:可以砍掉某個功能,可以砍掉某些模塊。

主要的目的就是提供有損服務,以保證服務正常運行。

3.限流:

限制系統的輸入和輸出流量已達到保護系統的目的。

一般來說系統的吞吐量是可以被測算的,一旦達到閾值,就需要限制流量。比如:延遲處理,拒絕處理,部分拒絕處理等等

 實際場景中常用的限流策略

  • Nginx前端限流

         按照一定的規則如IP、賬號、調用邏輯等在Nginx層面做限流

  • 業務應用系統限流

        1、客戶端限流(驗證碼;獲取動態請求路徑pathvariable,到達接口地址隱藏的效果)

        2、服務端限流(redis限速器,延遲隊列)

  • 數據庫限流

       數據庫鏈接池化Mysql(如max_connections)Redis(如tcp-backlog)都會有類似的限制連接數的配置。 //backlog:待辦事項列表

三.限流算法:

1.計數器算法

@1使用JUC工具包下的AtomicInteger或Semaphore:

public class AtomicIntegerDemo {
 
    private static AtomicInteger count = new AtomicInteger(0);
 
    public static void exec() {
        if (count.get() >= 5) {
            System.out.println("請求用戶過多,請稍后在試!"+System.currentTimeMillis()/1000);
        } else {
            count.incrementAndGet();//或者單純的increment
            try {
                //處理核心邏輯
                TimeUnit.SECONDS.sleep(1);
                System.out.println("--"+System.currentTimeMillis()/1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                count.decrementAndGet();
            }
        }
    }
}
public class SemaphoreDemo {
 
    private static Semaphore semphore = new Semaphore(50);//限流閾值50
 
    public static void exec() {
        if(semphore.getQueueLength()>100){  //獲取等待中的請求數
            System.out.println("當前等待排隊的任務數大於100,請稍候再試...");
        }
        try {
            semphore.acquire();
            // 處理核心邏輯
            TimeUnit.SECONDS.sleep(1);
            System.out.println("--" + System.currentTimeMillis() / 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semphore.release();//釋放信號量,給等待隊列
        }
    }
}
semaphore相對Atomic優點:如果是瞬時的高並發,可以使請求在阻塞隊列中排隊,而不是馬上拒絕請求,從而達到一個流量削峰的目的

@2使用redis:

將用戶id+請求路徑(對接口限流)做key,將訪問次數count做value,加入過期時間,原理同AtomicInteger

還可以基於redis的列表,用戶id做key,value為訪問路徑,並設置過期時間,當list的長度大於閾值,拒絕  //rpushx(如果list不存在,插入失敗),rpush,expire,llen等命令

2.漏桶(leaky bucket)

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

  • 任意速率水滴流入漏桶

  • 固定容量的漏桶,按照固定速率流出水滴
  • 如果流入水滴超出了桶的容量,則流入的水滴溢出了(被丟棄),而漏桶容量是不變的。

  • 漏桶為空,則無水滴可留

通過它,突發流量可以被整形以便為網絡提供一個穩定的流量。 漏桶算法比較好實現,在單機系統中可以使用隊列來實現

這里有兩個變量,一個是桶的大小,支持流量突發增多時可以存多少的水(burst),另一個是水桶漏洞!!的大小(rate)

漏桶算法對於存在突發特性的流量來說缺乏效率.

3.令牌桶(Token Bucket)

 令牌桶算法的原理是系統會以一個恆定的速度往桶里放入令牌,而如果請求需要被處理,則需要先從桶里獲取一個令牌,當桶里沒有令牌可取時,則拒絕服務。 當桶滿時,新添加的令牌被丟棄或拒絕。

令牌桶算法基本可以用下面的幾個概念來描述:

  • 令牌將按照固定的速率被放入令牌桶中。比如每秒放10個。
  • 令牌桶容量固定,超出容量的令牌被丟棄
  • 當一個n個字節大小的數據包(請求,突發流量)到達,將從桶中刪除m個令牌,接着數據包被發送到網絡上(上游發送至下游)。
  • 如果桶中的令牌不足m個,則不會刪除令牌,且該數據包將被限流(要么丟棄,要么緩沖區等待)。

 

 

 核心:令牌算法是根據向桶中放令牌的速率去控制輸出的速率

漏桶和令牌桶的比較

令牌桶可以在運行時控制和調整數據處理的速率並很好的處理某時的突發流量

提升數據整體處理速度:放令牌的頻率增加(令牌桶),提高漏洞大小(漏桶)

降低整體數據處理速度:增加每次獲取令牌的個數(請求大小,多少)或者放令牌的頻率減小(令牌桶)

整體而言,令牌桶算法更優,但是實現更為復雜一些。

限流實戰效果

  • 生產環境背景 
    1、服務商接口所能提供的服務上限是400條/s 
    2、業務方調用服務方QPS可能達到800/s,1200/s,或者更高 
    3、當服務商接口訪問頻率超過400/s時,超過的量將拒絕服務,業務方丟失數據
    4、業務方為多節點布置(分布式),但調用的是同一個服務商接口
  • 限流策略
    1、使用guava 的RateLimtier(令牌桶實現者),但是只能用於單機,分布式不可控
    2、使用DelayQueue的過程相對較麻煩,耗時可能比較長,而且達不到精准限流的效果 
    3、使用redis的計數器,精准限流,編寫簡單,適用於分布式,ok

應用級限流:

應用配置:

對於一個應用系統來說一定會有極限並發/請求數,即總有一個TPS/QPS閥值

Tomcat,其Connector 其中一種配置有如下幾個參數:

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

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

maxThreads:Tomcat能啟動用來處理請求的最大線程數如果請求處理量一直遠遠大於最大線程數則可能會僵死。

這里對nginx限流做了解:

Nginx自身有的請求限制模塊、流量限制模塊(基於令牌桶算法),可以方便的控制令牌速率,自定義調節限流,實現基本的限流控制。

vi /export/servers/nginx/conf/nginx.conf
limit_zone one $binary_remote_addr 20m;
limit_req_zone $binary_remote_addr zone=req_one:20m rate=12r/s;
limit_conn one 10;
limit_req zone=req_one burst=120;
 
limit_zone,是針對每個變量(這里指 IP,即$binary_remote_addr)定義一個存儲session狀態的容器。這個示例中定義了一個20m的容器,按照32bytes/session,可以處理640000個session。
limit_req_zone 與limit_zone類似。rate是請求頻率. 每秒允許 12個請求。可以設置請求速率
limit_conn one  10 : 表示一個IP能發起10個並發連接數,可以設置並發連接數
 

可以使用池化技術來限制總資源數:連接池、線程池。比如分配給每個應用的數據庫連接是100,那么本應用最多可以使用100個資源,超出了可以等待或者拋異常。
 


 

參考:

https://blog.csdn.net/syc001/article/details/72841951

https://www.cnblogs.com/softidea/p/6229543.html

https://www.cnblogs.com/mr-amazing/p/4935672.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM