当前位置: 首页 > 工具软件 > ngx_php > 使用案例 >

nginx之ngx_http_limit_req

胡玉书
2023-12-01

在Nginx里,一块完整的共享内存以数据结构ngx_shm_zone_t来封装表示。
typedef struct {
    u_char      *addr;     // 分配的共享内存的实际地址(这里实际共享内存的分配,根据当前系统可提供的接口,可以调用mmap或者shmget来进行分配,具体的用法,自己man吧)
    size_t       size;     // 共享内存的大小
    ngx_str_t    name;     // 该字段用作共享内存的唯一标识,能让Nginx知道想使用哪个共享内存
    ngx_log_t   *log;
    ngx_uint_t   exists;   /* unsigned  exists:1;  */
} ngx_shm_t;

typedef struct ngx_shm_zone_s  ngx_shm_zone_t;

typedef ngx_int_t (*ngx_shm_zone_init_pt) (ngx_shm_zone_t *zone, void *data);

struct ngx_shm_zone_s {
    void                     *data; // 指向自定义数据结构,一般用来初始化时使用,可能指向本地地址
    ngx_shm_t                 shm;  // 真正的共享内存所在
    ngx_shm_zone_init_pt      init; // 这里有一个钩子函数,用于实际共享内存进行分配后的初始化
    void                     *tag;  // 区别于shm.name,shm.name没法让Nginx区分到底是想新创建一个共享内存,还是使用已存在的旧的共享内存,因此这里引入tag字段来解决该问题,tag一般指向当前模块的ngx_module_t变量,见:...
};

代码

/*该结构用于存放每个客户端节点的相关信息*/
typedef struct {
    u_char                       color;
    u_char                       dummy;
    u_short                      len;
    ngx_queue_t                  queue;
    ngx_msec_t                   last;
    ngx_uint_t                   excess;
    ngx_uint_t                   count;
    u_char                       data[1];
} ngx_http_limit_req_node_t;

/*该结构体用于管理客户端节点信息,方式主要有队列和红黑树*/
typedef struct {
    ngx_rbtree_t                  rbtree;
    ngx_rbtree_node_t             sentinel;
    ngx_queue_t                   queue;
} ngx_http_limit_req_shctx_t;

配置

http {
    ......
    limit_req_zone $limit_key zone=limit_one:50m rate=30r/s;
    #定义limit_key为Key的变量名,用于后面赋值,每个Key都有自己的计数器。limit_one为zone的名称。rate表示每秒最多接受30个同时请求。
    server {
        ......
        if ( $request_uri ~* .*php.* ) {
              set $limit_one $binary_remote_addr;
              #对于全部PHP首先有个默认的Key,使用客户端的IP作为Key。相当于每个客户端IP都会在zone的限制内。
         }
        if ( $query_string ~* .*id/(\d+)\.php.* ) {
              set $limit_one $1;
              #提取id后面的值作为Key。
        }
        if ( $query_string ~* .*appid/wx(.*)\.html.* ) {
              set $limit_one $1;
              #提取appid作为Key。
        }
        limit_req zone=limit_one burst=200;
        #限制limit_one在此server内的漏斗容量为200。假设一个Key对应的请求数为200,那么第一秒内在处理的为30个请求,其余的170个请求在等待排队。假设一个Key对应的请求数为300,那么超出200的部分将直接返回503。
        .......
    }
}
 
limit_req_zone
语法:limit_req_zone  $session_variable  zone=name:size  rate=rate
默认值:none
上下文:http
命令解析:为session会话状态分配一个大小为size的内存存储区,限制了每秒(分、小时)只接受rate个IP的频率。

以上的例子,这里的 one 是声明一个 limit_zone 的名字。注意到这里使用了$binary_remote_addr,而不是$remote_addr。$remote_addr变量的大小在7到15个字节之间,存储占用32位平台上的32或64字节的内存,并且在64位平台上总是有64个字节内存。$binary_remote_addr变量总是占用4个字节内存,存储占用32位平台上的32字节的内存,并且在64位平台上总是有64个字节内存。一个存储区可以保存3200个32字节或1600个64字节。

limit_req
语法:limit_req  zone=name  burst=burst  [nodelay]
默认值:none
上下文:http、server、location

命令解析:该指令用于指定使用的内存存储区(zone)名称,以及最大的突发请求数(burse)。以上的配置,设置了名为“one”的存储区,大小为10兆字节,请求速率为每个客户端IP每秒1个请求。如果请求的速率超过了limit_req_zone指令中设置的速率,这些请求将被延迟处理,如果请求不需要被延迟,添加nodelay参数,服务器会立刻返回503状态码。

 ngx_http_limit_req_lookup(ngx_http_limit_req_conf_t *lrcf…)
函数功能:通过红黑树查找用户节点,并对节点的两次访问间隔频率进行计算,返回NGX_BUSY、NGX_AGAIN、NGX_OK、NGX_ DECLINED共4种状态。
(1)NGX_ DECLINED:节点不存在;
(2)NGX_OK:该客户端访问频率未超过设定值;
(3)NGX_AGAIN:该客户端访问频率超过了设定值,但是并未超过阈值(与burst有关);
(4)NGX_BUSY:该客户端访问频率超过了阈值;
备注:该函数是整个模块的核心,算法思想是令牌桶算法。令牌桶算法是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常用的一种算法。典型情况下,令牌桶算法用来控制发送到网络上的数据数目,并允许突发数据的发送。令牌桶这种控制机制基于令牌桶中是否存在令牌来指示什么时候可以发送流量。令牌桶中的每一个令牌都代表一个字节。如果令牌桶中存在令牌,则允许发送流量;如果令牌桶中不存在令牌,则不允许发送流量。因此,如果突发门限被合理地配置并且令牌桶中有足够的令牌,那么流量就可以以峰值速率发送。令牌桶算法的基本过程如下:
  (1)假如用户配置的平均发送速率为10r/s,则每隔0.1秒一个令牌被加入到桶中;
  (2)假设桶最多可以存发b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃;
  (3)当一个n个字节的数据包到达时,就从令牌桶中删除n个令牌,并且数据包被发送到网络;
  (4)如果令牌桶中少于n个令牌,那么不会删除令牌,并且认为这个数据包在流量限制之外;
  (5)算法允许最长b个字节的突发,但从长期运行结果看,数据包的速率被限制成常量r。对于在流量限制外的数据包可以以不同的方式处理:
  它们可以被丢弃;
  它们可以排放在队列中以便当令牌桶中累积了足够多的令牌时再传输;
  它们可以继续发送,但需要做特殊标记,网络过载的时候将这些特殊标记的包丢弃。
注意:令牌桶算法不能与另外一种常见算法“漏桶算法(Leaky Bucket)”相混淆。这两种算法的主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输数据外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,因此它适合于具有突发特性的流量。
Nginx模块开发(10)—limit_req模块分析 - cjhust - 我一直在努力
tp = ngx_timeofday();
now = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
ms = (ngx_msec_int_t) (now - lr->last);
excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;
//令牌桶算法:每来一个请求分配一个令牌1000
//rate*ms = 1s内该请求所花的时间< xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" />
//excess = 1s的余量
if (excess < 0) {                                                      //频率很低
  excess = 0;
}
*ep = excess;
if ((ngx_uint_t) excess > lrcf->burst) {              // 1s可以并发burst个请求
    return NGX_BUSY;
}
lr->excess = excess;
lr->last = now;
if (excess) {
   return NGX_AGAIN;
}
return NGX_OK;

压力测试

 ab.exe -c 100 -t 10 http://10.80.3.17:8080/index.html

 类似资料: