最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

redis實現(xiàn)的四種常見限流策略

 更新時間:2021年06月18日 08:50:06   作者:煙花散盡13141  
因為在網(wǎng)站運行期間可能會因為突然的訪問量導(dǎo)致業(yè)務(wù)異常、也有可能遭受別人惡意攻,所以我們對網(wǎng)站要進(jìn)行限流,本文主要介紹了redis四種常見限流策略,感興趣的可以了解一下

引言

  • 在web開發(fā)中功能是基石,除了功能以外運維和防護(hù)就是重頭菜了。因為在網(wǎng)站運行期間可能會因為突然的訪問量導(dǎo)致業(yè)務(wù)異常、也有可能遭受別人惡意攻擊
  • 所以我們的接口需要對流量進(jìn)行限制。俗稱的QPS也是對流量的一種描述
  • 針對限流現(xiàn)在大多應(yīng)該是令牌桶算法,因為它能保證更多的吞吐量。除了令牌桶算法還有他的前身漏桶算法和簡單的計數(shù)算法
  • 下面我們來看看這四種算法

固定時間窗口算法

  • 固定時間窗口算法也可以叫做簡單計數(shù)算法。網(wǎng)上有很多都將計數(shù)算法單獨抽離出來。但是筆者認(rèn)為計數(shù)算法是一種思想,而固定時間窗口算法是他的一種實現(xiàn)
  • 包括下面滑動時間窗口算法也是計數(shù)算法的一種實現(xiàn)。因為計數(shù)如果不和時間進(jìn)行綁定的話那么失去了限流的本質(zhì)了。就變成了拒絕了

優(yōu)點

  • 在固定的時間內(nèi)出現(xiàn)流量溢出可以立即做出限流。每個時間窗口不會相互影響
  • 在時間單元內(nèi)保障系統(tǒng)的穩(wěn)定。保障的時間單元內(nèi)系統(tǒng)的吞吐量上限

缺點

  • 正如圖示一樣,他的最大問題就是臨界狀態(tài)。在臨界狀態(tài)最壞情況會受到兩倍流量請求
  • 除了臨界的情況,還有一種是在一個單元時間窗內(nèi)前期如果很快的消耗完請求閾值。那么剩下的時間將會無法請求。這樣就會因為一瞬間的流量導(dǎo)致一段時間內(nèi)系統(tǒng)不可用。這在互聯(lián)網(wǎng)高可用的系統(tǒng)中是不能接受的。

實現(xiàn)

  • 好了,關(guān)于原理介紹及優(yōu)缺點我們已經(jīng)了解了。下面我們動手實現(xiàn)它
  • 首先我們在實現(xiàn)這種計數(shù)時,采用redis是非常好的選擇。這里我們通過redis實現(xiàn)

controller

@RequestMapping(value = "/start",method = RequestMethod.GET)
    public Map<string,object> start(@RequestParam Map<string, object=""> paramMap) {
        return testService.startQps(paramMap);
    }

service

@Override
public Map<string, object=""> startQps(Map<string, object=""> paramMap) {
    //根據(jù)前端傳遞的qps上線
    Integer times = 100;
    if (paramMap.containsKey("times")) {
        times = Integer.valueOf(paramMap.get("times").toString());
    }
    String redisKey = "redisQps";
    RedisAtomicInteger redisAtomicInteger = new RedisAtomicInteger(redisKey, redisTemplate.getConnectionFactory());
    int no = redisAtomicInteger.getAndIncrement();
    //設(shè)置時間固定時間窗口長度 1S
    if (no == 0) {
        redisAtomicInteger.expire(1, TimeUnit.SECONDS);
    }
    //判斷是否超限  time=2 表示qps=3
    if (no > times) {
        throw new RuntimeException("qps refuse request");
    }
    //返回成功告知
    Map<string, object=""> map = new HashMap<>();
    map.put("success", "success");
    return map;
}

結(jié)果測試

我們設(shè)置的qps=3 , 我們可以看到五個并發(fā)進(jìn)來后前三個正常訪問,后面兩個就失敗了。稍等一段時間我們在并發(fā)訪問,前三個又可以正常訪問。說明到了下一個時間窗口

滑動時間窗口算法

  • 針對固定時間窗口的缺點--臨界值出現(xiàn)雙倍流量問題。 我們的滑動時間窗口就產(chǎn)生了。
  • 其實很好理解,就是針對固定時間窗口,將時間窗口統(tǒng)計從原來的固定間隔變成更加細(xì)度化的單元了。
  • 在上面我們固定時間窗口演示中我們設(shè)置的時間單元是1S 。 針對1S我們將1S拆成時間戳。
  • 固定時間窗口是統(tǒng)計單元隨著時間的推移不斷向后進(jìn)行。而滑動時間窗口是我們認(rèn)為的想象出一個時間單元按照相對論的思想將時間固定,我們的抽象時間單元自己移動。抽象的時間單元比實際的時間單元更小。
  • 讀者可以看下下面的動圖,就可以理解了。

優(yōu)點

  • 實質(zhì)上就是固定時間窗口算法的改進(jìn)。所以固定時間窗口的缺點就是他的優(yōu)點。
  • 內(nèi)部抽象一個滑動的時間窗,將時間更加小化。存在邊界的問題更加小??蛻舾兄趿?。

缺點

  • 不管是固定時間窗口算法還是滑動時間窗口算法,他們都是基于計數(shù)器算法進(jìn)行優(yōu)化,但是他們對待限流的策略太粗暴了。
  • 為什么說粗暴呢,未限流他們正常放行。一旦達(dá)到限流后就會直接拒絕。這樣我們會損失一部分請求。這對于一個產(chǎn)品來說不太友好

實現(xiàn)

  • 滑動時間窗口是將時間更加細(xì)化,上面我們是通過redis#setnx實現(xiàn)的。這里我們就無法通過他統(tǒng)一記錄了。我們應(yīng)該加上更小的時間單元存儲到一個集合匯總。然后根據(jù)集合的總量計算限流。redis的zsett數(shù)據(jù)結(jié)構(gòu)就和符合我們的需求。
  • 為什么選擇zset呢,因為redis的zset中除了值以外還有一個權(quán)重。會根據(jù)這個權(quán)重進(jìn)行排序。如果我們將我們的時間單元及時間戳作為我們的權(quán)重,那么我們獲取統(tǒng)計的時候只需要按照一個時間戳范圍就可以了。
  • 因為zset內(nèi)元素是唯一的,所以我們的值采用uuid或者雪花算法一類的id生成器

controller

@RequestMapping(value = "/startList",method = RequestMethod.GET)
    public Map<string,object> startList(@RequestParam Map<string, object=""> paramMap) {
        return testService.startList(paramMap);
    }

service

@RequestMapping(value = "/startList",method = RequestMethod.GET)
    public Map<string,object> startList(@RequestParam Map<string, object=""> paramMap) {
        return testService.startList(paramMap);
    }

結(jié)果測試

  • 和固定時間窗口采用相同的并發(fā)。為什么上面也會出現(xiàn)臨界狀況呢。因為在代碼里時間單元間隔比固定時間間隔采用還要大 。 上面演示固定時間窗口時間單元是1S出現(xiàn)了最壞情況。而滑動時間窗口設(shè)計上就應(yīng)該間隔更短。而我設(shè)置成10S 也沒有出現(xiàn)壞的情況
  • 這里就說明滑動比固定的優(yōu)處了。如果我們調(diào)更小應(yīng)該更加不會出現(xiàn)臨界問題,不過說到底他還是避免不了臨界出現(xiàn)的問題

漏桶算法

  • 滑動時間窗口雖然可以極大程度的規(guī)避臨界值問題,但是始終還是避免不了
  • 另外時間算法還有個致命的問題,他無法面對突如其來的大量流量,因為他在達(dá)到限流后直接就拒絕了其他額外流量
  • 針對這個問題我們繼續(xù)優(yōu)化我們的限流算法。 漏桶算法應(yīng)運而生

優(yōu)點

  • 面對限流更加的柔性,不在粗暴的拒絕。
  • 增加了接口的接收性
  • 保證下流服務(wù)接收的穩(wěn)定性。均勻下發(fā)

缺點

  • 我覺得沒有缺點。非要雞蛋里挑骨頭那我只能說漏桶容量是個短板

實現(xiàn)

controller

@RequestMapping(value = "/startLoutong",method = RequestMethod.GET)
public Map<string,object> startLoutong(@RequestParam Map<string, object=""> paramMap) {
    return testService.startLoutong(paramMap);
}

service

在service中我們通過redis的list的功能模擬出桶的效果。這里代碼是實驗室性質(zhì)的。在真實使用中我們還需要考慮并發(fā)的問題

@Override
public Map<string, object=""> startLoutong(Map<string, object=""> paramMap) {
    String redisKey = "qpsList";
    Integer times = 100;
    if (paramMap.containsKey("times")) {
        times = Integer.valueOf(paramMap.get("times").toString());
    }
    Long size = redisTemplate.opsForList().size(redisKey);
    if (size >= times) {
        throw new RuntimeException("qps refuse request");
    }
    Long aLong = redisTemplate.opsForList().rightPush(redisKey, paramMap);
    if (aLong > times) {
        //為了防止并發(fā)場景。這里添加完成之后也要驗證。  即使這樣本段代碼在高并發(fā)也有問題。此處演示作用
        redisTemplate.opsForList().trim(redisKey, 0, times-1);
        throw new RuntimeException("qps refuse request");
    }
    Map<string, object=""> map = new HashMap<>();
    map.put("success", "success");
    return map;
}

下游消費

@Component
public class SchedulerTask {

    @Autowired
    RedisTemplate redisTemplate;

    private String redisKey="qpsList";

    @Scheduled(cron="*/1 * * * * ?")
    private void process(){
        //一次性消費兩個
        System.out.println("正在消費。。。。。。");
        redisTemplate.opsForList().trim(redisKey, 2, -1);
    }

}

測試

  • 我們還是通過50并發(fā)循環(huán)10次訪問。我們可以發(fā)現(xiàn)只有在一開始能達(dá)到比較高的吞吐量。在隨后桶的容量滿了之后。而下游水滴速率比上游請求速率慢的情況下。只能以下游恒定的速度接收訪問。
  • 他的問題也暴露的很明顯。針對時間窗口的不足漏桶進(jìn)行的不足,但是仍是不足。無法徹底避免請求溢出的問題。
  • 請求溢出本身就是一種災(zāi)難性的問題。所有的算法目前都沒有解決這個問題。只是在減緩他帶來的問題

令牌桶算法

令牌桶和漏桶法是一樣的。只不過將桶的作用方向改變了一下。

漏桶的出水速度是恒定的,如果流量突然增加的話我們就只能拒絕入池

但是令牌桶是將令牌放入桶中,我們知道正常情況下令牌就是一串字符當(dāng)桶滿了就拒絕令牌的入池,但是面對高流量的時候正常加上我們的超時時間就留下足夠長的時間生產(chǎn)及消費令牌了。這樣就盡可能的不會造成請求的拒絕

最后,不論是對于令牌桶拿不到令牌被拒絕,還是漏桶的水滿了溢出,都是為了保證大部分流量的正常使用,而犧牲掉了少部分流量

public Map<string, object=""> startLingpaitong(Map<string, object=""> paramMap) {
        String redisKey = "lingpaitong";
        String token = redisTemplate.opsForList().leftPop(redisKey).toString();
        //正常情況需要驗證是否合法,防止篡改
        if (StringUtils.isEmpty(token)) {
            throw new RuntimeException("令牌桶拒絕");
        }
        Map<string, object=""> map = new HashMap<>();
        map.put("success", "success");
        return map;
    }
@Scheduled(cron="*/1 * * * * ?")
    private void process(){
        //一次性生產(chǎn)兩個
        System.out.println("正在消費。。。。。。");
        for (int i = 0; i < 2; i++) {
            redisTemplate.opsForList().rightPush(redisKey, i);
        }
    }

到此這篇關(guān)于基于redis實現(xiàn)的四種常見的限流策略的文章就介紹到這了,更多相關(guān)redis 限流策略內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • windows下通過批處理腳本啟動redis的操作

    windows下通過批處理腳本啟動redis的操作

    本文主要給大家介紹了windows下通過批處理腳本啟動redis的操作,windows下redis啟動,需要進(jìn)入redis安裝目錄,然后shift+右鍵,選擇“在此處打開命令窗口”,然后輸入redis-server.exe redis.conf,就可以啟動redis了,文中有詳細(xì)的圖文參考,感興趣的朋友可以參考下
    2023-12-12
  • Redis在項目中的使用(JedisPool方式)

    Redis在項目中的使用(JedisPool方式)

    項目操作redis是使用的RedisTemplate方式,另外還可以完全使用JedisPool和Jedis來操作redis,本文給大家介紹Redis在項目中的使用,JedisPool方式,感興趣的朋友跟隨小編一起看看吧
    2021-12-12
  • Redis節(jié)省內(nèi)存的十個技巧分享

    Redis節(jié)省內(nèi)存的十個技巧分享

    你是否在工作中遇到過Redis的bigkey導(dǎo)致的內(nèi)存占用嚴(yán)重、查詢耗時大大增加?同時bigKey還可能導(dǎo)致Redis實例的崩潰,因為內(nèi)存不夠用了,所以本文給大家介紹了Redis極大節(jié)省內(nèi)存的10個技巧,需要的朋友可以參考下
    2024-04-04
  • 詳解如何在Windows上配置和使用Redis持久化功能

    詳解如何在Windows上配置和使用Redis持久化功能

    Redis 是一個強大的內(nèi)存數(shù)據(jù)庫,常用于緩存和實時數(shù)據(jù)處理,然而,由于其內(nèi)存特性,一旦服務(wù)器重啟或故障,存儲在 Redis 中的數(shù)據(jù)可能會丟失,為了確保數(shù)據(jù)的安全性和持久性,Redis 提供了多種持久化機制,本文將詳細(xì)介紹如何在 Windows 上配置和使用 Redis 的持久化功能
    2024-08-08
  • Redis實現(xiàn)消息的發(fā)布訂閱原理分析

    Redis實現(xiàn)消息的發(fā)布訂閱原理分析

    本文主要介紹了Redis實現(xiàn)消息的發(fā)布訂閱原理分析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • redis主從連接不成功錯誤問題及解決

    redis主從連接不成功錯誤問題及解決

    這篇文章主要介紹了redis主從連接不成功錯誤問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教<BR>
    2024-01-01
  • 關(guān)于redisson緩存序列化的幾枚大坑說明

    關(guān)于redisson緩存序列化的幾枚大坑說明

    這篇文章主要介紹了redisson緩存序列化幾枚大坑,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • SpringBoot集成Redis的思路詳解

    SpringBoot集成Redis的思路詳解

    Redis是一個開源的使用ANSI C語言編寫、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫,并提供多種語言的API。接下來通過本文給大家分享SpringBoot集成Redis的詳細(xì)過程,感興趣的朋友一起看看吧
    2021-10-10
  • Redis序列化轉(zhuǎn)換類型報錯的解決

    Redis序列化轉(zhuǎn)換類型報錯的解決

    本文主要介紹了Redis序列化轉(zhuǎn)換類型報錯的解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • 在項目中使用redis做緩存的一些思路

    在項目中使用redis做緩存的一些思路

    這篇文章主要介紹了在項目中使用redis做緩存的一些思路,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09

最新評論

威远县| 贺兰县| 马山县| 如东县| 新源县| 四会市| 桓仁| 讷河市| 北碚区| 卢氏县| 阆中市| 黎平县| 营山县| 南乐县| 溆浦县| 玉山县| 宿迁市| 吉林市| 灵寿县| 梧州市| 舒城县| 杨浦区| 阿拉善右旗| 怀宁县| 巴林右旗| 平山县| 思南县| 邻水| 莒南县| 天长市| 尼木县| 岳阳县| 东乡| 邵东县| 青海省| 贵阳市| 威海市| 清水县| 鹤壁市| 酉阳| 新巴尔虎左旗|