목차
고정 창
滑动窗口
hash实现
list实现
漏桶算法
해시 구현
목록 구현
데이터 베이스 Redis Go+Redis를 사용하여 일반적인 전류 제한 알고리즘을 구현하는 방법

Go+Redis를 사용하여 일반적인 전류 제한 알고리즘을 구현하는 방법

May 27, 2023 pm 11:16 PM
redis go

    고정 창

    Redis를 사용하여 고정 창을 구현하는 것은 상대적으로 간단합니다. 주로 동시에 고정 창이 하나만 있기 때문에 pexpire 명령을 사용하여 설정할 수 있기 때문입니다. 처음으로 창에 들어갈 때 만료 시간은 창 시간 크기이므로, 동시에 incr 명령을 사용하여 창 개수를 늘립니다. pexpire命令设置过期时间为窗口时间大小,这样窗口会随过期时间而失效,同时我们使用incr命令增加窗口计数。

    因为我们需要在counter==1的时候设置窗口的过期时间,为了保证原子性,我们使用简单的Lua脚本实现。

    const fixedWindowLimiterTryAcquireRedisScript = `
    -- ARGV[1]: 窗口时间大小
    -- ARGV[2]: 窗口请求上限
    
    local window = tonumber(ARGV[1])
    local limit = tonumber(ARGV[2])
    
    -- 获取原始值
    local counter = tonumber(redis.call("get", KEYS[1]))
    if counter == nil then 
       counter = 0
    end
    -- 若到达窗口请求上限,请求失败
    if counter >= limit then
       return 0
    end
    -- 窗口值+1
    redis.call("incr", KEYS[1])
    if counter == 0 then
        redis.call("pexpire", KEYS[1], window)
    end
    return 1
    `
    로그인 후 복사
    package redis
    
    import (
       "context"
       "errors"
       "github.com/go-redis/redis/v8"
       "time"
    )
    
    // FixedWindowLimiter 固定窗口限流器
    type FixedWindowLimiter struct {
       limit  int           // 窗口请求上限
       window int           // 窗口时间大小
       client *redis.Client // Redis客户端
       script *redis.Script // TryAcquire脚本
    }
    
    func NewFixedWindowLimiter(client *redis.Client, limit int, window time.Duration) (*FixedWindowLimiter, error) {
       // redis过期时间精度最大到毫秒,因此窗口必须能被毫秒整除
       if window%time.Millisecond != 0 {
          return nil, errors.New("the window uint must not be less than millisecond")
       }
    
       return &FixedWindowLimiter{
          limit:  limit,
          window: int(window / time.Millisecond),
          client: client,
          script: redis.NewScript(fixedWindowLimiterTryAcquireRedisScript),
       }, nil
    }
    
    func (l *FixedWindowLimiter) TryAcquire(ctx context.Context, resource string) error {
       success, err := l.script.Run(ctx, l.client, []string{resource}, l.window, l.limit).Bool()
       if err != nil {
          return err
       }
       // 若到达窗口请求上限,请求失败
       if !success {
          return ErrAcquireFailed
       }
       return nil
    }
    로그인 후 복사

    滑动窗口

    hash实现

    我们使用Redis的hash存储每个小窗口的计数,每次请求会把所有有效窗口的计数累加到count,使用hdel删除失效窗口,最后判断窗口的总计数是否大于上限。

    我们基本上把所有的逻辑都放到Lua脚本里面,其中大头是对hash的遍历,时间复杂度是O(N),N是小窗口数量,所以小窗口数量最好不要太多。

    const slidingWindowLimiterTryAcquireRedisScriptHashImpl = `
    -- ARGV[1]: 窗口时间大小
    -- ARGV[2]: 窗口请求上限
    -- ARGV[3]: 当前小窗口值
    -- ARGV[4]: 起始小窗口值
    
    local window = tonumber(ARGV[1])
    local limit = tonumber(ARGV[2])
    local currentSmallWindow = tonumber(ARGV[3])
    local startSmallWindow = tonumber(ARGV[4])
    
    -- 计算当前窗口的请求总数
    local counters = redis.call("hgetall", KEYS[1])
    local count = 0
    for i = 1, #(counters) / 2 do 
       local smallWindow = tonumber(counters[i * 2 - 1])
       local counter = tonumber(counters[i * 2])
       if smallWindow < startSmallWindow then
          redis.call("hdel", KEYS[1], smallWindow)
       else 
          count = count + counter
       end
    end
    
    -- 若到达窗口请求上限,请求失败
    if count >= limit then
       return 0
    end
    
    -- 若没到窗口请求上限,当前小窗口计数器+1,请求成功
    redis.call("hincrby", KEYS[1], currentSmallWindow, 1)
    redis.call("pexpire", KEYS[1], window)
    return 1
    `
    로그인 후 복사
    package redis
    
    import (
       "context"
       "errors"
       "github.com/go-redis/redis/v8"
       "time"
    )
    
    // SlidingWindowLimiter 滑动窗口限流器
    type SlidingWindowLimiter struct {
       limit        int           // 窗口请求上限
       window       int64         // 窗口时间大小
       smallWindow  int64         // 小窗口时间大小
       smallWindows int64         // 小窗口数量
       client       *redis.Client // Redis客户端
       script       *redis.Script // TryAcquire脚本
    }
    
    func NewSlidingWindowLimiter(client *redis.Client, limit int, window, smallWindow time.Duration) (
       *SlidingWindowLimiter, error) {
       // redis过期时间精度最大到毫秒,因此窗口必须能被毫秒整除
       if window%time.Millisecond != 0 || smallWindow%time.Millisecond != 0 {
          return nil, errors.New("the window uint must not be less than millisecond")
       }
    
       // 窗口时间必须能够被小窗口时间整除
       if window%smallWindow != 0 {
          return nil, errors.New("window cannot be split by integers")
       }
    
       return &SlidingWindowLimiter{
          limit:        limit,
          window:       int64(window / time.Millisecond),
          smallWindow:  int64(smallWindow / time.Millisecond),
          smallWindows: int64(window / smallWindow),
          client:       client,
          script:       redis.NewScript(slidingWindowLimiterTryAcquireRedisScriptHashImpl),
       }, nil
    }
    
    func (l *SlidingWindowLimiter) TryAcquire(ctx context.Context, resource string) error {
       // 获取当前小窗口值
       currentSmallWindow := time.Now().UnixMilli() / l.smallWindow * l.smallWindow
       // 获取起始小窗口值
       startSmallWindow := currentSmallWindow - l.smallWindow*(l.smallWindows-1)
    
       success, err := l.script.Run(
          ctx, l.client, []string{resource}, l.window, l.limit, currentSmallWindow, startSmallWindow).Bool()
       if err != nil {
          return err
       }
       // 若到达窗口请求上限,请求失败
       if !success {
          return ErrAcquireFailed
       }
       return nil
    }
    로그인 후 복사

    list实现

    如果小窗口数量特别多,可以使用list优化时间复杂度,list的结构是:

    [counter, smallWindow1, count1, smallWindow2, count2, smallWindow3, count3...]

    也就是我们使用list的第一个元素存储计数器,每个窗口用两个元素表示,第一个元素表示小窗口值,第二个元素表示这个小窗口的计数。由于Redis Lua脚本不支持字符串分割函数,因此不能将小窗口的值和计数放在同一元素中。

    具体操作流程:

    1.获取list长度

    2.如果长度是0,设置counter,长度+1

    3.如果长度大于1,获取第二第三个元素

    如果该值小于起始小窗口值,counter-第三个元素的值,删除第二第三个元素,长度-2

    4.如果counter大于等于limit,请求失败

    5.如果长度大于1,获取倒数第二第一个元素

    • 如果倒数第二个元素小窗口值大于等于当前小窗口值,表示当前请求因为网络延迟的问题,到达服务器的时候,窗口已经过时了,把倒数第二个元素当成当前小窗口(因为它更新),倒数第一个元素值+1

    • 否则,添加新的窗口值,添加新的计数(1),更新过期时间

    6.否则,添加新的窗口值,添加新的计数(1),更新过期时间

    7.counter + 1

    8.返回成功

    const slidingWindowLimiterTryAcquireRedisScriptListImpl = `
    -- ARGV[1]: 窗口时间大小
    -- ARGV[2]: 窗口请求上限
    -- ARGV[3]: 当前小窗口值
    -- ARGV[4]: 起始小窗口值
    
    local window = tonumber(ARGV[1])
    local limit = tonumber(ARGV[2])
    local currentSmallWindow = tonumber(ARGV[3])
    local startSmallWindow = tonumber(ARGV[4])
    
    -- 获取list长度
    local len = redis.call("llen", KEYS[1])
    -- 如果长度是0,设置counter,长度+1
    local counter = 0
    if len == 0 then 
       redis.call("rpush", KEYS[1], 0)
       redis.call("pexpire", KEYS[1], window)
       len = len + 1
    else
       -- 如果长度大于1,获取第二第个元素
       local smallWindow1 = tonumber(redis.call("lindex", KEYS[1], 1))
       counter = tonumber(redis.call("lindex", KEYS[1], 0))
       -- 如果该值小于起始小窗口值
       if smallWindow1 < startSmallWindow then 
          local count1 = redis.call("lindex", KEYS[1], 2)
          -- counter-第三个元素的值
          counter = counter - count1
          -- 长度-2
          len = len - 2
          -- 删除第二第三个元素
          redis.call("lrem", KEYS[1], 1, smallWindow1)
          redis.call("lrem", KEYS[1], 1, count1)
       end
    end
    
    -- 若到达窗口请求上限,请求失败
    if counter >= limit then 
       return 0
    end 
    
    -- 如果长度大于1,获取倒数第二第一个元素
    if len > 1 then
       local smallWindown = tonumber(redis.call("lindex", KEYS[1], -2))
       -- 如果倒数第二个元素小窗口值大于等于当前小窗口值
       if smallWindown >= currentSmallWindow then
          -- 把倒数第二个元素当成当前小窗口(因为它更新),倒数第一个元素值+1
          local countn = redis.call("lindex", KEYS[1], -1)
          redis.call("lset", KEYS[1], -1, countn + 1)
       else 
          -- 否则,添加新的窗口值,添加新的计数(1),更新过期时间
          redis.call("rpush", KEYS[1], currentSmallWindow, 1)
          redis.call("pexpire", KEYS[1], window)
       end
    else 
       -- 否则,添加新的窗口值,添加新的计数(1),更新过期时间
       redis.call("rpush", KEYS[1], currentSmallWindow, 1)
       redis.call("pexpire", KEYS[1], window)
    end 
    
    -- counter + 1并更新
    redis.call("lset", KEYS[1], 0, counter + 1)
    return 1
    `
    로그인 후 복사

    算法都是操作list头部或者尾部,所以时间复杂度接近O(1)

    漏桶算法

    漏桶需要保存当前水位和上次放水时间,因此我们使用hash

    counter==1일 때 창의 만료 시간을 설정해야 하기 때문에 원자성을 보장하기 위해 간단한 Lua 스크립트 구현을 사용합니다.

    const leakyBucketLimiterTryAcquireRedisScript = `
    -- ARGV[1]: 最高水位
    -- ARGV[2]: 水流速度/秒
    -- ARGV[3]: 当前时间(秒)
    
    local peakLevel = tonumber(ARGV[1])
    local currentVelocity = tonumber(ARGV[2])
    local now = tonumber(ARGV[3])
    
    local lastTime = tonumber(redis.call("hget", KEYS[1], "lastTime"))
    local currentLevel = tonumber(redis.call("hget", KEYS[1], "currentLevel"))
    -- 初始化
    if lastTime == nil then 
       lastTime = now
       currentLevel = 0
       redis.call("hmset", KEYS[1], "currentLevel", currentLevel, "lastTime", lastTime)
    end 
    
    -- 尝试放水
    -- 距离上次放水的时间
    local interval = now - lastTime
    if interval > 0 then
       -- 当前水位-距离上次放水的时间(秒)*水流速度
       local newLevel = currentLevel - interval * currentVelocity
       if newLevel < 0 then 
          newLevel = 0
       end 
       currentLevel = newLevel
       redis.call("hmset", KEYS[1], "currentLevel", newLevel, "lastTime", now)
    end
    
    -- 若到达最高水位,请求失败
    if currentLevel >= peakLevel then
       return 0
    end
    -- 若没有到达最高水位,当前水位+1,请求成功
    redis.call("hincrby", KEYS[1], "currentLevel", 1)
    redis.call("expire", KEYS[1], peakLevel / currentVelocity)
    return 1
    `
    로그인 후 복사
    package redis
    
    import (
       "context"
       "github.com/go-redis/redis/v8"
       "time"
    )
    
    // LeakyBucketLimiter 漏桶限流器
    type LeakyBucketLimiter struct {
       peakLevel       int           // 最高水位
       currentVelocity int           // 水流速度/秒
       client          *redis.Client // Redis客户端
       script          *redis.Script // TryAcquire脚本
    }
    
    func NewLeakyBucketLimiter(client *redis.Client, peakLevel, currentVelocity int) *LeakyBucketLimiter {
       return &LeakyBucketLimiter{
          peakLevel:       peakLevel,
          currentVelocity: currentVelocity,
          client:          client,
          script:          redis.NewScript(leakyBucketLimiterTryAcquireRedisScript),
       }
    }
    
    func (l *LeakyBucketLimiter) TryAcquire(ctx context.Context, resource string) error {
       // 当前时间
       now := time.Now().Unix()
       success, err := l.script.Run(ctx, l.client, []string{resource}, l.peakLevel, l.currentVelocity, now).Bool()
       if err != nil {
          return err
       }
       // 若到达窗口请求上限,请求失败
       if !success {
          return ErrAcquireFailed
       }
       return nil
    }
    로그인 후 복사

    슬라이딩 창

    해시 구현

    Redis의 해시를 사용하여 각 작은 창의 개수를 저장하고 각 요청은 모든 유효한 창</code를 저장합니다. >카운트가 <code>count로 누적되고, hdel을 사용하여 유효하지 않은 창을 삭제하고, 마지막으로 창의 총 개수가 상한보다 큰지 여부를 확인합니다.

    우리는 기본적으로 Lua 스크립트에 모든 로직을 넣었는데, 여기서 가장 큰 부분은 hash의 순회이고, 시간 복잡도는 O(N)이고, N은 작은 창의 수이므로 숫자는 작은 창문은 너무 많지 않은 것이 가장 좋습니다.

    const tokenBucketLimiterTryAcquireRedisScript = `
    -- ARGV[1]: 容量
    -- ARGV[2]: 发放令牌速率/秒
    -- ARGV[3]: 当前时间(秒)
    
    local capacity = tonumber(ARGV[1])
    local rate = tonumber(ARGV[2])
    local now = tonumber(ARGV[3])
    
    local lastTime = tonumber(redis.call("hget", KEYS[1], "lastTime"))
    local currentTokens = tonumber(redis.call("hget", KEYS[1], "currentTokens"))
    -- 初始化
    if lastTime == nil then 
       lastTime = now
       currentTokens = capacity
       redis.call("hmset", KEYS[1], "currentTokens", currentTokens, "lastTime", lastTime)
    end 
    
    -- 尝试发放令牌
    -- 距离上次发放令牌的时间
    local interval = now - lastTime
    if interval > 0 then
       -- 当前令牌数量+距离上次发放令牌的时间(秒)*发放令牌速率
       local newTokens = currentTokens + interval * rate
       if newTokens > capacity then 
          newTokens = capacity
       end 
       currentTokens = newTokens
       redis.call("hmset", KEYS[1], "currentTokens", newTokens, "lastTime", now)
    end
    
    -- 如果没有令牌,请求失败
    if currentTokens == 0 then
       return 0
    end
    -- 果有令牌,当前令牌-1,请求成功
    redis.call("hincrby", KEYS[1], "currentTokens", -1)
    redis.call("expire", KEYS[1], capacity / rate)
    return 1
    `
    로그인 후 복사
    rrree

    목록 구현

    작은 창의 수가 특히 큰 경우 list를 사용하여 시간 복잡도를 최적화할 수 있습니다. 목록의 구조는 다음과 같습니다. 🎜🎜[counter, smallWindow1, count1 , smallWindow2, count2, smallWindow3, count3...]🎜🎜즉, 목록의 첫 번째 요소를 사용하여 카운터를 저장합니다. 첫 번째 요소는 작은 창 값을 나타냅니다. 두 요소는 이 작은 창의 개수를 나타냅니다. Redis Lua 스크립트는 문자열 분할 기능을 지원하지 않기 때문에 작은 창의 값과 개수를 동일한 요소에 배치할 수 없습니다. 🎜🎜특정 작업 프로세스: 🎜🎜1. 목록의 길이를 가져옵니다. 🎜🎜2. 길이가 0이면 카운터를 설정하고 길이 + 1을 설정합니다. 🎜🎜3 길이가 1보다 크면 두 번째 및 세 번째 요소를 가져옵니다. 🎜🎜값이 시작 작은 창 값보다 작은 경우 카운터-세 번째 요소의 값, 두 번째 및 세 번째 요소 길이 -2🎜🎜4를 삭제합니다. 카운터가 제한보다 크거나 같으면 요청이 실패합니다🎜 🎜5. 길이가 1보다 크면 마지막 요소를 가져옵니다. 2. 첫 번째 요소🎜
    • 🎜두 번째 요소의 작은 창 값이 다음보다 큰 경우 현재 작은 창 값과 같으면 네트워크 지연으로 인해 현재 요청이 서버에 도달하지 않았음을 의미합니다. 창이 오래된 경우 두 번째 요소를 현재 작은 창으로 처리합니다(업데이트되므로). 두 번째 요소 값 +1🎜
    • 🎜그렇지 않으면 새 창 값을 추가하고 새 개수(1)를 추가하고 만료 시간을 업데이트합니다 🎜
    • 🎜🎜6. 그렇지 않으면 새 창 값을 추가합니다. , 새 개수 추가(1), 만료 시간 업데이트 🎜🎜7.counter + 1🎜🎜8. 반환 성공 🎜
      package redis
      
      import (
         "context"
         "github.com/go-redis/redis/v8"
         "time"
      )
      
      // TokenBucketLimiter 令牌桶限流器
      type TokenBucketLimiter struct {
         capacity int           // 容量
         rate     int           // 发放令牌速率/秒
         client   *redis.Client // Redis客户端
         script   *redis.Script // TryAcquire脚本
      }
      
      func NewTokenBucketLimiter(client *redis.Client, capacity, rate int) *TokenBucketLimiter {
         return &TokenBucketLimiter{
            capacity: capacity,
            rate:     rate,
            client:   client,
            script:   redis.NewScript(tokenBucketLimiterTryAcquireRedisScript),
         }
      }
      
      func (l *TokenBucketLimiter) TryAcquire(ctx context.Context, resource string) error {
         // 当前时间
         now := time.Now().Unix()
         success, err := l.script.Run(ctx, l.client, []string{resource}, l.capacity, l.rate, now).Bool()
         if err != nil {
            return err
         }
         // 若到达窗口请求上限,请求失败
         if !success {
            return ErrAcquireFailed
         }
         return nil
      }
      로그인 후 복사
      🎜알고리즘은 list의 헤드 또는 테일에서 작동하므로 시간 복잡성은 O(1)에 가깝습니다.🎜🎜Leaky bucket 알고리즘🎜🎜Leaky bucket은 현재 수위와 마지막 물 방류 시간을 저장해야 하므로 hash를 사용하여 이 두 값을 저장합니다. 🎜
      const slidingLogLimiterTryAcquireRedisScriptHashImpl = `
      -- ARGV[1]: 当前小窗口值
      -- ARGV[2]: 第一个策略的窗口时间大小
      -- ARGV[i * 2 + 1]: 每个策略的起始小窗口值
      -- ARGV[i * 2 + 2]: 每个策略的窗口请求上限
      
      local currentSmallWindow = tonumber(ARGV[1])
      -- 第一个策略的窗口时间大小
      local window = tonumber(ARGV[2])
      -- 第一个策略的起始小窗口值
      local startSmallWindow = tonumber(ARGV[3])
      local strategiesLen = #(ARGV) / 2 - 1
      
      -- 计算每个策略当前窗口的请求总数
      local counters = redis.call("hgetall", KEYS[1])
      local counts = {}
      -- 初始化counts
      for j = 1, strategiesLen do
         counts[j] = 0
      end
      
      for i = 1, #(counters) / 2 do 
         local smallWindow = tonumber(counters[i * 2 - 1])
         local counter = tonumber(counters[i * 2])
         if smallWindow < startSmallWindow then
            redis.call("hdel", KEYS[1], smallWindow)
         else 
            for j = 1, strategiesLen do
               if smallWindow >= tonumber(ARGV[j * 2 + 1]) then
                  counts[j] = counts[j] + counter
               end
            end
         end
      end
      
      -- 若到达对应策略窗口请求上限,请求失败,返回违背的策略下标
      for i = 1, strategiesLen do
         if counts[i] >= tonumber(ARGV[i * 2 + 2]) then
            return i - 1
         end
      end
      
      -- 若没到窗口请求上限,当前小窗口计数器+1,请求成功
      redis.call("hincrby", KEYS[1], currentSmallWindow, 1)
      redis.call("pexpire", KEYS[1], window)
      return -1
      `
      로그인 후 복사
      package redis
      
      import (
         "context"
         "errors"
         "fmt"
         "github.com/go-redis/redis/v8"
         "sort"
         "time"
      )
      
      // ViolationStrategyError 违背策略错误
      type ViolationStrategyError struct {
         Limit  int           // 窗口请求上限
         Window time.Duration // 窗口时间大小
      }
      
      func (e *ViolationStrategyError) Error() string {
         return fmt.Sprintf("violation strategy that limit = %d and window = %d", e.Limit, e.Window)
      }
      
      // SlidingLogLimiterStrategy 滑动日志限流器的策略
      type SlidingLogLimiterStrategy struct {
         limit        int   // 窗口请求上限
         window       int64 // 窗口时间大小
         smallWindows int64 // 小窗口数量
      }
      
      func NewSlidingLogLimiterStrategy(limit int, window time.Duration) *SlidingLogLimiterStrategy {
         return &SlidingLogLimiterStrategy{
            limit:  limit,
            window: int64(window),
         }
      }
      
      // SlidingLogLimiter 滑动日志限流器
      type SlidingLogLimiter struct {
         strategies  []*SlidingLogLimiterStrategy // 滑动日志限流器策略列表
         smallWindow int64                        // 小窗口时间大小
         client      *redis.Client                // Redis客户端
         script      *redis.Script                // TryAcquire脚本
      }
      
      func NewSlidingLogLimiter(client *redis.Client, smallWindow time.Duration, strategies ...*SlidingLogLimiterStrategy) (
         *SlidingLogLimiter, error) {
         // 复制策略避免被修改
         strategies = append(make([]*SlidingLogLimiterStrategy, 0, len(strategies)), strategies...)
      
         // 不能不设置策略
         if len(strategies) == 0 {
            return nil, errors.New("must be set strategies")
         }
      
         // redis过期时间精度最大到毫秒,因此窗口必须能被毫秒整除
         if smallWindow%time.Millisecond != 0 {
            return nil, errors.New("the window uint must not be less than millisecond")
         }
         smallWindow = smallWindow / time.Millisecond
         for _, strategy := range strategies {
            if strategy.window%int64(time.Millisecond) != 0 {
               return nil, errors.New("the window uint must not be less than millisecond")
            }
            strategy.window = strategy.window / int64(time.Millisecond)
         }
      
         // 排序策略,窗口时间大的排前面,相同窗口上限大的排前面
         sort.Slice(strategies, func(i, j int) bool {
            a, b := strategies[i], strategies[j]
            if a.window == b.window {
               return a.limit > b.limit
            }
            return a.window > b.window
         })
      
         for i, strategy := range strategies {
            // 随着窗口时间变小,窗口上限也应该变小
            if i > 0 {
               if strategy.limit >= strategies[i-1].limit {
                  return nil, errors.New("the smaller window should be the smaller limit")
               }
            }
            // 窗口时间必须能够被小窗口时间整除
            if strategy.window%int64(smallWindow) != 0 {
               return nil, errors.New("window cannot be split by integers")
            }
            strategy.smallWindows = strategy.window / int64(smallWindow)
         }
      
         return &SlidingLogLimiter{
            strategies:  strategies,
            smallWindow: int64(smallWindow),
            client:      client,
            script:      redis.NewScript(slidingLogLimiterTryAcquireRedisScriptHashImpl),
         }, nil
      }
      
      func (l *SlidingLogLimiter) TryAcquire(ctx context.Context, resource string) error {
         // 获取当前小窗口值
         currentSmallWindow := time.Now().UnixMilli() / l.smallWindow * l.smallWindow
         args := make([]interface{}, len(l.strategies)*2+2)
         args[0] = currentSmallWindow
         args[1] = l.strategies[0].window
         // 获取每个策略的起始小窗口值
         for i, strategy := range l.strategies {
            args[i*2+2] = currentSmallWindow - l.smallWindow*(strategy.smallWindows-1)
            args[i*2+3] = strategy.limit
         }
      
         index, err := l.script.Run(
            ctx, l.client, []string{resource}, args...).Int()
         if err != nil {
            return err
         }
         // 若到达窗口请求上限,请求失败
         if index != -1 {
            return &ViolationStrategyError{
               Limit:  l.strategies[index].limit,
               Window: time.Duration(l.strategies[index].window),
            }
         }
         return nil
      }
      로그인 후 복사
      🎜토큰 버킷🎜🎜토큰 버킷은 리키 버킷(Leaky Bucket)의 반대 알고리즘으로 볼 수 있습니다. 그 중 하나는 버킷에 물을 붓는 것이고, 다른 하나는 버킷에서 토큰을 얻는 것입니다. 🎜rrreeerrreee🎜슬라이딩 로그🎜🎜알고리즘 프로세스는 여러 전략을 지정할 수 있다는 점을 제외하면 슬라이딩 창과 동일합니다. 동시에 요청이 실패하면 어떤 전략이 차단되었는지 호출자에게 알려야 합니다. 🎜rrreerrree

      위 내용은 Go+Redis를 사용하여 일반적인 전류 제한 알고리즘을 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    본 웹사이트의 성명
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

    핫 AI 도구

    Undresser.AI Undress

    Undresser.AI Undress

    사실적인 누드 사진을 만들기 위한 AI 기반 앱

    AI Clothes Remover

    AI Clothes Remover

    사진에서 옷을 제거하는 온라인 AI 도구입니다.

    Undress AI Tool

    Undress AI Tool

    무료로 이미지를 벗다

    Clothoff.io

    Clothoff.io

    AI 옷 제거제

    AI Hentai Generator

    AI Hentai Generator

    AI Hentai를 무료로 생성하십시오.

    인기 기사

    R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
    1 몇 달 전 By 尊渡假赌尊渡假赌尊渡假赌
    R.E.P.O. 최고의 그래픽 설정
    1 몇 달 전 By 尊渡假赌尊渡假赌尊渡假赌
    Will R.E.P.O. 크로스 플레이가 있습니까?
    1 몇 달 전 By 尊渡假赌尊渡假赌尊渡假赌

    뜨거운 도구

    메모장++7.3.1

    메모장++7.3.1

    사용하기 쉬운 무료 코드 편집기

    SublimeText3 중국어 버전

    SublimeText3 중국어 버전

    중국어 버전, 사용하기 매우 쉽습니다.

    스튜디오 13.0.1 보내기

    스튜디오 13.0.1 보내기

    강력한 PHP 통합 개발 환경

    드림위버 CS6

    드림위버 CS6

    시각적 웹 개발 도구

    SublimeText3 Mac 버전

    SublimeText3 Mac 버전

    신 수준의 코드 편집 소프트웨어(SublimeText3)

    Redis 클러스터 모드를 구축하는 방법 Redis 클러스터 모드를 구축하는 방법 Apr 10, 2025 pm 10:15 PM

    Redis Cluster Mode는 Sharding을 통해 Redis 인스턴스를 여러 서버에 배포하여 확장 성 및 가용성을 향상시킵니다. 시공 단계는 다음과 같습니다. 포트가 다른 홀수 redis 인스턴스를 만듭니다. 3 개의 센티넬 인스턴스를 만들고, Redis 인스턴스 및 장애 조치를 모니터링합니다. Sentinel 구성 파일 구성, Redis 인스턴스 정보 및 장애 조치 설정 모니터링 추가; Redis 인스턴스 구성 파일 구성, 클러스터 모드 활성화 및 클러스터 정보 파일 경로를 지정합니다. 각 redis 인스턴스의 정보를 포함하는 Nodes.conf 파일을 작성합니다. 클러스터를 시작하고 Create 명령을 실행하여 클러스터를 작성하고 복제본 수를 지정하십시오. 클러스터에 로그인하여 클러스터 정보 명령을 실행하여 클러스터 상태를 확인하십시오. 만들다

    Redis 데이터를 지우는 방법 Redis 데이터를 지우는 방법 Apr 10, 2025 pm 10:06 PM

    Redis 데이터를 지우는 방법 : Flushall 명령을 사용하여 모든 키 값을 지우십시오. FlushDB 명령을 사용하여 현재 선택한 데이터베이스의 키 값을 지우십시오. 선택을 사용하여 데이터베이스를 전환 한 다음 FlushDB를 사용하여 여러 데이터베이스를 지우십시오. del 명령을 사용하여 특정 키를 삭제하십시오. Redis-Cli 도구를 사용하여 데이터를 지우십시오.

    Redis 명령을 사용하는 방법 Redis 명령을 사용하는 방법 Apr 10, 2025 pm 08:45 PM

    Redis 지시 사항을 사용하려면 다음 단계가 필요합니다. Redis 클라이언트를 엽니 다. 명령 (동사 키 값)을 입력하십시오. 필요한 매개 변수를 제공합니다 (명령어마다 다름). 명령을 실행하려면 Enter를 누르십시오. Redis는 작업 결과를 나타내는 응답을 반환합니다 (일반적으로 OK 또는 -err).

    단일 스레드 레 디스를 사용하는 방법 단일 스레드 레 디스를 사용하는 방법 Apr 10, 2025 pm 07:12 PM

    Redis는 단일 스레드 아키텍처를 사용하여 고성능, 단순성 및 일관성을 제공합니다. 동시성을 향상시키기 위해 I/O 멀티플렉싱, 이벤트 루프, 비 블로킹 I/O 및 공유 메모리를 사용하지만 동시성 제한 제한, 단일 고장 지점 및 쓰기 집약적 인 워크로드에 부적합한 제한이 있습니다.

    Redis의 소스 코드를 읽는 방법 Redis의 소스 코드를 읽는 방법 Apr 10, 2025 pm 08:27 PM

    Redis 소스 코드를 이해하는 가장 좋은 방법은 단계별로 이동하는 것입니다. Redis의 기본 사항에 익숙해집니다. 특정 모듈을 선택하거나 시작점으로 기능합니다. 모듈 또는 함수의 진입 점으로 시작하여 코드를 한 줄씩 봅니다. 함수 호출 체인을 통해 코드를 봅니다. Redis가 사용하는 기본 데이터 구조에 익숙해 지십시오. Redis가 사용하는 알고리즘을 식별하십시오.

    Redis Lock을 사용하는 방법 Redis Lock을 사용하는 방법 Apr 10, 2025 pm 08:39 PM

    Redis를 사용하여 잠금 작업을 사용하려면 SetNX 명령을 통해 잠금을 얻은 다음 만료 명령을 사용하여 만료 시간을 설정해야합니다. 특정 단계는 다음과 같습니다. (1) SETNX 명령을 사용하여 키 값 쌍을 설정하십시오. (2) 만료 명령을 사용하여 잠금의 만료 시간을 설정하십시오. (3) DEL 명령을 사용하여 잠금이 더 이상 필요하지 않은 경우 잠금을 삭제하십시오.

    Redis 대기열을 읽는 방법 Redis 대기열을 읽는 방법 Apr 10, 2025 pm 10:12 PM

    Redis의 대기열을 읽으려면 대기열 이름을 얻고 LPOP 명령을 사용하여 요소를 읽고 빈 큐를 처리해야합니다. 특정 단계는 다음과 같습니다. 대기열 이름 가져 오기 : "큐 :"와 같은 "대기열 : my-queue"의 접두사로 이름을 지정하십시오. LPOP 명령을 사용하십시오. 빈 대기열 처리 : 대기열이 비어 있으면 LPOP이 NIL을 반환하고 요소를 읽기 전에 대기열이 존재하는지 확인할 수 있습니다.

    Redis의 메시지 미들웨어를 만드는 방법 Redis의 메시지 미들웨어를 만드는 방법 Apr 10, 2025 pm 07:51 PM

    메시지 미들웨어로서 Redis는 생산 소비 모델을 지원하고 메시지를 지속하고 안정적인 전달을 보장 할 수 있습니다. Middleware 메시지로 Redis를 사용하면 낮은 대기 시간, 신뢰할 수 있으며 확장 가능한 메시징이 가능합니다.

    See all articles