百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

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

mhr18 2024-11-20 18:48 23 浏览 0 评论

分布式计数器限流

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

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

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

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

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

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

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 object
end
--方法:判断是否能通过流量控制
--返回值为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) > 10; --限流的次数
 --被限流
 if limited then
 redis:incrValue(self.key);
 return false;
 end
 if not current or current == ngx.null then
 redis:setValue(self.key, 1);
 redis:expire(self.key, 10); --限流的时间范围
 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 0;
end
--方法:归还redis连接
function _Module.close(self)
 self.red:close();
end
return _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 = {} };
--取得用户的ip
local 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 key
local 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);
end
return ;

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

count_rate_limit:ip:192.168.233.1

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

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

location = /access/demo/nginx/lua {
 set $count 0;
 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次之后就会被限流。在浏览器中输入如下测试地址:

http://nginx.server/access/demo/nginx/lua?seckillGoodId=1

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

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

以上代码有两点缺陷:

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

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

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

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

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

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

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

---此脚本的环境:redis内部,不是运行在Nginx内部
--返回0表示被限流,返回其他表示统计的次数
local cacheKey = KEYS[1]
local data = redis.call("incr", cacheKey)
local count=tonumber(data)
--首次访问,设置过期时间
if count == 1 then
 redis.call("expire", cacheKey, 10) --设置超时时间10秒
end
if count > 10 then --设置超过的限制为10人
表示需要限流 return 0; --0表示需要限流
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命令如下:

[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限流脚本的代码如下:

---此脚本的环境:nginx内部
local RedisKeyRateLimiter = require("luaScript.module.ratelimit.RedisKeyRateLimiter");
--定义出错的JSON输出对象
local errorOut = { resp_code = -1, resp_msg = "限流出错", datas = {} };
--读取get参数
local args = ngx.req.get_uri_args()
--取得用户的ip
local 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 key
local 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);
end
return ;

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

 location = /access/demo/evalsha/lua {
 set $count 0;
 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。

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

http://nginx.server/access/demo/evalsha/lua

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

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

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

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

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

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

  1. 下篇文章给大家讲解的是高并发核心编程,限流原理与实战,Nginx漏桶限流详解;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: