Web应用程序限速方法

一般来说Web应用程序的开发者不太关心网络限速的问题。所以通常写的程序逻辑基本认为用户提交上来的数据速率越快越好;用户下载文件时,下载越快越好。但现实情况是服务器的带宽不是无限的,通常我们并不希望某一个用户的极速下载导致其它用户感觉此Web应用程序不可用。这样就带来了网络速率的需求。我在实际工作中大概总结出好几种限速办法,在这里记录以备忘。

ngx_http_core_module限制下载速率

最简单是直接使用ngx_http_core_module中的limit_ratelimit_rate_after指令,如下

location /flv/ {
    alias /www/flv/;
    limit_rate_after 500k;
    limit_rate       50k;
}

limit_rate可限制响应传输至浏览器客户端的速率,limit_rate_after表示浏览器客户端下载多少后才可以执行限速(使下载小文件不受限,下载大文件才限速)。

limit_rate还有一种配合后端被代理服务器的用户,如下

location /download/ {
    proxy_pass http://127.0.0.1:8080/download/; # proxied server return "X-Accel-Limit-Rate" response header
}

后端被代理服务器可返回X-Accel-Limit-Rate响应头,nginx将根据这个响应头设置的值进行限速。这样就可以灵活控制限速的逻辑(比如有些用户下载不限速,有些用户下载限速,而且限速的数值也可根据不同用户身份而不同)

nginx-upload-module限制上传速率

location /upload {
        # 转到后台处理URL,表示Nginx接收完上传的文件后,然后交给后端处理的地址
        upload_pass @backend;

        # 临时保存路径, 可以使用散列
        # 上传模块接收到的文件临时存放的路径, 1 表示方式,该方式是需要在/tmp/nginx_upload下创建以0到9为目录名称的目录,上传时候会进行一个散列处理。
        upload_store /tmp/nginx_upload;

        # 上传文件的权限,rw表示读写 r只读
        upload_store_access user:rw group:rw all:rw;

        set $upload_field_name "file";
        # upload_resumable on;

        # 这里写入http报头,pass到后台页面后能获取这里set的报头字段
        upload_set_form_field "${upload_field_name}_name" $upload_file_name;
        upload_set_form_field "${upload_field_name}_content_type" $upload_content_type;
        upload_set_form_field "${upload_field_name}_path" $upload_tmp_path;

        # Upload模块自动生成的一些信息,如文件大小与文件md5值
        upload_aggregate_form_field "${upload_field_name}_md5" $upload_file_md5;
        upload_aggregate_form_field "${upload_field_name}_size" $upload_file_size;

        # 允许的字段,允许全部可以 "^.*$"
        upload_pass_form_field "^.*$";
        # upload_pass_form_field "^submit$|^description$";

        # 每秒字节速度控制,0表示不受控制,默认0, 128K
        upload_limit_rate 0;

        # 如果pass页面是以下状态码,就删除此次上传的临时文件
        upload_cleanup 400 404 499 500-505;

        # 打开开关,意思就是把前端脚本请求的参数会传给后端的脚本语言,比如:http://192.168.1.251:9000/upload/?k=23,后台可以通过POST['k']来访问。
        upload_pass_args on;
    }

    location @backend {
        proxy_pass http://127.0.0.1:8080/process_upload;
    }

upload_limit_rate即可对上传速率进行限制。

ngx_stream_proxy_module限制上传下载速率

server {
    listen 81;
    proxy_pass 127.0.0.1:8081;
    proxy_download_rate 200k;
    proxy_upload_rate 200k;
}

使用ngx_stream_proxy_module的好处时只要是tcp或udp协议且使用nginx作反向代理,都可以限速。proxy_download_rate可限制下载速率,proxy_upload_rate可限制上传速率。

Java使用Guava的RateLimiter进行限速

上面说的全是使用nginx配置的方式进行限速,当有很特殊需求时,我们也可以使用程序来限速,如Java可使用GuavaRateLimiter进行限速。

RateLimiter 从概念上来讲,速率限制器会在可配置的速率下分配许可证。如果必要的话,每个acquire() 会阻塞当前线程直到许可证可用后获取该许可证。一旦获取到许可证,不需要再释放许可证。

RateLimiter使用的是一种叫令牌桶的流控算法,RateLimiter会按照一定的频率往桶里扔令牌,线程拿到令牌才能执行,比如你希望自己的应用程序QPS不要超过1000,那么RateLimiter设置1000的速率后,就会每秒往桶里扔1000个令牌。(这个跟nginx的ngx_http_limit_req_module中用到的leaky bucket是一个意思)

RateLimiter经常用于限制对一些物理资源或者逻辑资源的访问速率。与Semaphore 相比,Semaphore 限制了并发访问的数量而不是使用速率。

RateLimiter几个关键的方法

  • static RateLimiter create(double permitsPerSecond) 根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询)
  • static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) 根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和)
  • double acquire(int permits) 从RateLimiter获取指定许可数,该方法会被阻塞直到获取到请求
  • void setRate(double permitsPerSecond) 动态更新RateLimite的稳定速率,参数permitsPerSecond 由构造RateLimiter的工厂方法提供。
  • boolean tryAcquire(int permits) 从RateLimiter 获取许可数,如果该许可数可以在无延迟下的情况下立即获取得到的话
  • boolean tryAcquire(int permits, long timeout, TimeUnit unit) 从RateLimiter 获取指定许可数如果该许可数可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可数的话,那么立即返回false (无需等待)

使用示例

限制写入response的速率不超过200kB/s

RateLimiter limiter = RateLimiter.create(1024*200);
while(....){
  byte[] bytes = ...
  limiter.acquire(bytes.length);
  response.getWriter().write(bytes);
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序人生

深入了解IAM和访问控制

本文为InfoQ中文站特供稿件,首发地址为:http://www.infoq.com/cn/articles/aws-iam-dive-in。 访问控制,换句话...

4847
来自专栏天天P图攻城狮

Android系统打印方案分析

Android Print API Android默认实现了打印的框架,使用PrintManager+PrintManagerService可以轻松实现打印...

1.2K4
来自专栏Deep learning进阶路

OpenCV学习日记(一)——开发环境的配置

于 2016/6/13 Visual studio2010 + openCV 2.4.9 一、关于开发环境的选择 我一开始是抱着什么都要用最新的态度,选择了op...

2030
来自专栏我是东东强

细说子网

最近闲来无事,抄起本《Wireshark网络分析就是这么简单》想了解下数据包粒度(Packet-level)的网络测量及分析方法,书开篇提出一个面试题,与子网掩...

1121
来自专栏三丰SanFeng

浅谈UDP(数据包长度,收包能力,丢包及进程结构选择)

udp 数据包的理论长度是多少,合适的 udp 数据包应该是多少呢?

5519
来自专栏SDNLAB

容器 Flannel vxlan 基本原理和验证

作者简介:yangjunsss,曾就职于IBM、青云QingCloud,现就职于华为,研究方向:容器微服务、IaaS、P2P分布式。邮箱 cj.yangjun@...

1662
来自专栏纯洁的微笑

秒杀聊聊秒杀限流的多种实现

俗话说的好,冰冻三尺非一日之寒,滴水穿石非一日之功,罗马也不是一天就建成的。两周前秒杀案例初步成型,分享到了中国最大的同性交友网站-码云。同时也收到了不少小伙伴...

1162
来自专栏iKcamp

iKcamp|基于Koa2搭建Node.js实战(含视频)☞ 解析JSON

视频地址:https://www.cctalk.com/v/15114923886141 JSON 数据 我颠倒了整个世界,只为摆正你的倒影。 前面的文章中,...

4039
来自专栏开源优测

RFC1180 TCP/IP指南

1152
来自专栏北京马哥教育

LVS集群详解

一、什么是集群 LVS(Linux Virtual Server)Linux虚拟服务器,将多台虚拟主机组织起来满足同一个需求。由国人章文嵩开发,通过LVS提...

46810

扫码关注云+社区