[實際問題]單個應用的限流

應用限流算法

前言安全

對於一個高併發的系統來講,限流實際上是很是重要的一個環節,當巨大的流量到達咱們的服務器的時候,就很容易形成接口不可用。因此針對此類問題,本文對限流這個問題在業務代碼層面進行深刻探討。服務器

(七牛雲的圖牀不知道爲啥在Markdown裏解析不出來,今天用富文本編輯器吧)併發

限流算法app

1,計數器算法編輯器

算法如其名,對請求進行技術,超過了設定的上限,則拒絕請求,是比較粗暴簡單的一種算法。好比設定上限爲100,那麼每來一次請求計數器就+1,固然這裏的+1的過程確定是原子性的,能夠採用AtomicLong來實現,在接下里的一秒內,若是計數器的值達到了100,那麼後面的請求所有被拒絕。ide

缺點:計數器只在請求數上進行了考慮,並無考慮請求到來的速率,也就是說若是一秒鐘前面的100mm請求數就到達了100,那麼在後面的900mm中,請求就只能所有被拒絕,這種現象叫作「突刺現象」。那麼這樣會形成問題呢,從安全角度來思考,若是咱們的應用設置一分鐘最多100請求,此時有一個不懷好意的用戶,在這一分鐘的最後一秒鐘就直接發送了100個請求,而後在下一分鐘的一開始又瞬間發送了100個請求,這樣其實已經大大超過了請求速率的上限,這樣應用就不提供服務了,這裏的缺點在於,對於秒級的限流,咱們沒有沒有作到精度控制,也就是一分鐘的時間卻只有一個計數器,這樣天然是不行的。高併發

2,滑動窗口算法學習

這個在學習計網的時候都必定有所接觸,實際上就是對固定的時間片進行劃分而且隨着時間進行移動,這種算法能夠克服上面的計數器算法的臨界點的問題,在滑動從窗口算法中,將時間段進行了劃分,好比將一分鐘均等劃分爲六個小格子,每個小格子有本身獨立的計數器而且負責本身這一個格子的計數。每過去十秒鐘,窗口就會日後走動一格。對於計數器的臨界問題,在滑動窗口中就不會有這個問題了,好比在前一分鐘的最後一秒惡意用戶忽然發送了100個請求(下圖灰色部分),到後一分鐘的第一秒的時候(下圖橘色部分),又來了100個請求,可是此時滑動窗口也向後面移動了一格,因此移動後的時間窗口內的總請求是200,應用就能檢測到請求速率超高進而進行限流。測試

3, 漏桶算法

顧名思義,這個算法相似於漏斗,上面口大,下面口小,來得及處理的就從下面的口子出去,來不及的就先存在桶裏,若是桶滿了,請求就先丟棄。

圖示:

​ 缺點:漏桶算法也有缺點,那就是不能應對短期的突發流量。

4,令牌桶算法

令牌桶算法是對漏桶算法的一種改進,桶算法能夠限制請求調用的速率,而令牌桶算法可以限制調用的平均速率,同時容許必定程度的突發調用。令牌桶算法會以一個恆定的速率向桶中放入令牌,每一次請求調用都須要獲取令牌,只有獲取到了,纔有機會繼續執行,當桶裏沒有令牌的時候,將當前請求丟棄或者阻塞。其中,放令牌的動做是不斷地執行的,若是桶裏的令牌數量達到上限,就丟棄令牌。因此這種算法能夠抵擋瞬時增長的請求,只有在桶裏沒有令牌的時候,請求就會進行等待或者被丟棄。

 

 

令牌桶算法demo

針對生產中經常使用的限流方式,這裏採用Guava中提供的RateLimiter來對令牌桶算法寫個demo。代碼理解起來也不難。

/**
 * @author yintianhao
 * @createTime 2020/10/18 16:54
 * @description
 */
@RestController
@Slf4j
public class DemoController {


    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM--dd HH:mm:ss");

    /**
     * rateLimiter 限流器
     *
     * */
    private static final RateLimiter rateLimiter = RateLimiter.create(10);


    @GetMapping("/get")
    public String getResponse(){

        if(rateLimiter.tryAcquire()){
            //一次拿一個,默認返回時間是0,即拿不到就當即返回
            log.info(String.format("time:%s msg:%s",sdf.format(new Date()),"請求正常"));
            try {
                Thread.sleep(500);
            }catch (Exception e){
                e.printStackTrace();
            }
            return "正常請求";
        }else{
            log.error(String.format("time:%s msg:%s",sdf.format(new Date()),"請求限流"));
            return "限流了";
        }
    }


}
DemoController

再用JMeter來進行測試,設置十個線程去請求。

查看限流狀況:

相關文章
相關標籤/搜索