1
If you write a bug management system, with this PeriodLimit you can limit each tester to only one bug per day. Is work easier? :P

The essential reason for the popularity of microservice architecture today is to reduce the overall complexity of the system and to distribute the system risks to the subsystems to maximize the stability of the system. After the domain is divided into different subsystems, each subsystem can be developed independently , test, release, R&D rhythm and efficiency can be significantly improved.

However, it also brings problems, such as: the call link is too long, the complexity of the deployment architecture is increased, and various middleware needs to support distributed scenarios. In order to ensure the normal operation of microservices, service governance is indispensable, which usually includes: current limit, downgrade, and circuit breaker.

The current limit refers to limiting the frequency of interface calls, so as not to exceed the load limit and drag down the system. for example:

  1. E-commerce spike scene
  2. API current limit for different merchants

Commonly used current limiting algorithms are:

  • Fixed time window current limit
  • Sliding time window current limit
  • Leaky bucket current limiting
  • Token Bucket Current Limit

This article mainly explains the fixed time window current limiting algorithm. The main usage scenarios are:

  • Each mobile number can only send 5 SMS verification codes per day
  • Each user is limited to 3 consecutive password attempts per hour
  • Each member can only claim 3 benefits per day

working principle

From a certain point in time, the number of requests for each request is +1, and at the same time, it is judged whether the number of requests in the current time window exceeds the limit. If the limit is exceeded, the request is rejected, and then the counter is cleared to wait for the request at the beginning of the next time window.

Advantages and disadvantages

Advantage

The implementation is simple and efficient, and it is especially suitable for scenarios such as a user can only send 10 articles a day, can only send 5 SMS verification codes, and can only try to log in 5 times. Such scenarios are very common in actual business.

Disadvantages

The disadvantage of fixed time window current limiting is that it cannot handle critical section request burst scenarios.

Assuming that the current is limited to 100 requests per 1s, the user initiates 200 requests within 1s starting at 500ms in the middle, and all 200 requests can be passed at this time. This is inconsistent with our expectation of limiting the current 100 times per second. The root cause is that the fine-grained current limiting is too coarse.

go-zero code implementation

core/limit/periodlimit.go

Redis expiration time is used in go-zero to simulate a fixed time window.

redis lua script:
-- KYES[1]:限流器key
-- ARGV[1]:qos,单位时间内最多请求次数
-- ARGV[2]:单位限流窗口时间
-- 请求最大次数,等于p.quota
local limit = tonumber(ARGV[1])
-- 窗口即一个单位限流周期,这里用过期模拟窗口效果,等于p.permit
local window = tonumber(ARGV[2])
-- 请求次数+1,获取请求总数
local current = redis.call("INCRBY",KYES[1],1)
-- 如果是第一次请求,则设置过期时间并返回 成功
if current == 1 then
  redis.call("expire",KYES[1],window)
  return 1
-- 如果当前请求数量小于limit则返回 成功
elseif current < limit then
  return 1
-- 如果当前请求数量==limit则返回 最后一次请求
elseif current == limit then
  return 2
-- 请求数量>limit则返回 失败
else
  return 0
end
Fixed Time Window Current Limiter Definition
type (
  // PeriodOption defines the method to customize a PeriodLimit.
  // go中常见的option参数模式
  // 如果参数非常多,推荐使用此模式来设置参数
  PeriodOption func(l *PeriodLimit)

  // A PeriodLimit is used to limit requests during a period of time.
  // 固定时间窗口限流器
  PeriodLimit struct {
    // 窗口大小,单位s
    period     int
    // 请求上限
    quota      int
    // 存储
    limitStore *redis.Redis
    // key前缀
    keyPrefix  string
    // 线性限流,开启此选项后可以实现周期性的限流
    // 比如quota=5时,quota实际值可能会是5.4.3.2.1呈现出周期性变化
    align      bool
  }
)

Pay attention to the align parameter. When align=true, the request upper limit will change periodically.
For example, when quota=5, the actual quota may be 5.4.3.2.1 showing periodic changes

Current limiting logic

In fact, the current-limiting logic is implemented in the lua script above. It should be noted that the return value is

  • 0: Indicates an error, such as redis failure, overload
  • 1: Allow
  • 2: Allowed but the upper limit has been reached in the current window. If it is running a batch business, you can sleep and wait for the next window at this time (the author considers it very carefully)
  • 3: Reject
// Take requests a permit, it returns the permit state.
// 执行限流
// 注意一下返回值:
// 0:表示错误,比如可能是redis故障、过载
// 1:允许
// 2:允许但是当前窗口内已到达上限
// 3:拒绝
func (h *PeriodLimit) Take(key string) (int, error) {
  // 执行lua脚本
  resp, err := h.limitStore.Eval(periodScript, []string{h.keyPrefix + key}, []string{
    strconv.Itoa(h.quota),
    strconv.Itoa(h.calcExpireSeconds()),
  })
  
  if err != nil {
    return Unknown, err
  }

  code, ok := resp.(int64)
  if !ok {
    return Unknown, ErrUnknownCode
  }

  switch code {
  case internalOverQuota:
    return OverQuota, nil
  case internalAllowed:
    return Allowed, nil
  case internalHitQuota:
    return HitQuota, nil
  default:
    return Unknown, ErrUnknownCode
  }
}

This fixed window current limit may be used to limit, for example, a user can only send verification code SMS 5 times a day. At this time, we need to correspond to the Chinese time zone (GMT+8), and in fact, the current limit time should start from 0:00. At this time, we Additional alignment is required (set align to true).

// 计算过期时间也就是窗口时间大小
// 如果align==true
// 线性限流,开启此选项后可以实现周期性的限流
// 比如quota=5时,quota实际值可能会是5.4.3.2.1呈现出周期性变化
func (h *PeriodLimit) calcExpireSeconds() int {
  if h.align {
    now := time.Now()
    _, offset := now.Zone()
    unix := now.Unix() + int64(offset)
    return h.period - int(unix%int64(h.period))
  }

  return h.period
}

project address

https://github.com/zeromicro/go-zero

Welcome to go-zero and support us star

WeChat exchange group

Follow the "161dd15e9dca61 Microservice Practice " and click on the exchange group get the QR code of the community group.


kevinwan
931 声望3.5k 粉丝

go-zero作者