Nginx限流模块详解

高并发系统有三把利器:缓存、降级和限流;

限流的目的是通过对并发访问/请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页)、排队等待(秒杀)、降级(返回兜底数据或默认数据);

高并发系统常见的限流有:限制总并发数(数据库连接池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(nginx的limit_req模块,用来限制每秒的平均速率);

另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。

1.限流算法

最简单粗暴的限流算法就是计数器法了,而比较常用的有漏桶算法和令牌桶算法;

1.1计数器

计数器法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。

那么我们我们可以设置一个计数器counter,其有效时间为1分钟(即每分钟计数器会被重置为0),每当一个请求过来的时候,counter就加1,如果counter的值大于100,就说明请求数过多;

这个算法虽然简单,但是有一个十分致命的问题,那就是临界问题。

如下图所示,在1:00前一刻到达100个请求,1:00计数器被重置,1:00后一刻又到达100个请求,显然计数器不会超过100,所有请求都不会被拦截;

然而这一时间段内请求数已经达到200,远超100。

clipboard.png

1.2 漏桶算法

如下图所示,有一个固定容量的漏桶,按照常量固定速率流出水滴;如果桶是空的,则不会流出水滴;流入到漏桶的水流速度是随意的;如果流入的水超出了桶的容量,则流入的水会溢出(被丢弃);

可以看到漏桶算法天生就限制了请求的速度,可以用于流量整形和限流控制;

clipboard.png

1.3 令牌桶算法

令牌桶是一个存放固定容量令牌的桶,按照固定速率r往桶里添加令牌;桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃;

当一个请求达到时,会尝试从桶中获取令牌;如果有,则继续处理请求;如果没有则排队等待或者直接丢弃;

可以发现,漏桶算法的流出速率恒定或者为0,而令牌桶算法的流出速率却有可能大于r;

clipboard.png

2.nginx基础知识

Nginx主要有两种限流方式:按连接数限流(ngx_http_limit_conn_module)、按请求速率限流(ngx_http_limit_req_module);

学习限流模块之前还需要了解nginx对HTTP请求的处理过程,nginx事件处理流程等;

2.1HTTP请求处理过程

nginx将HTTP请求处理流程分为11个阶段,绝大多数HTTP模块都会将自己的handler添加到某个阶段(其中有4个阶段不能添加自定义handler),nginx处理HTTP请求时会挨个调用所有的handler;

typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0, //目前只有realip模块会注册handler(nginx作为代理服务器时有用,后端以此获取客户端原始ip)
 
    NGX_HTTP_SERVER_REWRITE_PHASE,  //server块中配置了rewrite指令,重写url
 
    NGX_HTTP_FIND_CONFIG_PHASE,   //查找匹配location;不能自定义handler;
    NGX_HTTP_REWRITE_PHASE,       //location块中配置了rewrite指令,重写url
    NGX_HTTP_POST_REWRITE_PHASE,  //检查是否发生了url重写,如果有,重新回到FIND_CONFIG阶段;不能自定义handler;
 
    NGX_HTTP_PREACCESS_PHASE,     //访问控制,限流模块会注册handler到此阶段
 
    NGX_HTTP_ACCESS_PHASE,        //访问权限控制
    NGX_HTTP_POST_ACCESS_PHASE,   //根据访问权限控制阶段做相应处理;不能自定义handler;
 
    NGX_HTTP_TRY_FILES_PHASE,     //只有配置了try_files指令,才会有此阶段;不能自定义handler;
    NGX_HTTP_CONTENT_PHASE,       //内容产生阶段,返回响应给客户端
 
    NGX_HTTP_LOG_PHASE            //日志记录
} ngx_http_phases;

nginx使用结构体ngx_module_s表示一个模块,其中字段ctx,是一个指向模块上下文结构体的指针;nginx的HTTP模块上下文结构体如下所示(上下文结构体的字段都是一些函数指针):

typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);  //此方法注册handler到相应阶段
 
    void       *(*create_main_conf)(ngx_conf_t *cf);   //http块中的主配置
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);
 
    void       *(*create_srv_conf)(ngx_conf_t *cf);    //server配置
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
 
    void       *(*create_loc_conf)(ngx_conf_t *cf);    //location配置
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

以ngx_http_limit_req_module模块为例,postconfiguration方法简单实现如下:

static ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf)
{
    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
    
    *h = ngx_http_limit_req_handler;  //ngx_http_limit_req_module模块的限流方法;nginx处理HTTP请求时,都会调用此方法判断应该继续执行还是拒绝请求
 
    return NGX_OK;
}

2.2 nginx事件处理简单介绍

假设nginx使用的是epoll。

nginx需要将所有关心的fd注册到epoll,添加方法生命如下:

static ngx_int_t ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

方法第一个参数是ngx_event_t结构体指针,代表关心的一个读或者写事件;nginx为事件可能会设置一个超时定时器,从而能够处理事件超时情况;定义如下:


struct ngx_event_s {
    
    ngx_event_handler_pt  handler; //函数指针:事件的处理函数
 
    ngx_rbtree_node_t   timer;     //超时定时器,存储在红黑树中(节点的key即为事件的超时时间)
 
    unsigned         timedout:1;   //记录事件是否超时
 

一般都会循环调用epoll_wait监听所有fd,处理发生的读写事件;epoll_wait是阻塞调用,最后一个参数timeout是超时时间,即最多阻塞timeout时间如果还是没有事件发生,方法会返回;

nginx在设置超时时间timeout时,会从上面说的记录超时定时器的红黑树中查找最近要到时的节点,以此作为epoll_wait的超时时间,如下面代码所示;

ngx_msec_t ngx_event_find_timer(void)
{
    node = ngx_rbtree_min(root, sentinel);
    timer = (ngx_msec_int_t) (node->key - ngx_current_msec);
 
    return (ngx_msec_t) (timer > 0 ? timer : 0);
}

同时nginx在每次循环的最后,会从红黑树中查看是否有事件已经过期,如果过期,标记timeout=1,并调用事件的handler;

void ngx_event_expire_timers(void)
{
    for ( ;; ) {
        node = ngx_rbtree_min(root, sentinel);
 
        if ((ngx_msec_int_t) (node->key - ngx_current_msec) <= 0) {  //当前事件已经超时
            ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
 
            ev->timedout = 1;
 
            ev->handler(ev);
 
            continue;
        }
 
        break;
    }
}

nginx就是通过上面的方法实现了socket事件的处理,定时事件的处理;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值