点赞
评论
收藏
分享
举报
NGINX带宽限制原理及源码分析
发表于2020-12-21 12:31

浏览 5.8k

概述

NGINX的速率控制用来控制新建连接的速度,并发控制用来控制并发连接数目,而带宽控制是用来控制单个连接上从服务器到客户端数据传输的速率。

我们前面已经分析了NGINX速率限制并发限制的原理。作为NGINX流量控制系列的最后一篇文章,本文我们分析NGINX的带宽控制的原理。


原理

算法

NGINX采用了令牌桶算法进行带宽控制。使用一张经典的图偏来描述令牌桶算法:

具体流程是:

  1.          系统以固定速率产生令牌,并且缓存到令牌桶里。
  2.            当令牌桶满时,再来的令牌会被丢弃。
  3.            传输报文时,根据报文的大小消费对应数量的令牌。
  4.           当令牌不足时,不能够传输报文。

令牌桶算法用一只用来存储令牌,还用一个队列存储请求。从作用上来说,漏桶和令牌桶算法最明显的区别就是是否允许突发流量(burst)的处理,漏桶算法能够强行限制数据的实时传输(处理)速率,对突发流量不做额外处理,它对流量进行的是管制(policy);而令牌桶算法能够在限制数据的平均传输速率的同时允许某种程度的突发传输,它对流量进行的是整形(shapping)。

执行

NGINX速率控制发生在HTTP11个处理阶段的CONTENT阶段,通过过滤模块ngx_http_write_filter_module完成的。

过滤模块是对发往客户端的内容进行头部和内容处理(过滤)的模块。分为头部处理模块和内容处理模块。它是在获取回复内容之后,在向用户发送响应之前得到执行的。通过ngx_http_top_header_filter(r) ngx_http_top_body_filter(r, in)两个函数分为两个阶段过滤HTTP回复的头部和内容主体。

从代码角度来说,NGINX把所有对头部处理的函数通过变量ngx_http_top_header_filterngx_http_next_header_filter连接起来形成一个单链表。同样的道理,通过变量ngx_http_top_body_filterngx_http_next_body_filter形成一个对内容处理的单链表。这样在往客户端发送数据是分别调用ngx_http_send_headerngx_http_output_filter函数遍历头部链表和内容链表中的回调函数逐个进行处理。

过滤模块的回调函数在上述单链表的位置决定了它被执行次序。而过滤模块的回调函数在单链表中的位置是在编译期间模块声明的位置决定的。当编译完Nginx之后,在ngx_modules.c文件中有如下数据结构:

ngx_module_t *ngx_modules[] = {

...

        &ngx_http_write_filter_module,

        &ngx_http_header_filter_module,

        &ngx_http_chunked_filter_module,

        &ngx_http_range_header_filter_module,

        &ngx_http_gzip_filter_module,

        &ngx_http_postpone_filter_module,

        &ngx_http_ssi_filter_module,

        &ngx_http_charset_filter_module,

        &ngx_http_userid_filter_module,

        &ngx_http_headers_filter_module,

        &ngx_http_copy_filter_module,

        &ngx_http_range_body_filter_module,

        &ngx_http_not_modified_filter_module,

        NULL

};

按照上述声明,定义的第一个过滤模块是ngx_http_write_filter_module然后最后一个过滤模块是ngx_http_not_modified_filter_module。而各个模块的对应的回调函数是按照声明顺序相反的次序执行的。也就是说,ngx_http_not_modified_filter_module模块对应的回调函数是链表中的第一个,而ngx_http_write_filter_module对应的回调函数处在链表的末尾,也就是说它最后得到执行。

配置

带宽控制在实现上是针对单个的连接进行带宽限制。从NGINX架构上来看,单个的连接整个生命周期的处理是在某一个单独的worker进程中进行的。所以,带宽控制不需要在各个worker进程之间共享和同步数据。从而不需要像速率控制、并发控制那样定义一个共享内存的zone来共享和同步的数据。基于此,带宽控制的指令就没有了速率控制和并发控制所需要定义共享内存区域的指令。它通过limit_rate limit_rate_after两条指令来实现,具体语法和意义如下:

指令limit_rate

Syntax:limit_rate rate;

Default:limit_rate 0;

Context:http, server, location, if in location

限制发向客户端响应的数据的速率。单位是BYTES每秒。默认值0表示不进行速率限制。此限制是针对每一个连接请求而言的,所以,如果客户端同时有并行的n个连接,那么这个客户端的整体速率就是n倍的limit_rate

参数值也可以包含变量,这样可以做到根据不同的情况进行不同的带宽控制。如下所示:

map $slow $rate {

    14k;

    28k;

}

limit_rate $rate;

server {

    if ($slow) {

        set $limit_rate 4k;

    }

    ...

}

指令limit_rate_after

Syntax:limit_rate_after size;

Default:limit_rate_after 0;

Context:http, server, location, if in location

在传输完一定数量的BYTES之后设开始实施带宽控制。与指令limit_rate一样,后面的参数数值可以通过变量来设置。

Example:

location /jikui/ {

    limit_rate_after   500k;

    limit_rate  50k;

}

配置说明

指令limit_rate_after只有在配置了limit_rate的前提下才能生效。如果只配置limit_rate_after则不会有带宽控制的效果。

如果同时配置了sendfile_max_chunk 指令,按照两者较小的数值进行带宽控制。

通过limit_ratelimit_rate_after指令只能控制单个连接的带宽,没有办法对整个client的带宽进行限制。通过如下limit_connlimit_rate组合能限制某一个客户端在单个worker进程中的总带宽是:worker进程数量*10*50k

location /jikui/ {

    limit_conn 10;

    limit_rate  50k;

}

代码实现

配置层面

NGINX解析配置时,在解析到limit_ratelimit_rate_after指令时,只需要把配置的数值分别设置到ngx_http_core_loc_conf_s结构中对应的limit_ratelimit_rate_after成员变量中。

数据平面

模块ngx_http_write_filter_module只对发送到客户端的内容进行过滤。它通过函数ngx_http_write_filter_init ngx_http_write_filter挂接到ngx_http_top_body_filter函数链表中。

在处理客户端请求的CONTENT阶段,通过函数ngx_http_send_response准备好要送的数据以后,分别调用ngx_http_send_headerngx_http_output_filter来对发送请求的头部和内容进行过滤。对应带宽控制功能的函数是ngx_http_write_filter就是通过函数ngx_http_output_filter来调用的。

它的逻辑是:

1.     如果结构体ngx_http_core_loc_conf_s设置了limit_rate参数,通过下面的公式计算当前可以发送的数据的长度。

limit = (off_t) r->limit_rate * (ngx_time() - r->start_sec + 1) - (c->sent - r->limit_rate_after);

2.     如果计算的limit小于0,说明按照当前的速率配置和已经发送的数据的情况,需要暂缓发送。这时候根据delay = (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1)计算需要推迟发送的时间。并且根据计算的时间生成一个timer加入到NGINX自身的timer系统中等待执行。

3.     如果limit大于0,说明按照当前的速率配置可以进行发送数据。调用send_chain(c, r->out, limit)进行发送特定数量的数据。

4.     如果通过step3发送完毕以后,还有剩余的数据没有发送,则根据上次发送数据所用的时间delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate)生成需要暂缓发送的时间然后生成timer来规划下一次发送。

5.     同时如果还有数据要发送,而且上次发送的数据大于等于limit减去两个page大小的数量limit - (off_t) (2 * ngx_pagesize),则生成一个1秒钟的timer尽快规划发送。

结语

作为NGINX流量管理的三大功能之一,带宽控制的实现相对简单。它也只是实现了单个连接上的带宽控制,经常要和速率控制、并发控制一起使用来实现客户端的流量管理。

如果想更方便地对客户端总的带宽流量进行控制,可以采用类似ngx_limit_speed_module第三方模块。这个第三方模块通过定义共享内存区域可以让多个worker进程共享带宽数据从而方便地进行客户端总带宽的限制。


已修改于2023-03-08 02:07
本作品系原创
创作不易,留下一份鼓励
皮皮鲁

暂无个人介绍

关注



写下您的评论
发表评论
全部评论(3)

按点赞数排序

按时间排序

求教下,我http和https的同样配置limit 500k,   测试发现http限速正常, 但https限速只有60k作用,请问是需要哪边配置优化吗

赞同

0

回复举报

发表于2021-11-18 11:47



回复壹捌少年
回复

多谢!流量管理的三大功能中,带宽控制是最简单的。

赞同

0

回复举报

发表于2020-12-29 20:09



回复皮皮鲁
回复

感谢分享,简单易懂

赞同

0

回复举报

发表于2020-12-21 14:18



回复阿尔巴
回复
关于作者
皮皮鲁
这家伙很懒还未留下介绍~
85
文章
2
问答
45
粉丝
相关文章
Nginx是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。1、查看可用的Nginx版本访问Nginx镜像库地址: https://hub.docker.com/_/nginx?tab=tags。可以通过Sortby查看其他版本的Nginx,默认是最新版本 nginx:latest。你也可以在下拉列表中找到其他你想要的版本:此外,我们还可以用 dockersearchnginx 命令来查看可用版本:$dockersearchnginx NAMEDESCRIPTIONSTARSOFFICIALAUTOMATED nginxOfficialbuildofNginx.3260[OK] jwilder/nginx-proxyAutomatedNginxreverseproxyfordocke
点赞 4
浏览 4.1k
Nginx的知识网结构图如下:
点赞 0
浏览 3.1k
译者序简介代码结构头文件整数常用返回值错误处理字符串概述格式化数值转换正则表达式时间容器数组列表队列红黑树哈希内存管理堆内存池共享内存日志周期缓冲网络连接事件事件I/O事件定时器事件延迟事件遍历事件进程线程模块添加新模块核心模块配置指令HTTP连接请求配置阶段变量复杂值请求重定向子请求请求结束请求体响应响应体响应体过滤构建过滤模块缓冲复用负载均衡例子代码风格一般规则文件注释预处理器类型变量函数表达式条件和循环标签译者序本文档是nginx官方文档“DeveloperGuide”(https://nginx.org/en/docs/dev/development_guide.html)的中文版本,由白山云(http://www.baishancloud.com)NGINX开发团队负责翻译。官方文档是HTML页面发布的,我们翻译的时候转成了Markdown,以方便编辑。同时也一并保留了英文的Markdown版本:https://github.com/baishancloud/nginx-development-guide/blob/master/en.md。希望此中文版文档能为广大的ngi
点赞 0
浏览 8.9k