前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >涨薪5K必学高并发核心编程,限流原理与实战,分布式计数器限流

涨薪5K必学高并发核心编程,限流原理与实战,分布式计数器限流

作者头像
愿天堂没有BUG
发布2022-10-28 11:46:18
2860
发布2022-10-28 11:46:18
举报
文章被收录于专栏:愿天堂没有BUG(公众号同名)

分布式计数器限流

分布式计算器限流是使用Redis存储限流关键字key的统计计数。

这里介绍两种限流的实现方案:Nginx Lua分布式计数器限流和RedisLua分布式计数器限流。

实战:Nginx Lua分布式计数器限流

本小节以对用户IP计数器限流为例实现单IP在一定时间周期(如10秒)内只能访问一定次数(如10次)的限流功能。由于使用到Redis存储分布式访问计数,通过Nginx Lua编程完成全部功能,因此这里将这种类型的限流称为Nginx Lua分布式计数器限流。

本小节的Nginx Lua分布式计数器限流案例架构如图9-3所示。

图9-3 Nginx Lua分布式计数器限流架构

首先介绍限流计数器脚本RedisKeyRateLimiter.lua,该脚本负责完成访问计数和限流的结果判断,其中涉及Redis的存储访问,具体的代码如下:

代码语言:javascript
复制
local redisExecutor = require("luaScript.redis.RedisOperator");--一个统一的模块对象local _Module = {}_Module.__index = _Module--方法:创建一个新的实例function _Module.new(self, key) local object = { red = nil } setmetatable(object, self) --创建自定义的redis操作对象 local red = redisExecutor:new(); red:open(); object.red = red; object.key = "count_rate_limit:" .. key; return objectend--方法:判断是否能通过流量控制--返回值为true表示通过流量控制,返回值为false表示被限制function _Module.acquire(self) local redis = self.red; local current = redis:getValue(self.key); --判断是否大于限制次数 local limited = current and current ~= ngx.null and tonumber(current) > ; --限流的次数 --被限流 if limited then redis:incrValue(self.key); return false; end if not current or current == ngx.null then redis:setValue(self.key, ); redis:expire(self.key, ); --限流的时间范围 else redis:incrValue(self.key); end return true;end--方法:取得访问次数,供演示使用function _Module.getCount(self) local current = self.red:getValue(self.key); if current and current ~= ngx.null then return tonumber(current); end return ;end--方法:归还redis连接function _Module.close(self) self.red:close();endreturn _Module以上代码位于练习工程LuaDemoProject的src/luaScript/module/ratelimit/文件夹下,文件名称为RedisKeyRateLimiter.lua。然后介绍access_auth_nginx限流脚本,该脚本使用前面定义的RedisKeyRateLimiter.lua通用访问计算器脚本,完成针对同一个IP的限流操作,具体的代码如下:---此脚本的环境:nginx内部---启动调试--local mobdebug = require("luaScript.initial.mobdebug");--mobdebug.start();--导入自定义的计数器模块local RedisKeyRateLimiter = require("luaScript.module.ratelimit.RedisKeyRateLimiter");定义出错的输出对象--定义出错的JSON输出对象local errorOut = { resp_code = -1, resp_msg = "限流出错", datas = {} };--取得用户的iplocal shortKey = ngx.var.remote_addr;--没有限流关键字段,提示错误if not shortKey or shortKey == ngx.null then errorOut.resp_msg = "shortKey不能为空" ngx.say(cjson.encode(errorOut)); return ;end--拼接计数的redis keylocal key = "ip:" .. shortKey;local limiter = RedisKeyRateLimiter:new(key);local passed = limiter:acquire();--如果通过流量控制if passed then ngx.var.count = limiter:getCount(); --注意,在这里直接输出会导致content阶段的指令被跳过 --ngx.say( "目前的访问总数:",limiter:getCount(),"<br>");end--回收redis连接limiter:close();--如果没有流量控制,就终止nginx的处理流程if not passed then errorOut.resp_msg = "抱歉,被限流了"; ngx.say(cjson.encode(errorOut)); ngx.exit(ngx.HTTP_UNAUTHORIZED);endreturn ;

以上代码位于练习工程LuaDemoProject的 src/luaScript/module/ratelimit/文件夹下,文件名称为access_auth_nginx.lua。access_auth_nginx.lua在拼接计数器的key时使用了Nginx的内置变量$remote_addr获取客户端的IP地址,最终在Redis存储访问计数的key的格式如下:

代码语言:javascript
复制
count_rate_limit:ip:192.168.233.1

这里的192.168.233.1为笔者本地的测试IP,存储在Redis中针对此IP的限流计数结果如图9-4所示。

图9-4 存储在Redis中针对此IP的限流计数结果

在Nginx的access请求处理阶段,使用access_auth_nginx.lua脚本进行请求限流的配置代码如下:

代码语言:javascript
复制
location = /access/demo/nginx/lua { set $count ; access_by_lua_file luaScript/module/ratelimit/access_auth_nginx.lua; content_by_lua_block { ngx.say( "目前的访问总数:",ngx.var.count,"<br>"); ngx.say("hello world!"); }}

以上配置位于练习工程LuaDemoProject的src/conf/nginxratelimit.conf文件中,在使之生效之前,需要在openresty-start.sh脚本中换上该配置文件,然后重启Nginx。

接下来,开始限流自验证。

上面的代码中,由于RedisKeyRateLimiter所设置的限流规则为单IP在10秒内限制访问10次,所以,在验证的时候,在浏览器中刷新10次之后就会被限流。在浏览器中输入如下测试地址:

代码语言:javascript
复制
http://nginx.server/access/demo/nginx/lua?seckillGoodId=1

10秒内连续刷新,第6次的输出如图9-5所示。

图9-5 自验证时第6次刷新的输出

10秒之内连续刷新,发现第10次之后请求被限流了,说明Lua限流脚本工作是正常的,被限流后的输出如图9-6所示。

图9-6 自验证时刷新10次之后的输出

以上代码有两点缺陷:

(1)数据一致性问题:计数器的读取和自增由两次Redis远程操作完成,如果存在多个网关同时进行限流,就可能会出现数据一致性问题。

(2)性能问题:同一次限流操作需要多次访问Redis,存在多次网络传输,大大降低了限流的性能。

实战:Redis Lua分布式计数器限流

大家知道,Redis允许将Lua脚本加载到Redis服务器中执行,可以调用大部分Redis命令,并且Redis保证了脚本的原子性。由于既使用Redis存储分布式访问计数,又通过Redis执行限流计数器的Lua脚本,因此这里将这种类型的限流称为RedisLua分布式计数器限流。

本小节的Redis Lua分布式计数器限流案例的架构如图9-7所示。

图9-7 Redis Lua分布式计数器限流架构

首先来看限流的计数器脚本redis_rate_limiter.lua,该脚本负责完成访问计数和限流结果的判断,其中会涉及Redis计数的存储访问。需要注意的是,该脚本将在Redis中加载和执行。

计数器脚本redis_rate_limiter.lua的代码如下:

代码语言:javascript
复制
---此脚本的环境:redis内部,不是运行在Nginx内部--返回表示被限流,返回其他表示统计的次数local cacheKey = KEYS[]local data = redis.call("incr", cacheKey)local count=tonumber(data)--首次访问,设置过期时间if count ==  then redis.call("expire", cacheKey, ) --设置超时时间秒endif count >  then --设置超过的限制为人表示需要限流 return ; --表示需要限流end--redis.debug(redis.call("get", cacheKey))return count;

以上代码位于练习工程LuaDemoProject的 src/luaScript/module/ratelimit/文件夹下,文件名为redis_rate_limiter.lua。在调用该脚本之前,首先要将其加载到Redis,并且获取其加载之后的sha1编码,以供Nginx上的限流脚本access_auth_evalsha.lua使用。

将redis_rate_limiter.lua加载到Redis的Linux Shell命令如下:

代码语言:javascript
复制
[root@localhost ~]#cd /work/develop/LuaDemoProject/src/luaScript/module/ratelimit/[root@localhost ratelimit]#/usr/local/redis/bin/redis-cli script load "$(cat redis_rate_limiter.lua)""2c95b6bc3be1aa662cfee3bdbd6f00e8115ac657"

然后来看access_auth_evalsha.lua限流脚本,该脚本使用Redis的evalsha操作指令,远程访问加载在Redis上的redis_rate_limiter.lua访问计算器脚本,完成针对同一个IP的限流操作。

access_auth_evalsha.lua限流脚本的代码如下:

代码语言:javascript
复制
---此脚本的环境:nginx内部local RedisKeyRateLimiter = require("luaScript.module.ratelimit.RedisKeyRateLimiter");--定义出错的JSON输出对象local errorOut = { resp_code = -, resp_msg = "限流出错", datas = {} };--读取get参数local args = ngx.req.get_uri_args()--取得用户的iplocal shortKey = ngx.var.remote_addr;--没有限流关键字段,提示错误if not shortKey or shortKey == ngx.null then errorOut.resp_msg = "shortKey不能为空" ngx.say(cjson.encode(errorOut)); return ;end--拼接计数的redis keylocal key = "count_rate_limit:ip:" .. shortKey;local limiter = RedisKeyRateLimiter:new(key);local passed = limiter:acquire();--如果通过流量控制if passed then ngx.var.count = limiter:getCount(); --注意,在这里直接输出会导致content阶段的指令被跳过 --ngx.say( "目前的访问总数:",limiter:getCount(),"<br>");end--回收redis连接limiter:close();如果没有流量控制就终止的处理流程--如果没有流量控制,就终止Nginx的处理流程if not passed then errorOut.resp_msg = "抱歉,被限流了"; ngx.say(cjson.encode(errorOut)); ngx.exit(ngx.HTTP_UNAUTHORIZED);endreturn ;

以上代码位于练习工程LuaDemoProject的 src/luaScript/module/ratelimit/文件夹下,文件名为access_auth_evalsha.lua。在Nginx的access请求处理阶段,使用access_auth_evalsha.lua脚本进行请求限流的配置如下:

代码语言:javascript
复制
 location = /access/demo/evalsha/lua { set $count ; access_by_lua_file luaScript/module/ratelimit/access_auth_evalsha.lua; content_by_lua_block { ngx.say( "目前的访问总数:",ngx.var.count,"<br>"); ngx.say("hello world!"); }}

以上配置位于练习工程LuaDemoProject的 src/conf/nginx-ratelimit.conf文件中,在使之生效之前需要在openresty-start.sh脚本中换上该配置文件,然后重启Nginx。

接下来开始限流自验证。在浏览器中访问以下地址:

代码语言:javascript
复制
http://nginx.server/access/demo/evalsha/lua

10秒之内连续刷新,发现第10次之后请求被限流了,说明Redis内部的Lua限流脚本工作是正常的,被限流后的输出如图9-8所示。

图9-8 自验证时刷新10次之后的输出

通过将Lua脚本加载到Redis执行有以下优势:

(1)减少网络开销:不使用Lua的代码需要向Redis发送多次请求,而脚本只需一次即可,减少网络传输。

(2)原子操作:Redis将整个脚本作为一个原子执行,无须担心并发,也就无须事务。

(3)复用:只要Redis不重启,脚本加载之后会一直缓存在Redis中,其他客户端可以通过sha1编码执行。

本文给大家讲解的内容是高并发核心编程,限流原理与实战,分布式计数器限流

  1. 下篇文章给大家讲解的是高并发核心编程,限流原理与实战,Nginx漏桶限流详解;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 愿天堂没有BUG 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 分布式计数器限流
  • 实战:Nginx Lua分布式计数器限流
  • 实战:Redis Lua分布式计数器限流
  • 本文给大家讲解的内容是高并发核心编程,限流原理与实战,分布式计数器限流
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档