由于业务需要,现阶段需开发一个限制客户端上传速度的模块,在网上看了很多资料,有基于Nginx 第三方模块实现的,有使用内置的Nginx模块的,自己认为是可以通过OpenResty内置的API去实现的,但是不确定会不会有问题,就想问一下各位,有没有什么好的解决方案?多谢!
个人感觉挺疑惑的, 为什么上传还需要限速呢? 不过不考虑目的, 也可以探讨一下功能方面的实现到底应该怎么办. 于是我找了一些 nginx 限制上传速度的实现, 不过结果主要是 limit_rate 这个指令, 这个指令是用来限制回复的速度的. 另一个介绍的比较多的是
client_max_body_size, 这个指令是用来限制客户端上传的文件大小. 和功能描述距离比较远. 最后找到了一个已经不活跃的模块, limit_upload_rate, 这个 nginx 的 c 模块提供了一些指令达到限速的目的, 瞄了一眼源码, 了解到应该是在
input filter
阶段做上传限速这个功能.
知道应该在什么阶段做这个事情之后, 应该就去找 openresty 提供的 api 了, 在 openresty 邮件列表找到了这篇帖子, 春哥在这篇帖子里面解释了由于 ngx_lua 的内在机制, 并没有实现跟 nginx 完全相同的 input filter
阶段, 但是也提供了另外的思路, 就是利用 ngx.req.socket 与 ngx.req.init_body(), ngx.req.append_body(), 和 ngx.req.finish_body() 这几个 api 结合, 可以模拟出 input filter
相同的功能, 知道了相关的 api 之后, 实现的话, 就没有太大的难度了. 具体的 api 介绍在 lua-nginx-module , 我不再介绍重复的内容.
附上我的实现代码, 如果你想要试一下的话可以看我的 github仓库, 我在这里阐述一下 api 文档里面找不到的东西.
- 关于 sock:receive 返回的 err == “closed” 判断
这个示例是我通过在 lua-nginx-module 仓库搜索 ngx.req.init_body 得到的信息, 044-req-body.t 这是一个测试文件, 你可以在 1216
行找到这个判断, 然后我通过 postman, wireshark 抓包, 验证了这个 err 应该就是 openresty 提示请求体读取完毕, 因为我在测试过程中, 并没有发现 postman 有关闭写端, 发送 FIN 包, 这个连接也是 keepalive 的, 所以这个用法应该是正确的.
- 关于限流算法
这个限流算法比较简单, 累加接收到的数据大小, 然后计算, 这个数据量, 在当前的限速条件下, 应该需要传输多少秒, 这是期待值, 用期待值与真正流逝的时间作差, 这样的话, 我们判断两者的差值, 当期待值大于当前当前花费的时间时, 就应该直接挂起差值这么久, 而期待值小于流逝的时间时, 代表客户端并没有跑满我们的限速, 可以继续读取.
- 关于参数的调整
sock:receive 的参数是 1 * 1024, 也就是每次读取 1kb, 可能会导致 ngx.req.append_body 的调用次数比较多, 应该可以根据精度要求调整这个值.
ngx.sleep 事实上在需要 sleep 时, 挂起时间可以比期待值与实际时间的差值, 也就是代码中的 interval
稍大, 因为客户端的速度已经快于我们的限速了, 我们可以适当的加上一点惩罚值, 来平衡它之后的速度, 如果我们只是挂起一个恰当好的时间, 它之后还是会因为超速被挂起. 加上惩罚值之后, 从性能方面考虑的话, 对于一个大文件, 应该可以节省很多次 ngx.sleep 的调用.
- 关于时间精度
ngx.now 返回的是 nginx 缓存的时间, 可能也会对精度有一定的影响. 如果想要更精确, 可以在 ngx.now 之前, 强制 nginx 更新时间. 不过个人感觉没有必要.
评论前必须登录!
注册