本文是根据开涛的博客 聊聊高并发系统之限流特技-2 整理而成,自学笔记第二篇
欢迎访问我的个人博客 http://rayleung.xyz/
1.系统限流实践 - 理论篇
2.系统限流实践 - 应用限流
3.系统限流实践 - 分布式限流
4.系统限流实践 - 接入层限流(上)
5.系统限流实践 - 接入层限流(下*完结)
上篇学习了通过Nginx模块进行限流的方法(传送门),接下来学习一下利用Nginx+Lua进行接入层限流
Openresty提供了lua-resty-limit-traffic模块进行限流,模块实现了limit.conn和limit.req的功能和算法。参考链接
这里使用的是windows的openresty环境,具体安装配置请参考windows版openresty安装或者自行百度
首先需要下载lua-resty-limit-traffic模块
然后把zip包解压出来,进入lib/resty目录下,会看到有一个叫limit文件夹,里面存放的就是我们需要的模块;最后把limit文件夹复制拷贝到openresty根目录下的nginx/resty目录下
resty.limit.req使用漏桶算法,它与Nginx的标准模块ngx_limit_req类似,不过resty.limit.req使用更灵活,可以在绝大部分的上下文上使用
参考链接
http {
lua_shared_dict my_limit_req_store 100m;
server {
location / {
access_by_lua '
local limit_req = require "resty.limit.req"
-- 限制请求速率为200 req/sec,并且允许100 req/sec的突发请求
-- 就是说我们会把200以上300一下的请求请求给延迟
-- 超过300的请求将会被拒绝
local lim, err = limit_req.new("my_limit_req_store", 200, 100)
if not lim then --申请limit_req对象失败
ngx.log(ngx.ERR,
"failed to instantiate a resty.limit.req object: ", err)
return ngx.exit(500)
end
-- 下面代码针对每一个单独的请求
-- 使用ip地址作为限流的key
local key = ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, true)
if not delay then
if err == "rejected" then
return ngx.exit(503)
end
ngx.log(ngx.ERR, "failed to limit req: ", err)
return ngx.exit(500)
end
if delay > 0 then
-- 第二个参数(err)保存着超过请求速率的请求数
-- 例如err等于31,意味着当前速率是231 req/sec
local excess = err
-- 当前请求超过200 req/sec 但小于 300 req/sec
-- 因此我们sleep一下,保证速率是200 req/sec,请求延迟处理
ngx.sleep(delay) --非阻塞sleep(秒)
end
';
}
}
}
语法: obj, err = class.new(shdict_name, rate, burst)
成功的话会返回resty.limit.req
对象,失败的话返回nil
和一个描述错误原因的字符串值
语法: delay, err = obj:incoming(key, commit)
key这里是指需要限流的ip;commit真心没看懂(囧),先按照例子传true
返回值根据情况的不同返回不同的值
1.如果请求没超过速率,那么delay和err返回0
2.如果请求超过速率但没超过“速率+burst”的值,那么delay将会返回一个合适的秒数,告诉你多久后这个请求才会被处理;第二个参数(err)保存着超过请求速率的请求数量
3.如果请求超过“速率+burst”的值,那么delay会返回nil,err会返回”rejected”字符串
4.如果一个error发生了,delay会返回nil,err会返回具体错误的字符串描述
inconing方法不会sleep自己,需要调用者调用’ngx.sleep’去延迟请求处理。
http {
......
lua_shared_dict limit_req_store 10m;
......
}
resty-limit.lua
local limit_req = require "resty.limit.req"
local rate = 2 --固定平均速率2r/s
local burst = 10 --桶容量
local error_status = 503
local nodelay = false --是否需要不延迟处理
local lim, err = limit_req.new("limit_req_store", rate, burst)
if not lim then --没定义共享字典
ngx.exit(error_status)
end
local key = ngx.var.binary_remote_addr --IP维度限流
--请求流入,如果你的请求需要被延迟则返回delay>0
local delay, err = lim:incoming(key, true)
if not delay and err == "rejected" then
ngx.exit(error_status)
end
if delay > 0 then --根据需要决定延迟或者不延迟处理
if nodelay then
--直接突发处理
else
ngx.sleep(delay) --延迟处理
end
end
http {
......
server {
location /resty-limit {
access_by_lua_file /path/to/your/resty-limit.lua;
echo "you success";
}
}
......
}
把限流的配置放在nginx的access阶段,如果限流的话,不会输出you success
。
public class NginxLimit {
public static void main(String[] args) throws IOException, InterruptedException {
final NginxLimit distrubuteLimit = new NginxLimit();
final CountDownLatch latch = new CountDownLatch(1);//两个工人的协作
for (int i = 0; i < 10; i++) {
final int finalI = i;
Thread t = new Thread(new Runnable() {
public void run() {
try {
latch.await();
String rev = distrubuteLimit.sendGet("http://localhost:9998/resty-limit", null);
System.out.println(rev);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
latch.countDown();
System.in.read();
}
}
并发产生10个请求
you success
发送GET请求出现异常
发送GET请求出现异常
发送GET请求出现异常
发送GET请求出现异常
发送GET请求出现异常
发送GET请求出现异常
you success
you success
you success
根据lua文件的配置,10个请求里应该有5个成功,5个失败,但是实际观察只有4个成功,与设想有点误差。试过用不同的配置去测试,都一直存在这个问题,误差为1,这里可以在以后研究为什么出现这个问题。
ngx_limit_req配置上更加灵活,不过测试中发现有一个缺点,就是与nginx的ngx_http_limit_req_module没有允许一定程度的并发。大家可以根据各自的使用场景决定用那种发发实现限流。
聊聊高并发系统之限流特技-2
lua-resty-limit-traffic - https://github.com/openresty/lua-resty-limit-traffic
欢迎关注个人公众号