[date: 2021-08-01 16:38] [visits: 23]

OpenResty限频限流

之前对系统限流这个话题有过一次讨论,之后有在实际工作中遇到一次应用,写一篇文章记录本次实践。

背景

目前在做的系统有提供Open API接入,即客户通过API Key接入使用系统功能。所允许接入的API根据功能种类不同所需系统开销有所差异,从技术角度希望对系统增加一些保护措施,避免用户接入使用过程有意或无意高频调用一些API对系统构成压力,从而影响整个系统稳定性。

限制主要考虑两个维度:

如上描述,限频、限流本质是同一件事:“限制一定时间内的调用次数”,但此处特意使用两个不同的名词用于体现差异性(后文会多次提及):

在目标系统中针对特定API(POST /api/order),期望实现:“每秒调用不能超过20次,每分钟调用不能超过200次”,前者称之为限频,后者称为限流。

方案

限频限流可以选择在应用层实现,早期在一些项目中的确有借助Redis实践过,结论是只能满足前期需要,后期系统QPS过高时应用层与Redis的开销不容小觑。

当前项目的Open API网关是OpenResty,选择把限频限流做在网关这一层是个不错的选择,重点依赖OpenResty官方提供的几个Lua扩展:

借助上述OpenResty Lua扩展,除实现限频限流功能外,还期望能对用户提供一些“反馈”信息:

最初希望直接使用resty.limit.traffic达到目的,但在阅读与熟悉官方文档后,发现上述反馈信息需求无法被满足,最终选择参考resty.limit.traffic代码进行一些调整以达到目的。

实现

针对API调用,限频限流已实现特性列表:

代码不多也不少,请移步Gist查看:

OpenResty相关配置:

http {
    lua_package_path "/usr/local/openresty/lualib/?.lua;/usr/local/openresty/nginx/conf/lua/?.lua;;"; 
    
    # for limit
    lua_shared_dict limit 2m;
    lua_shared_dict limit_req_store 16m;
    lua_shared_dict limit_count_store 16m;
    lua_shared_dict limit_conn_store 16m;
    
    init_worker_by_lua_file init_worker.lua;
    
    server {
        # ...
        access_by_lua_file access.lua;
        log_by_lua_file log.lua;
    }
}

待实现特性列表:

补充

针对resty.limit.req以及burst额外补充一些知识以便更深入理解限频与限流的区别。文章开始用“单位时间”描述限频,“时间窗口”描述限流,单位时间全文下来都假定为秒,这是为了更通用的表达和理解。但在Nginx内部实现的单位是毫秒,那么针对描述“每秒请求数不超过10”的理解应该是“每100毫秒的请求数不超过1”。

在一秒的开始时刻服务器接收到10个请求,按每100毫秒请求数不超过1原则,此时系统只会处理1个请求,剩余9个被拒绝。设置burst=10可解决该问题,让系统不拒绝剩余9个请求,从外部观察者角度即是1秒处理10个请求,没有触发限制。

结语

对于实现部分很多细节没有剖析和描述,主要由于此文在实践很久后才写,记忆力有些模糊。

最近两年因工作太忙,关注一时所得而忽视了长期成长,长久来看还是得不偿失,因此选择继续更新文章。

参考资料