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

浏览 2k

概述

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
问答
42
粉丝
相关文章
前因关于Nginx部署、配置的文章网上已经发布过很多,包括我自己也私藏了不少还发布过两篇:后端必备Nginx配置前端必备Nginx配置整理出来为的就是需要的时候,复制、粘贴就能使用。然而千奇百怪的实际开发中,你肯定需要增删Nginx配置。你就得上网搜一下,复制粘贴出bug了又得调一下...搞定还得保存下来以备后患。多了不好找还得整理...就搞得很麻烦后果今天我给大家推荐一款"Nginx配置利器",配配变量就能一键生成常用配置。和繁琐低效配置说再见👋网站链接:nginxconfig在线配置网站nginxconfiggithub项目nginxconfig目前支持:Angular、React、Vue、Node.jsPHP、Pythonwordpress、Magento、Drupal缓存、Https、日志等各种配置...使用实现用户访问*.myweb.com域名自动跳转到myweb.com配置,并且开启http强制跳转到https的配置。配置完之后,下方还有安装步骤指导你配置生效。交互体验相当好生成配置 /etc/nginx/sites-available/myweb.
点赞 1
浏览 1.1k
原文作者:OwenGarrettofF5原文链接:在Kubernetes中部署应用交付服务(第2部分)转载来源:NGINX官方网站本文是以下系列博文中的一篇:在Kubernetes中部署应用交付服务(第1部分)解释了为什么因分治而重复使用的应用服务反而可以提高整体效率:因为NetOps和DevOps团队有不同的要求,所以他们会选择最适合他们特定需求的工具。在Kubernetes中部署应用交付服务(第2部分)(本文)以WAF为例,就应用交付服务在Kubernetes环境中的部署位置提供了指导。您可以根据自己的需求,以基于每个Service或基于每个POD的方式,将WAF部署在Kubernetes环境的“前门”或IngressController上。在本系列博文的上一篇,我们讨论了DevOps在控制应用部署、管理和交付方式方面日益增长的影响力。虽然这可能看似会引发与NetOps团队的冲突,但企业需要明白的是,每个团队都有不同的职责、目标和运维模式。要想实现专门化并提高运维效率,关键是要慎重选择负载均衡和We
点赞 1
浏览 1.1k
又是一个热情似火的六月,一年一度的 Microservices June 微服务之月再次回归!微服务之月是 NGINX 一年一度的免费线上教学项目,今年我们将主要关注“微服务交付”的概念和技巧。完成课
点赞 1
浏览 610