乐趣区

关于golang:gozero-如何扛住流量冲击一

不论是在单体服务中还是在微服务中,开发者为前端提供的 API 接口都是有拜访下限的,当拜访频率或者并发量超过其接受范畴时候,咱们就必须思考限流来保障接口的可用性或者降级可用性。即接口也须要装置上保险丝,以避免非预期的申请对系统压力过大而引起的零碎瘫痪。

go-zero 集成了开箱即用的 限流器 。其中内置了两种限流器,也对应两类应用场景:

品种 原理 场景
periodlimit 单位工夫限度拜访次数 须要强行限度数据的传输速率
tokenlimit 令牌桶限流 限度数据的均匀传输速率,同时容许某种程度的突发传输

本文就来介绍一下 periodlimit

应用

const (
    seconds = 1
    total   = 100
    quota   = 5
)
// New limiter
l := NewPeriodLimit(seconds, quota, redis.NewRedis(s.Addr(), redis.NodeType), "periodlimit")

// take source
code, err := l.Take("first")
if err != nil {logx.Error(err)
    return true
}

// switch val => process request
switch code {
    case limit.OverQuota:
        logx.Errorf("OverQuota key: %v", key)
        return false
    case limit.Allowed:
        logx.Infof("AllowedQuota key: %v", key)
        return true
    case limit.HitQuota:
        logx.Errorf("HitQuota key: %v", key)
        // todo: maybe we need to let users know they hit the quota
        return false
    default:
        logx.Errorf("DefaultQuota key: %v", key)
        // unknown response, we just let the sms go
        return true
}

periodlimit

go-zero 采取 滑动窗口 计数的形式,计算一段时间内对同一个资源的拜访次数,如果超过指定的 limit,则回绝拜访。当然如果你是在一段时间内拜访不同的资源,每一个资源访问量都不超过 limit,此种状况是容许大量申请进来的。

而在一个分布式系统中,存在多个微服务提供服务。所以当霎时的流量同时拜访同一个资源,如何让计数器在分布式系统中失常计数?同时在计算资源拜访时,可能会波及多个计算,如何保障计算的原子性?

  • go-zero 借助 redisincrby 做资源拜访计数
  • 采纳 lua script 做整个窗口计算,保障计算的原子性

上面来看看 lua script 管制的几个要害属性:

argument mean
key[1] 拜访资源的标示
ARGV[1] limit => 申请总数,超过则限速。可设置为 QPS
ARGV[2] window 大小 => 滑动窗口,用 ttl 模拟出滑动的成果
-- to be compatible with aliyun redis, 
-- we cannot use `local key = KEYS[1]` to reuse thekey
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
-- incrbt key 1 => key visis++
local current = redis.call("INCRBY", KEYS[1], 1)
-- 如果是第一次拜访,设置过期工夫 => TTL = window size
-- 因为是只限度一段时间的拜访次数
if current == 1 then
    redis.call("expire", KEYS[1], window)
    return 1
elseif current < limit then
    return 1
elseif current == limit then
    return 2
else
    return 0
end

至于上述的 return code,返回给调用方。由调用方来决定申请后续的操作:

return code tag call code mean
0 OverQuota 3 over limit
1 Allowed 1 in limit
2 HitQuota 2 hit limit

上面这张图形容了申请进入的过程,以及申请触发 limit 时后续产生的状况:

后续解决

如果在服务某个工夫点,申请大批量打进来,periodlimit 短期工夫内达到 limit 阈值,而且设置的工夫范畴还远远没有达到。后续申请的解决就成为问题。

periodlimit 中并没有解决,而是返回 code。把后续申请的解决交给了开发者本人解决。

  1. 如果不做解决,那就是简略的将申请回绝
  2. 如果须要解决这些申请,开发者能够借助 mq 将申请缓冲,减缓申请的压力
  3. 采纳 tokenlimit,容许临时的流量冲击

所以下一篇咱们就来聊聊 tokenlimit

总结

go-zero 中的 periodlimit 限流计划是基于 redis 计数器,通过调用 redis lua script,保障计数过程的原子性,同时保障在分布式的状况下计数是失常的。

然而这种计划也存在毛病,因为它要记录时间窗口内的所有行为记录,如果这个量特地大的时候,内存耗费会变得十分重大。

参考

  • go-zero periodlimit
  • 分布式服务限流实战,曾经为你排好坑了

同时欢送大家应用 go-zero 并退出咱们,https://github.com/tal-tech/g…

如果感觉文章不错,欢送 github 点个 star ????

我的项目地址:
https://github.com/tal-tech/go-zero

退出移动版