系统限流实践,限流实践


本文是根据开涛的博客 聊聊高并发系统之限流特技-2 整理而成,自学笔记第二篇
欢迎访问我的个人博客 http://rayleung.xyz/

目录

1.系统限流实践 - 理论篇
2.系统限流实践 - 应用限流
3.系统限流实践 - 分布式限流
4.系统限流实践 - 接入层限流(上)
5.系统限流实践 - 接入层限流(下*完结)

开篇

上篇学习了通过Nginx模块进行限流的方法(传送门),接下来学习一下利用Nginx+Lua进行接入层限流

Openresty提供了lua-resty-limit-traffic模块进行限流,模块实现了limit.connlimit.req的功能和算法。参考链接

实践

添加模块

这里使用的是windows的openresty环境,具体安装配置请参考windows版openresty安装或者自行百度
首先需要下载lua-resty-limit-traffic模块

然后把zip包解压出来,进入lib/resty目录下,会看到有一个叫limit文件夹,里面存放的就是我们需要的模块;最后把limit文件夹复制拷贝到openresty根目录下的nginx/resty目录下

resty.limit.req模块

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
            ';
        }
    }
}

方法说明

new

语法: obj, err = class.new(shdict_name, rate, burst)
成功的话会返回resty.limit.req对象,失败的话返回nil和一个描述错误原因的字符串值

incoming

语法: 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

配置nginx.conf

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

欢迎关注个人公众号

相关内容

    暂无相关文章