如果你写一个 bug 管理系统,用了这个 PeriodLimit 你就可以限制每个测试人员每天只能给你提一个 bug。工作是不是就轻松很多了?:P
如今微服务架构大行其道本质原因是因为要降低系统的整体复杂度,将系统风险均摊到子系统从而最大化保证系统的稳定性,通过领域划分拆成不同的子系统后各个子系统能独立的开发、测试、发布,研发节奏和效率能明显提高。
但同时也带来了问题,比如:调用链路过长,部署架构复杂度提升,各种中间件需要支持分布式场景。为了确保微服务的正常运行,服务治理就不可或缺了,通常包括:限流,降级,熔断。
其中限流指的是针对接口调用频率进行限制,以免超出承载上限拖垮系统。比如:
电商秒杀场景
API 针对不同商户限流
常用的限流算法有:
固定时间窗口限流
滑动时间窗口限流
漏桶限流
令牌桶限流
本文主要讲解固定时间窗口限流算法,主要的使用场景比如:
每个手机号每天只能发5条验证码短信
每个用户每小时只能连续尝试3次密码
每个会员每天只能领3次福利
工作原理
从某个时间点开始每次请求过来请求数+1,同时判断当前时间窗口内请求数是否超过限制,超过限制则拒绝该请求,然后下个时间窗口开始时计数器清零等待请求。
优缺点
优点
实现简单高效,特别适合用来限制比如一个用户一天只能发10篇文章、只能发送5次短信验证码、只能尝试登录5次等场景,实际业务中此类场景非常多见。
缺点
固定时间窗口限流的缺点在于无法处理临界区请求突发场景。
假设每 1s 限流 100 次请求,用户在中间 500ms 时开始 1s 内发起 200 次请求,此时 200 次请求是可以全部通过的。这就和我们预期 1s 限流 100 次不合了,根源在于限流的细粒度太粗。
go-zero 代码实现
core/limit/periodlimit.go
go-zero 中使用 redis 过期时间来模拟固定时间窗口。
redis lua 脚本:
-- 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
固定时间窗口限流器定义
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
? }
)
注意一下 align 参数,align=true 时请求上限将会呈现周期性的变化。
比如quota=5时实际quota可能是5.4.3.2.1呈现出周期性变化
限流逻辑
其实限流逻辑在上面的 lua 脚本实现了,需要注意的是返回值
0:表示错误,比如可能是 redis 故障、过载
1:允许
2:允许但是当前窗口内已到达上限,如果是跑批业务的话此时可以休眠 sleep 一下等待下个窗口(作者考虑的非常细致)
3:拒绝
// 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
? }
}
这个固定窗口限流可能用来限制比如一个用户一天只能发送5次验证码短信,此时我们就需要跟中国时区对应(GMT+8),并且其实限流时间应该从零点开始,此时我们需要额外对齐(设置 align 为 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
}
项目地址
https://github.com/zeromicro/go-zero
来源:https://juejin.cn/post/7051406419823689765


猜你喜欢
- 创建云函数目录首先,我们需要在uni-app项目文件夹下,创建一个云函数目录,路径随意,我这里是functions。然后先随便在里面放一些文
- 前言go mod tidy的作用是把项目所需要的依赖添加到go.mod,并删除go.mod中,没有被项目使用的依赖。Tidy makes s
- strConnString = "driver={MySQL ODBC 3.51 
- 优化是一项复杂的任务,因为它最终需要对整个系统的理解.当用你的系统/应用的小知识做一些局部优化是可能的时候,你越想让你的系统更优化,你必须知
- 前言使用的pyecharts是v1.0这里需要注意,pyecharts0.5的版本和v1.0以上的版本完全不一样,可以说是两个包该包能够方便
- 列表与元组列表用大括号[]表示,元组用圆括号()表示。列表可以修改,字符串与元组不可修改。元组的分片还是元组,列表的分片还是列表。1.列表方
- DOM DiffVue创建视图分为俩种情况:首次渲染,会用组件template转换成的真实DOM来替换应用中的根元素当数据更新后,视图重新渲
- 通常我们做统计图的时候需要借助组件来完成例如mschart,aspchart等但是这个类不需要任何组件,而且使用方便,本站测试可用:clsG
- 数据库访问是程序中应用最普遍的部分。随着C#和ADO.NET的引入,这种操作变得更简单。这篇文章将示范四种最基础的数据库操作。
- 简单的Python代码:用户登录注册利用业余时间,写了一个用户进行登录注册的代码,非常简单。主要实现的功能是:1、可以进行用户登录,在用户进
- 神经网络一般用GPU来跑,我们的神经网络框架一般也都安装的GPU版本,本文就简单记录一下GPU使用的编写。GPU的设置不在model,而是在
- js一共有9种数据类型,分别是:未定义(undefined)、空(null)、布尔型(boolean)、字符串(string)、数值(num
- 前面的话正则表达式是javascript操作字符串的一个重要组成部分,但在以往的版本中并未有太多改变。然而,在ES6中,随着字符
- 很多开发人员在使用MySQL时经常会在部分列上进行函数计算等,导致无法走索引,在数据量大的时候,查询效率低下。针对此种情况本文从MySQL5
- 因为要学着写渗透工具,这几天都在上python编程基础课,听得我打瞌睡,毕竟以前学过嘛。最后sherry老师留了作业,其中一道题是这样的:题
- 内容摘要:我们在浏览一些文章的时候,当鼠标选中一些文字的时候,就出现了一些隐藏的字符,平常是看不到的。这些干扰码一般有两种,一是随机无意义的
- 在学习python的时候,被推荐了使用PyCharm这款IDE,但是在import包的时候却发生了问题- -无法引入,但是明明通过了pip进
- 以下所有例子中结构定义如下:type User struct { Id_ bson.ObjectId `bson:
- ancestor:祖先adjacent:相邻algorithm:运算法则anonymous box:无名盒子。例: anonymous in
- 学习前言在前一段时间已经完成了卷积神经网络的复习,现在要对循环神经网络的结构进行更深层次的明确。RNN简介RNN 是当前发展非常火热的神经网