近在学习Nginx(著名的高性能http服务器和反向代理服务器)的模块开发,在此分享nginx的限速实现核心代码。

Nginx的http核心模块ngx_http_core_module中提供limit_rate这个指令可以用于控制速度,limit_rate_after用于设置http请求传输多少字节后开始限速。
另外两个模块ngx_http_limit_conn_modulengx_http_limit_req_module分别用于连接数和连接频率的控制。

限制速度的配置指令简单易懂,限速支持固定的数值


[plain]  view plain copy


1. location /flv/ {  
2.     limit_rate_after 500k;  
3.     limit_rate       50k;  
4. }


查看nginx源代码,可以发现ngx_http_write_filter_module.c源文件具体实现了速度的控制,nginx的特点是高度模块化,从名字可以看出这个文件其实也是一个filter模块(nginx中的模块分handler,filter,upstream等三类),这个模块属于filter类别。




[cpp]  view plain copy


    1. static ngx_int_t  
    2. ngx_http_write_filter_init(ngx_conf_t *cf)  
    3. {  
    4.     ngx_http_top_body_filter = ngx_http_write_filter;  
    5.    
    6. return NGX_OK;  
    7. }



    模块挂载了一个函数在filter的顶端(经过编译链接后此模块即被“压”到filter“链表”的尾部),用于控制数据的输出,这个函数里面就包含了速度的控制。




    [cpp]  view plain copy

    1. if (r->limit_rate) {  
    2.     limit = r->limit_rate * (ngx_time() - r->start_sec + 1)  
    3.             - (c->sent - clcf->limit_rate_after);  
    4.   
    5. if (limit <= 0) {  
    6.         c->write->delayed = 1;  
    7.         ngx_add_timer(c->write,  
    8.                       (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1));  
    9.   
    10.         c->buffered |= NGX_HTTP_WRITE_BUFFERED;  
    11.   
    12. return NGX_AGAIN;  
    13.     }  
    14.   
    15. if (clcf->sendfile_max_chunk  
    16.         && (off_t) clcf->sendfile_max_chunk < limit)  
    17.     {  
    18.         limit = clcf->sendfile_max_chunk;  
    19.     }  
    20.   
    21. }


    上面代码的逻辑是:如果配置文件设置了限速(limit_rate是速度值,size_t类型,0表示不限速)

    1. 当c->sent<clcf->limit_rate_after时,说明还没有到需要限速的阈值,计算limit值大于0(下一次应该传输位置偏移量),不必限速
    2. 当c->sent>clcf->limit_rate_after时,需要控制限速,分两种情况:
    • r->limit_rate * (ngx_time() – r->start_sec + 1)>(c->sent – clcf->limit_rate_after)      理论传输量>实际传输量,不必控制(传得慢了)
    • r->limit_rate * (ngx_time() – r->start_sec + 1)<(c->sent – clcf->limit_rate_after)      理论传输量<实际传输量,需要设置延时(传得快了)

        chain = c->send_chain(c, r->out, limit);


    通过上面的c->send_chain函数异步发送数据,nginx在处理完上面send_chain函数后做了延时的微调,倘若进行到下面的程序之前异步IO使得c->sent增加了,则按照增加量添加延时时间delay,因为一般情况这段时间c->sent应该不会来得及改变的。所以如果异步IO改变了数据传输量,也应该及时做速度限制的调整,看得出来nginx对这些细节上的处理非常仔细啊,保证一个准确度。


    [cpp]  view plain copy


    1. if (r->limit_rate) {  
    2.   
    3.     nsent = c->sent;  
    4.   
    5. if (clcf->limit_rate_after) {  
    6.   
    7.         sent -= clcf->limit_rate_after;  
    8. if (sent < 0) {  
    9.             sent = 0;  
    10.         }  
    11.   
    12.         nsent -= clcf->limit_rate_after;  
    13. if (nsent < 0) {  
    14.             nsent = 0;  
    15.         }  
    16.     }  
    17.   
    18.     delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate);  
    19.   
    20. if (delay > 0) {  
    21.         limit = 0;  
    22.         c->write->delayed = 1;  
    23.         ngx_add_timer(c->write, delay);  
    24.     }  
    25. }

    接下来nginx还做了点延时的微调,不过这个是涉及到sendfile_max_chunk指令,而不是limit_rate指令的,所以不做分析。




    [cpp]  view plain copy



      1. if (limit  
      2.     && c->write->ready  
      3.     && c->sent - sent >= limit - (off_t) (2 * ngx_pagesize))  
      4. {  
      5.     c->write->delayed = 1;  
      6.     ngx_add_timer(c->write, 1);  
      7. }


      总之,可以看出nginx是通过使用ngx_add_timer函数实现对write event的控制,进而实现速度上限的控制。

      题外话:nginx对于速度的限制不止是通过limit_rate设置阈值,在upstream模块中通过获取上游服务器返回的响应头headers[“X-Accel-Limit-Rate”]的值也可来动态调整limit_rate的具体数值,这个可以用来实现变化的速度控制。