最近2018中文字幕在日韩欧美国产成人片_国产日韩精品一区二区在线_在线观看成年美女黄网色视频_国产精品一区三区五区_国产精彩刺激乱对白_看黄色黄大色黄片免费_人人超碰自拍cao_国产高清av在线_亚洲精品电影av_日韩美女尤物视频网站

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
限流

限流概述

系統(tǒng)存在服務(wù)上限,流量超過服務(wù)上限會導致系統(tǒng)卡死、崩潰。
限流:為了在高并發(fā)時系統(tǒng)穩(wěn)定可用,犧牲或延遲部分請求流量以保證系統(tǒng)整體服務(wù)可用。

成都創(chuàng)新互聯(lián)公司專業(yè)為企業(yè)提供海西網(wǎng)站建設(shè)、海西做網(wǎng)站、海西網(wǎng)站設(shè)計、海西網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、海西企業(yè)網(wǎng)站模板建站服務(wù),10余年海西做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。

限流算法

  • 固定窗口計數(shù)
    • 將時間劃分為多個窗口;
    • 在每個窗口內(nèi)每有一次請求就將計數(shù)器加一;
    • 如果計數(shù)器超過了限制數(shù)量,則本窗口內(nèi)所有的請求都被丟棄當時間到達下一個窗口時,計數(shù)器重置。
  • 滑動窗口計數(shù)
    • 將時間劃分為多個區(qū)間;
    • 在每個區(qū)間內(nèi)每有一次請求就將計數(shù)器加一維持一個時間窗口,占據(jù)多個區(qū)間;
    • 每經(jīng)過一個區(qū)間的時間,則拋棄最老的一個區(qū)間,并納入最新的一個區(qū)間;
    • 如果當前窗口內(nèi)區(qū)間的請求計數(shù)總和超過了限制數(shù)量,則本窗口內(nèi)所有的請求都被丟棄。
  • 漏桶
    • 將每個請求視作"水滴"放入"漏桶"進行存儲;
    • "漏桶"以固定速率向外"漏"出請求來執(zhí)行如果"漏桶"空了則停止"漏水";
    • 如果"漏桶"滿了則多余的"水滴"會被直接丟棄。
  • 令牌桶
    • 令牌以固定速率生成;
    • 生成的令牌放入令牌桶中存放,如果令牌桶滿了則多余的令牌會直接丟棄,當請求到達時,會嘗試從令牌桶中取令牌,取到了令牌的請求可以執(zhí)行;
    • 如果桶空了,那么嘗試取令牌的請求會被直接丟棄。

漏桶和令牌桶對比

  • 兩者實際上是相同的
    • 在實現(xiàn)上是相同的基本算法,描述不同。
    • 給定等效參數(shù)的情況下,這兩種算法會將完全相同的數(shù)據(jù)包視為符合或不符合。
    • 兩者實現(xiàn)的屬性和性能差異完全是由于實現(xiàn)的差異造成的,即它們不是源于底層算法的差異。
  • 漏桶算法在用作計量時,可以允許具有抖動或突發(fā)性的一致輸出數(shù)據(jù)包流,可用于流量管制和整形,并且可以用于可變長度數(shù)據(jù)包。
  • 參考:
    • 漏桶 - wikipedia
    • 令牌桶 - wikipedia

相關(guān)閱讀:

  • 分布式服務(wù)限流實戰(zhàn),已經(jīng)為你排好坑了
  • 接口限流算法總結(jié) - 穿林度水

限流注解組件實現(xiàn)

  1. 利用 Spring 攔截器實現(xiàn)
  2. 使用方式:Controller 方法或類加上限流注解,請求到達攔截器時進行攔截處理
  3. 使用 Redis 記錄數(shù)據(jù),Lua 保證多個命令原子性執(zhí)行。
  • 使用示例

    @RestController
    @RequestMapping("/ratelimit/custom")
    @RateLimit(threshold = 10, rateLimiter = RateLimiterEnum.FIXED_WINDOW, time = 10, timeUnit = TimeUnit.SECONDS)
    public class RateLimitController {
    
        @GetMapping("/fixed/window")
        @RateLimit(threshold = 10, rateLimiter = RateLimiterEnum.FIXED_WINDOW, time = 10, timeUnit = TimeUnit.SECONDS)
        public ResponseResult fixedWindow(Long id) {
            id += RandomUtil.randomLong();
            log.info("custom:fixedWindow:{}", id);
            return ResponseResult.success("custom:fixedWindow:" + id);
        }
    
    }
    
  • 限流注解 RateLimit.java

    • 支持不同類型緩存 key: key() + keyType()
    • 支持使用不同限流算法: rateLimiter()
    • 限流流量閾值設(shè)置: threshold()
    • 限流時長設(shè)置: time() + timeUnit()
  • 限流攔截器處理 RateLimitInterceptor.java
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
    
        HandlerMethod handlerMethod = ((HandlerMethod) handler);
        // 從方法和類上獲取注解
        RateLimit annotation = AspectUtil.findMethodOrClassAnnotation(handlerMethod.getMethod(),
                RateLimit.class);
        if (annotation == null) {
            return true;
        }
    
        AspectKeyTypeEnum.KeyTypeData data = AspectKeyTypeEnum.KeyTypeData.builder()
                .prefix("rate:limit").key(annotation.key()).build();
        String limitKey = annotation.keyType()
                .obtainTypeKey(handlerMethod.getMethod(), handlerMethod.getMethodParameters(), data);
        RateLimiterEnum limiterEnum = annotation.rateLimiter();
    
        // 執(zhí)行限流腳本
        Long isLimit = redisUtil.execute(limiterEnum.obtainScript(),
                Lists.newArrayList(limitKey), limiterEnum.obtainArgvs(annotation).toArray());
        if (isLimit != null && isLimit != 0L) {
            return true;
        }
    
        throw new ResponseException(ResponseEnum.RATE_LIMITED);
    }
    
  • 限流算法 lua 腳本

    固定窗口: fixed_window_rate_limiter.lua
      ```lua
      -- 限流key ,string 保存調(diào)用限流的次數(shù)
      local key = KEYS[1];
      -- 最大訪問量
      local capacity = tonumber(ARGV[1]);
      -- 限流時長(毫秒)
      local ttl = tonumber(ARGV[2]);
    
      local count = redis.call('INCR', key);
      if (count == 1) then
          -- 首次訪問設(shè)置過期時間
          redis.call('PEXPIRE', key, ttl);
      end
    
      local res = 0;
      if (count <= capacity) then
          res = 1;
      end
    
      -- 被限流返回0,未被限流返回1
      return res;
      ```
    
    滑動窗口: sliding_window_rate_limiter.lua
      ```lua
      -- 限流 key , zset 保存未被限流的 id 與時間戳
      local key = KEYS[1];
      -- 最大訪問量
      local capacity = tonumber(ARGV[1]);
      -- 限流時長(毫秒)
      local ttl = tonumber(ARGV[2]);
      -- 當前時間戳(毫秒)
      local now = tonumber(ARGV[3]);
      -- 唯一ID
      local ukid = ARGV[4];
    
      -- 清除過期的數(shù)據(jù)
      redis.call('ZREMRANGEBYSCORE', key, 0, now - ttl);
    
      local count = redis.call('ZCARD', key);
      local res = 0;
      if (count < capacity) then
          -- 往 zset 中添加一個值、得分均為當前時間戳的元素,[value,score]
          redis.call("ZADD", key, now, ukid);
          -- 重置 zset 的過期時間,單位毫秒
          redis.call("PEXPIRE", key, ttl);
          res = 1;
      end
    
      -- 被限流返回0,未被限流返回1
      return res;
      ```
    
    漏桶: leaky_bucket_rate_limiter.lua
      ```lua
      -- 限流 key , hash 保存限流相關(guān)信息
      local key = KEYS[1];
      -- 最大訪問量
      local capacity = tonumber(ARGV[1]);
      -- 限流時長(毫秒)
      local ttl = tonumber(ARGV[2]);
      -- 當前時間戳(毫秒)
      local now = tonumber(ARGV[3]);
      -- 水流出速率(每毫秒)
      local rate = tonumber(ARGV[4]);
    
      -- 限流信息
      local info = redis.call("HMGET", key, "last_time", "stored_water");
      -- 上次處理時間
      local last_time = tonumber(info[1]);
      -- 當前存儲的水量,默認為0,存在保存值使用保存值
      local stored_water = tonumber(info[2]);
      if (stored_water == nil) then
          stored_water = 0;
      end
    
      if (last_time ~= nil) then
          -- 根據(jù)上次處理時間和當前時間差,計算流出后的水量
          local leaked_water = math.floor((now - last_time) * rate);
          stored_water = math.max(stored_water - leaked_water, 0);
          if (leaked_water > 0) then
              last_time = nil;
          end
      end
    
      -- 首次訪問、泄露了水 設(shè)置上次處理時間
      if (last_time == nil) then
          redis.call("HSET", key, "last_time", now);
      end
    
      -- 被限流返回0,未被限流返回1
      local res = 0;
      if (capacity > stored_water) then
          redis.call("HSET", key, "stored_water", stored_water + 1);
          res = 1;
      end
    
      redis.call("PEXPIRE", key, ttl);
      return res;
      ```
    
    令牌桶: token_bucket_rate_limiter.lua
      ```lua
      -- 限流 key , hash 保存限流相關(guān)信息
      local key = KEYS[1];
      -- 最大訪問量
      local capacity = tonumber(ARGV[1]);
      -- 限流時長(毫秒)
      local ttl = tonumber(ARGV[2]);
      -- 當前時間戳(毫秒)
      local now = tonumber(ARGV[3]);
      -- 生成令牌速率(每毫秒)
      local rate = tonumber(ARGV[4]);
    
      -- 限流信息
      local info = redis.call("HMGET", key, "last_time", "stored_tokens");
      -- 上次處理時間
      local last_time = tonumber(info[1]);
      -- 令牌數(shù)量,默認為最大訪問量,存在保存值使用保存值
      local stored_tokens = tonumber(info[2]);
      if (stored_tokens == nil) then
          stored_tokens = capacity;
      end
    
      if (last_time ~= nil) then
          -- 根據(jù)上次處理時間和當前時間差,觸發(fā)式往桶里添加令牌
          local add_tokens = math.floor((now - last_time) * rate);
          stored_tokens = math.min(add_tokens + stored_tokens, capacity);
          if (add_tokens > 0) then
              last_time = nil;
          end
      end
    
      -- 首次訪問、添加了令牌 設(shè)置上次處理時間
      if (last_time == nil) then
          redis.call("HSET", key, "last_time", now);
      end
    
      -- 被限流返回0,未被限流返回1
      local res = 0;
      if (stored_tokens > 0) then
          redis.call("HSET", key, "stored_tokens", stored_tokens - 1);
          res = 1;
      end
    
      redis.call("PEXPIRE", key, ttl);
      return res;
      ```
    

其他

demo 地址:https://github.com/EastX/java-practice-demos/tree/main/demo-ratelimit

推薦閱讀:

  • 限流 - JavaGuide
  • 架構(gòu)之高并發(fā):限流 - pdai

名稱欄目:限流
網(wǎng)頁鏈接:http://fisionsoft.com.cn/article/dsoidcj.html