`
sunzixun
  • 浏览: 75939 次
  • 性别: Icon_minigender_1
  • 来自: 苏州
社区版块
存档分类
最新评论

<nginx> limit_rate

阅读更多

记得去年的一个 enhance FTP 的项目里, 有一个需求是要求

 

“针对每个IP进行限速 k ,然后对来自这个区域的所有IP 也要限速 K ”

 

 因为应用是多线程多进程的。 就用了一个比较搞笑的方法 我叫他为《水桶法》

 

准备工作是创建一个消息队列 ,赶项目 就用了Posix的

 1 每个连接都对应一个 con_id(独一无二)

 2 每次该con_id收满 M 字节 就往队列中插入一个 segment,

   里面优先级是con_id buf 就是当前的时间戳

struct msgbuf{
long con_id;
char time_stamp[0x10];
};

  3 有一个专门的master线程 负责从队列中每隔 T s进行采样(OS 同步), 统计当前该IP 对应的链接速度求和 然后进行一些计算按照该 IP和链接上限k ,K 算出该con_id  需要usleep的时间片(有点类似TTL) ,放回一个sleep队列

 

 4 再该链接收消息的时候 就取出sleep队列里面的 usleep需要的时间 。

 

 大概就是这个样子,还有很多细节包括计算算法就不描述了。  当然经过多次调试 最后效果还凑合~

 

但是很明显看到需要大量的 内核态处理 cpu_time会用的很多。 解决方法就是用ACE的MQ 而不是系统自带, 对于时间的获取改成计数器等 。(欢迎讨论)

 

 

昨天公司要一个流媒体服务器 能自动限速的。 我就直接在 rtsp 服务器前面弄了个 nginx去自动proxy_server转向 来利用它的 limit_rate ,效果很好,于是就打算来看看它的实现,哈哈

 

 

nginx  的主要配置都放在了

struct ngx_http_core_loc_conf_s {
   ngx_str_t     name;          /* location name */   
 .....
   size_t        limit_rate;              /* limit_rate */
   size_t        limit_rate_after;        /* limit_rate_after */

 .....
}

 

这个结构里面

 

同时去初始化一个过滤模块(ngx_http_write_filter_module) 对应的模块指令:

 

struct ngx_command_s {
    ngx_str_t             name; // 指令的字符串 一般是 nginx.conf 配置文件里面的名字
    ngx_uint_t            type; // 表示 这个指令配置的范围 是upstream ? main ? server 和参数个数
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);//当然是用来设置模块配置的
    ngx_uint_t            conf;//在nginx.conf中的位置
    ngx_uint_t            offset;//结构体成员对应的偏移
    void                 *post;
};

 

 limit_rate的配置就是:

    { ngx_string("limit_rate"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                        |NGX_CONF_TAKE1,
      ngx_conf_set_size_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_core_loc_conf_t, limit_rate),
      NULL },

 

然后就是初始化重要的结构体 nginx的模块实现基本靠他了

就像kernel的 module_init() moudle_eixt()一样

只是他被C 代码遍历 后者用了gcc特性而已

 typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);
    void       *(*create_main_conf)(ngx_conf_t *cf);
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);
    void       *(*create_srv_conf)(ngx_conf_t *cf);
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
    void       *(*create_loc_conf)(ngx_conf_t *cf);
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

 从名字很容易看出他们用来干什么。 

 初始化如下

static ngx_http_module_t  ngx_http_write_filter_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_write_filter_init,            /* postconfiguration */
    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */
    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */
    NULL,                                  /* create location configuration */
    NULL,                                  /* merge location configuration */
};

 

这样的话 就会在ngx_http_module_t  链表中 调用 ngx_http_write_filter_init

函数很简单 其实就是

   

gx_http_top_body_filter = ngx_http_write_filter;

 

 

给函数指针对象赋值,让上一个模块能够来调用  ngx_http_write_filter

 

 

然后开看看关键的

ngx_int_t
ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)

 

    off_t                      size, sent, nsent, limit;
    ngx_uint_t                 last, flush;
    ngx_msec_t                 delay;
    ngx_chain_t               *cl, *ln, **ll, *chain;
    ngx_connection_t          *c;
    ngx_http_core_loc_conf_t  *clcf;
    c = r->connection;
    if (c->error) {
        return NGX_ERROR;
    }
    size = 0;
    flush = 0;
    last = 0;
    ll = &r->out;

 

首先是遍历 链接 r 中的 out (ngx_buf_t) 链表,找出保存在链表中的消息大小,刷新点 和恢复上一次链接情况(一些标志位)

for (cl = r->out; cl; cl = cl->next) {
        ll = &cl->next;
...
#if 1
//如果内存或者后备缓冲中都没有消息了 并且呢 表明没有被刷新 标志位却表明也在内存里 说明 
        if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {
//这么奇异的情况 就需要你调试了,它会根据配置 等你来处理, 很爽吧 
            ngx_debug_point(); //为什么不加个宏 ? 万一卡死了呢。。。
            return NGX_ERROR;
        }
#endif
//计算消息总大小 
        size += ngx_buf_size(cl->buf);
//根据上一次的情况 ,设置正确的标志位
        if (cl->buf->flush || cl->buf->recycled) {
            flush = 1;
        }

        if (cl->buf->last_buf) {
            last = 1;
        }
    }

 ps : 记得去年刚毕业的时候 写代码我也喜欢这么做 系统是一个巨大的状态机 有很多标志位。

 然后在可能矛盾的逻辑中 写一个check_point() 自动把异常的状态位恢复 。。 后来想想其实应该跟踪为啥这样,而不是强制修复,哈哈

 

 

继续: 这里就是把新的链表 一个一个加到 ll后面

 for (ln = in; ln; ln = ln->next) {
        cl = ngx_alloc_chain_link(r->pool);//到 pool去找一个节点,如果找到就使用,否则分配后使用
        if (cl == NULL) {
            return NGX_ERROR;
        }

        cl->buf = ln->buf;
        *ll = cl;   //把填充好的节点地址放到ll的当前位置
      ll = &cl->next;  //ll的地址继续指向新的地址等待填充 

//同上      
#if 1
        if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {
            ngx_debug_point();
            return NGX_ERROR;
        }
#endif

 

好了经过上面的工作 要对根据 正确的buf 和正确的标志位做处理了

 

 

 

 //如果没有last buf也不是flush点 而且总大小又小于postpone_output 说明这次无事可做

if (!last && !flush && in && size < (off_t) clcf->postpone_output) {
        return NGX_OK;
    }
//如果标记了延迟写 那么这次就不发送标记一下放到下次
    if (c->write->delayed) {
        c->buffered |= NGX_HTTP_WRITE_BUFFERED;
        return NGX_AGAIN;
    }
//如果消息大小为0 而且又没有任何要缓冲的意思 
//NGX_LOWLEVEL_BUFFERED 0x0f :每一位都是一个缓冲情况 
    if (size == 0 && !(c->buffered & NGX_LOWLEVEL_BUFFERED)) {
        if (last) { //如果已经是最后一个
            r->out = NULL;
            c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;//去位

            return NGX_OK;
        }
//如果需要flush 这里就把out遍历到最后一个 然后清除缓存位
        if (flush) {
            do {
                r->out = r->out->next;
            } while (r->out);
            c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;

            return NGX_OK;
        }

        ngx_debug_point();
        return NGX_ERROR;
    }

 

 

 下面就是限速的主要实现了

 

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

        if (limit <= 0) {
            c->write->delayed = 1;
            ngx_add_timer(c->write,
                          (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1));

            c->buffered |= NGX_HTTP_WRITE_BUFFERED;

            return NGX_AGAIN;
        }

    } 

 

其实关键是一个  ngx_add_timer 函数, 这里利用了和操作系统 计算时间片类似的方法 比如linux kernel 里面都是每次分配默认的时间片(按优先级等等) 用完就从计算然后增加(比如CFS调度算法)

 

这里的思想就是 计算出超过的时间量 然后增加到他的事件时间里面 通过一个红黑数保存

 

其实很像libevent 那个小根堆 把时间放到一个数据结构中保存

----待续

 

 其实慢慢的研究nginx发现,里面很多思想和设计都是参考了内核(module , 调度但是没有用和apache一样用伙伴分配算法,这点也体现了他的伟大) 。

 

分享到:
评论

相关推荐

    nginx限制连接数ngx_http_limit_conn_module模块1

    **语法:** `limit_rate rate` **默认值:** `0` **配置段:** `http, server, location, if in location` `limit_rate` 指令用来限制每个连接的数据传输速率,单位为字节/秒。设置为 0 表示关闭限速。 ### 3. ...

    nginx.config_nginx_

    limit_rate rate; deny all; ``` `limit_conn`限制连接数,`limit_rate`限制速率,`deny`阻止所有请求。 以上仅是Nginx配置的冰山一角,实际应用中还可以结合正则表达式、if条件语句、自定义模块等实现更多高级...

    Nginx使用limit_req_zone对同一IP访问进行限流的方法

    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; 说明:区域名称为one(自定义),占用空间大小为10m,平均处理的请求频率不能超过每秒一次。 $binary_remote_addr是$remote_addr(客户端IP)的二进

    nginx防并发限制ip连接数等配置.zip

    Nginx通过设置`limit_conn`和`limit_req`模块来限制每个IP地址的并发连接数和请求速率。这两个模块可以帮助我们有效地控制服务器的访问流量,避免DDoS攻击和其他滥用行为。 **2. limit_conn模块** `limit_conn`模块...

    解读nginx中limit配置参数

    limit_rate 名称 默认配置 作用域 官方说明 中文解读 模块 limit_rate limit_rate 0; http, server, location, if in location Limits the rate of response transmission to a client. The rate is ...

    Nginx带宽控制(限速模块使用)

    好消息是 Nginx 提供了 limit_rate 和limit_rate_after,举个例子来说明一下: 代码如下: location /download/ {  limit_rate_after 500k;  limit_rate 50k; } 大概意思是:用户下载达到 500k 后,便控制其速度在...

    Nginx服务器限制访问速度的配置方法

    在Nginx中进行访问速度限制主要可以通过两个指令来实现:`limit_rate` 和 `limit_conn`。`limit_rate` 指令用于控制响应内容的传输速率,即限制每个连接的最大传输速度,而 `limit_conn` 指令用于限制给定时间内,一...

    Nginx服务器对数据传输速度限制的基本配置方法讲解

    这时,我们可以引入第三方模块`Nginx-limit-traffic-rate-module`。该项目位于GitHub上(https://github.com/bigplum/Nginx-limit-traffic-rate-module),它可以基于客户端IP或下载URL限制总的下载速度,即使有多个...

    ngx_dynamic_limit_req_module:ngx_dynamic_limit_req_module模块用于动态锁定IP并定期释放它

    Syntax: dynamic_limit_req_zone key zone=name:size rate=rate [sync] redis=127.0.0.1 block_second=time; Default: — Context: http dynamic_limit_req_redis 设置可选参数,unix_socket,端口,...

    nginx中的limit_req限速设置配置示例

    WIKI: http://wiki.nginx.org/HttpLimitReqModule 漏桶原理(leaky bucket): ...limit_req_zone $binary_remote_addr zone=qps1:1m rate=1r/s; limit_req_zone $binary_remote_addr zone=qps2:1m rate=2r/s; limit_

    详解Nginx http资源请求限制(三种方法)

    定义的格式为`limit_conn_zone &lt;key&gt; zone=&lt;name&gt;:&lt;size&gt;;`,其中`&lt;key&gt;`是指定作为键计算的表达式,`&lt;name&gt;`和`&lt;size&gt;`指定区域的名称和大小。例如,`limit_conn_zone $binary_remote_addr zone=addr:10m;`定义了一...

    nginx与apache限制ip连接数和带宽方法.docx

    上述代码使用 limit_zone 指令来指定 limit_conn 指令的作用域,然后在 location 块中使用 limit_conn 指令来限制 IP 连接数为 20,使用 limit_rate 指令来限制带宽为 500KB/s。 二、Apache 限制 IP 连接数和带宽 ...

    详解Nginx限流配置

    本文以示例的形式,由浅入深讲解Nginx...limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s; server { location /login/ { limit_req zone=ip_limit; proxy_pass http://login_upstream; } } $

    Nginx教程 防御ddos,用户访问控制,限流.zip

    3. **响应速度限制**:利用`limit_rate`指令控制响应数据的发送速率,避免过快消耗带宽。例如: ```nginx limit_rate 10k; ``` **四、Nginx优化** 优化Nginx的性能是保持高效服务的关键,包括但不限于以下几点...

    nginx-limit-req-test:在Nginx中测试limit_req(使用Koa)

    运行Nginx下载Nginx 用此仓库替换nginx.conf nginx-1.16.1 / conf / nginx.conf(Windows10) nginx细节# ...http { # ... limit_req_zone $binary_remote_addr zone=testZone:10m rate=10r/s; server { ...

    Nginx下搭建flv视频服务器且支持视频拖动进度条播放.docx

    `limit_rate_after`和`limit_rate`用于限制视频下载速度,确保视频能快速缓冲并维持稳定的速度。 4. 安装支持FLV拖动进度条的播放器。一个常用的解决方案是JW FLV Media Player(jwplayer),它是一个功能丰富的...

Global site tag (gtag.js) - Google Analytics