这是张富涛的第15篇原创

用令牌桶算法完成API接口限流

本文介绍了“令牌桶算法”,和使用lua+redis实现基于令牌桶算法的限流。

1. 限流需求的产生背景

软件开发时偶尔会面临高并发或突发流量,经典的情况是秒杀业务或者是某明星发了爆炸性的微博,很可能因为下游的服务器处理能力不足导致程序异常,甚至造成服务雪崩。

面对高并发或突发流量场景的解决方案之一是“限流”,通过在架构中的网关层进行限制单位时间内的最大请求数量,达到保护后端服务的作用。

熟悉淘宝双11的读者一定见过这个截图,这样的截图一般更容易出现在临近活动结束的时候,访问API的人太多,淘宝会在网关层进行限流。

“令牌桶算法”是完成限流功能的常用方法,本人在开发API网关的“限流”模块时,尝试运用了“令牌桶算法”,到现在上线足有一年多,效果较好,鉴于开发成本较低,研发思路可以公式化照搬,且网上并没有类似的实践分享,于是写出此文向大家分享一些心得。

2. “限流”功能的期望效果

限流一般需要设定两个值,一个是“单位时间”,一个是“行动次数”,比如我限制我的后端API最多:

  • “1秒钟/10次”
  • 或者"1分钟/1000次"

如果要实现这种效果最简单的方案是使用“令牌桶算法”完成。

3. 什么是令牌桶算法

令牌桶算法的概念里总共有2个桶:

  • 一个桶叫“令牌桶”,里面装满了令牌,明确标明最多装有多少令牌,这些令牌就是调用的次数,调用一次API会消耗一个令牌,令牌没了将不能调用API。这就是单位时间内能调用多少次API,最多调用的次数即“令牌桶”中令牌的最大承载数。

  • 而另一个桶叫“补桶”,补桶里装有所有的令牌,令牌数等于这个API的“总可调用的次数”,“补桶”定期向“令牌桶”里补令牌,每次补令牌后“令牌桶”中的令牌的总数不能多于令牌桶的最大承载数。因为“令牌桶”用一次就花掉一个令牌,而补桶多久向令牌桶补一次令牌,即是“限流的单位时间”。两个桶进行协调合作就能完成限流。

在对API进行限流时,一般补桶中的令牌可以视为无穷大。

4. 实现令牌桶算法的研发思路

4.1. 技术选型

API调用的特性是高频、多次的,每次调用API前,会检查“令牌桶”中的令牌,并消耗令牌,将令牌数“-1”,所以该信息应该在内存中操作,又由于API网关或流量控制入口经常是集群部署方案,故建议将该信息放入NoSql中,这里选择的是redis进行管理“令牌桶中的令牌数”。

假设在流量高峰时,并发数会极多,故在对令牌桶的令牌数做“检查”和“减少”时,需要注意“原子性”问题。因为redis为了支持原子性的操作,可以允许执行lua脚本,故选择用lua脚本完成“消耗令牌”的操作。

4.2. 什么是“原子性”?

原子是化学反应不可再分的基本微粒。所以原子性指事务的不可分割性,一个事务的所有操作要么不间断地全部被执行,要么一个也没有执行。

比如在进行操作令牌时,大家看下,如果用下面这种方案,是否会出现原子性问题呢?

具体出现原子性问题的情况如下图,这与mysql的事物像:

上图在令牌桶中令牌仅剩1个的情况,同时来了多个请求,因为“检查”和“消耗令牌”的操作是非原子性的,所以我们应该将其作为原子(事物)执行。

4.3. lua in redis 解决原子性问题

lua是十分精巧的脚本语言,设计目的就是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。lua由标准C编写而成,代码简洁优美,几乎在所有操作系统和平台上都可以编译,运行。一个完整的lua解释器不过200k,在目前所有脚本引擎中,Lua的速度是最快的。这一切都决定了lua是作为嵌入式脚本的最佳选择。

比如有些游戏更新时,运行更新器后,更新器会从网络上down一段lua代码并执行,完成程序的更新。

使用redis中执行lua的好处是:

  1. 减少网络开销:可以将多个请求通过脚本的形式一次发送,减少网络时延和请求次数。
  2. 原子性的操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。
  3. 代码复用:客户端发送的脚步会永久存在redis中,这样,其他客户端可以复用这一脚本来完成相同的逻辑。

我们使用lua完成redis中的令牌桶操作后,将会解决高并发的问题:

其中因为Redis中的操作为原子性,故不用担心高并发问题。

4.4. 令牌桶设计

我们将api的名称作为redis的key,value为令牌桶中的令牌,限流时间为key的过期时间。这样如果key已过期代表上个限流单位已经过去(redis的特性key过期会不存在),则直接创建key,并扣除一次。如果未过期直接扣除令牌。

假如“api_name()”这个API的限流为“10次/1秒”,我们可以设置Redis的key=api_name,value=10,expires(过期时间)=1。

注意:在这里不用关心value是否会变为0以下,如果变为0以下则代表令牌用光了,则在声明周期内,每次访问会不断进行“减1”,则一直返回被限流状态。而key过期后重新访问,则会重新创建key并完成“补令牌”操作。

4.5. 符合业务的令牌桶优化

限流的单位不止为秒级,偶尔会较长如“10次/分”或“10次/小时”,这样势必面临相邻单位时间会共享次数的问题,举个例子,如果设置为“10次/小时”,而基于4.4节的设计,第一次访问API的时间在10:30,即10:30~11:30之间,最多允许访问10次。但这往往是解释不清的,比如提问:“限流是一小时10次,都从10点进入11点,次数不应该刷新了吗,为什么我还不能访问?”

所以在此基础,建议将redis中的key由“api名称”变为“api名称+当前时间”,如:第一次访问API时,key:“api_name:2021031901” 这样将会在当前小时内完成api的限流。

5. lua代码分享

local flag = redis.call('EXISTS', KEYS[1])
if flag == 0thenredis.call('INCRBY',KEYS[1],ARGV[1]-1)redis.call('EXPIRE',KEYS[1],ARGV[2])return trueend
local number =redis.call('DECR', KEYS[1])
if number >= 0then return trueend
return false

---------------

公众号:张富涛的学习笔记(ID:futaoNT)

知乎:张富涛

CSDN:张富涛

这是一个在夜晚可以靠编程拯救世界的程序员,关注他将在第一时间获悉他的知识、工作心得!

长按下图二维码关注:

用令牌桶算法完成API接口限流相关推荐

  1. Springboot+Redis 实现API接口限流

    添加Redis的jar包. <dependency><groupId>org.springframework.boot</groupId><artifactI ...

  2. api接口限流 防止恶意刷接口

    api限流的场景 限流的需求出现在许多常见的场景中 1.秒杀活动,有人使用软件恶意刷单抢货,需要限流防止机器参与活动 2.某api被各式各样系统广泛调用,严重消耗网络.内存等资源,需要合理限流 3.淘 ...

  3. 14 基于网关Spring Cloud Zuul的接口限流实现方案

    在Spring Cloud Zuul网关中,限流业务是放在前置过滤器实现的,也就是在请求被Zuul转发给微服务之前进行限流.另外,当前置过滤器中同时存在限流.鉴权.身份认证等业务时,应该将限流业务放在 ...

  4. c++ lua 可以做什么_Redis令牌桶算法(全网最全,后续可以接入lua做原子性操作)...

    一 .场景描述 在开发接口服务器的过程中,为了防止客户端对于接口的滥用,保护服务器的资源, 通常来说我们会对于服务器上的各种接口进行调用次数的限制.比如对于某个 用户,他在一个时间段(interval ...

  5. 接口限流算法:漏桶算法令牌桶算法

    工作中对外提供的API 接口设计都要考虑限流,如果不考虑限流,会成系统的连锁反应,轻者响应缓慢,重者系统宕机,整个业务线崩溃,如何应对这种情况呢,我们可以对请求进行引流或者直接拒绝等操作,保持系统的可 ...

  6. 接口限流算法:漏桶算法amp;令牌桶算法

    转载自 接口限流算法:漏桶算法&令牌桶算法 背景 每一个对外提供的API接口都是需要做流量控制的,不然会导致系统直接崩溃.很简单的例子,和保险丝的原理一样,如果用电符合超载就会烧断保险丝断掉电 ...

  7. PHP+Redis令牌桶算法 接口限流

    在开发接口服务器的过程中,为了防止客户端对于接口的滥用,保护服务器的资源, 通常来说我们会对于服务器上的各种接口进行调用次数的限制.比如对于某个 用户,他在一个时间段(interval)内,比如 1 ...

  8. 接口限流算法:漏桶算法令牌桶算法。

    背景 每一个对外提供的API接口都是需要做流量控制的,不然会导致系统直接崩溃.很简单的例子,和保险丝的原理一样,如果用电符合超载就会烧断保险丝断掉电源以达到保护的作用.API限流的意义也是如此,如果A ...

  9. 使用令牌桶算法解决调用第三方接口限流问题

    我们在调用第三方接口时常常会碰到接口限流问题,为了解决这一问题,大家想出了许多方法.我这里介绍一下我的方法,第三方接口限流一般是基于令牌桶算法的,那么我们可以以彼之道还治彼身,使用令牌桶算法实现我方调 ...

最新文章

  1. 计算机程序的思维逻辑 (43) - 剖析TreeMap
  2. VS Code 2022路线图:大量Spring Boot优化提上日程!难道是被JB Code吓到了?
  3. 基于MIPS架构的BackTrace实现
  4. 计算机网络工程与菅理,网络工程与管理
  5. wamp解决ajax跨域问题
  6. Ble Connection Events(转载)
  7. Tensorflow实战系列之五:
  8. 物联网安防技术融合在细分领域的应用分析
  9. P3293-[SCOI2016]美味【主席树】
  10. 记录一个奇葩问题 宝塔 nginx: [warn] conflicting server
  11. 文献读的越多,离原创越远
  12. mysql写下拉树_PHP+mysql实现从数据库获取下拉树功能的方法
  13. Linux用户空间与内核空间(理解高端内存)
  14. 2089. 找出数组排序后的目标下标
  15. 强悍的 Linux —— 权限管理(组及用户管理)
  16. php env 函数不存在,php-Laravel 5.2无法读取env fi
  17. linux下的16进制编辑器,在Linux上使用十六进制编辑器 | MOS86
  18. CAPL中的键值对(hash)数据类型
  19. ps手机计算机图标教程,ps制作手机图标的方法
  20. Clock skew too great

热门文章

  1. “AI司机”赋能RoboTaxi,驭势科技开启全场景自动驾驶下半场
  2. ckc交易什么意思_31伦敦金交易开户,股票601398,股票三条线是什么意思
  3. Tranform + Transitions + Animation
  4. ROS :Tranform出错简易处理 tf出错现象及解析
  5. Matlab中的 imread 函数
  6. 表格table以及其属性
  7. 软件测试一般流程及方法
  8. python学习之socket模块网络通信的异常信息汇总
  9. wps文档怎么给整段文字加框_WPS文字技巧—如何为WPS文档增加页面特效边框
  10. 如何使用 Python 构建 西门子PLC Snap7通信