亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁(yè) > 網(wǎng)站 > Nginx > 正文

Nginx源碼研究之nginx限流模塊詳解

2024-08-30 12:29:54
字體:
供稿:網(wǎng)友

高并發(fā)系統(tǒng)有三把利器:緩存、降級(jí)和限流;

限流的目的是通過對(duì)并發(fā)訪問/請(qǐng)求進(jìn)行限速來保護(hù)系統(tǒng),一旦達(dá)到限制速率則可以拒絕服務(wù)(定向到錯(cuò)誤頁(yè))、排隊(duì)等待(秒殺)、降級(jí)(返回兜底數(shù)據(jù)或默認(rèn)數(shù)據(jù));

高并發(fā)系統(tǒng)常見的限流有:限制總并發(fā)數(shù)(數(shù)據(jù)庫(kù)連接池)、限制瞬時(shí)并發(fā)數(shù)(如nginx的limit_conn模塊,用來限制瞬時(shí)并發(fā)連接數(shù))、限制時(shí)間窗口內(nèi)的平均速率(nginx的limit_req模塊,用來限制每秒的平均速率);

另外還可以根據(jù)網(wǎng)絡(luò)連接數(shù)、網(wǎng)絡(luò)流量、CPU或內(nèi)存負(fù)載等來限流。

1.限流算法

最簡(jiǎn)單粗暴的限流算法就是計(jì)數(shù)器法了,而比較常用的有漏桶算法和令牌桶算法;

1.1計(jì)數(shù)器

計(jì)數(shù)器法是限流算法里最簡(jiǎn)單也是最容易實(shí)現(xiàn)的一種算法。比如我們規(guī)定,對(duì)于A接口來說,我們1分鐘的訪問次數(shù)不能超過100個(gè)。

那么我們我們可以設(shè)置一個(gè)計(jì)數(shù)器counter,其有效時(shí)間為1分鐘(即每分鐘計(jì)數(shù)器會(huì)被重置為0),每當(dāng)一個(gè)請(qǐng)求過來的時(shí)候,counter就加1,如果counter的值大于100,就說明請(qǐng)求數(shù)過多;

這個(gè)算法雖然簡(jiǎn)單,但是有一個(gè)十分致命的問題,那就是臨界問題。

如下圖所示,在1:00前一刻到達(dá)100個(gè)請(qǐng)求,1:00計(jì)數(shù)器被重置,1:00后一刻又到達(dá)100個(gè)請(qǐng)求,顯然計(jì)數(shù)器不會(huì)超過100,所有請(qǐng)求都不會(huì)被攔截;

然而這一時(shí)間段內(nèi)請(qǐng)求數(shù)已經(jīng)達(dá)到200,遠(yuǎn)超100。

Nginx源碼,nginx,限流模塊

1.2 漏桶算法

如下圖所示,有一個(gè)固定容量的漏桶,按照常量固定速率流出水滴;如果桶是空的,則不會(huì)流出水滴;流入到漏桶的水流速度是隨意的;如果流入的水超出了桶的容量,則流入的水會(huì)溢出(被丟棄);

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

Nginx源碼,nginx,限流模塊

1.3 令牌桶算法

令牌桶是一個(gè)存放固定容量令牌的桶,按照固定速率r往桶里添加令牌;桶中最多存放b個(gè)令牌,當(dāng)桶滿時(shí),新添加的令牌被丟棄;

當(dāng)一個(gè)請(qǐng)求達(dá)到時(shí),會(huì)嘗試從桶中獲取令牌;如果有,則繼續(xù)處理請(qǐng)求;如果沒有則排隊(duì)等待或者直接丟棄;

可以發(fā)現(xiàn),漏桶算法的流出速率恒定或者為0,而令牌桶算法的流出速率卻有可能大于r;

Nginx源碼,nginx,限流模塊

2.nginx基礎(chǔ)知識(shí)

Nginx主要有兩種限流方式:按連接數(shù)限流(ngx_http_limit_conn_module)、按請(qǐng)求速率限流(ngx_http_limit_req_module);

學(xué)習(xí)限流模塊之前還需要了解nginx對(duì)HTTP請(qǐng)求的處理過程,nginx事件處理流程等;

2.1HTTP請(qǐng)求處理過程

nginx將HTTP請(qǐng)求處理流程分為11個(gè)階段,絕大多數(shù)HTTP模塊都會(huì)將自己的handler添加到某個(gè)階段(其中有4個(gè)階段不能添加自定義handler),nginx處理HTTP請(qǐng)求時(shí)會(huì)挨個(gè)調(diào)用所有的handler;

typedef enum { NGX_HTTP_POST_READ_PHASE = 0, //目前只有realip模塊會(huì)注冊(cè)handler(nginx作為代理服務(wù)器時(shí)有用,后端以此獲取客戶端原始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, //檢查是否發(fā)生了url重寫,如果有,重新回到FIND_CONFIG階段;不能自定義handler;  NGX_HTTP_PREACCESS_PHASE,  //訪問控制,限流模塊會(huì)注冊(cè)handler到此階段  NGX_HTTP_ACCESS_PHASE,  //訪問權(quán)限控制 NGX_HTTP_POST_ACCESS_PHASE, //根據(jù)訪問權(quán)限控制階段做相應(yīng)處理;不能自定義handler;  NGX_HTTP_TRY_FILES_PHASE,  //只有配置了try_files指令,才會(huì)有此階段;不能自定義handler; NGX_HTTP_CONTENT_PHASE,  //內(nèi)容產(chǎn)生階段,返回響應(yīng)給客戶端  NGX_HTTP_LOG_PHASE   //日志記錄} ngx_http_phases;

nginx使用結(jié)構(gòu)體ngx_module_s表示一個(gè)模塊,其中字段ctx,是一個(gè)指向模塊上下文結(jié)構(gòu)體的指針;nginx的HTTP模塊上下文結(jié)構(gòu)體如下所示(上下文結(jié)構(gòu)體的字段都是一些函數(shù)指針):

typedef struct { ngx_int_t (*preconfiguration)(ngx_conf_t *cf); ngx_int_t (*postconfiguration)(ngx_conf_t *cf); //此方法注冊(cè)handler到相應(yīng)階段  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方法簡(jiǎn)單實(shí)現(xiàn)如下:

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請(qǐng)求時(shí),都會(huì)調(diào)用此方法判斷應(yīng)該繼續(xù)執(zhí)行還是拒絕請(qǐng)求  return NGX_OK;}

2.2 nginx事件處理簡(jiǎn)單介紹

假設(shè)nginx使用的是epoll。

nginx需要將所有關(guān)心的fd注冊(cè)到epoll,添加方法生命如下:

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

方法第一個(gè)參數(shù)是ngx_event_t結(jié)構(gòu)體指針,代表關(guān)心的一個(gè)讀或者寫事件;nginx為事件可能會(huì)設(shè)置一個(gè)超時(shí)定時(shí)器,從而能夠處理事件超時(shí)情況;定義如下:

struct ngx_event_s {  ngx_event_handler_pt handler; //函數(shù)指針:事件的處理函數(shù)  ngx_rbtree_node_t timer;  //超時(shí)定時(shí)器,存儲(chǔ)在紅黑樹中(節(jié)點(diǎn)的key即為事件的超時(shí)時(shí)間)  unsigned   timedout:1; //記錄事件是否超時(shí) };

一般都會(huì)循環(huán)調(diào)用epoll_wait監(jiān)聽所有fd,處理發(fā)生的讀寫事件;epoll_wait是阻塞調(diào)用,最后一個(gè)參數(shù)timeout是超時(shí)時(shí)間,即最多阻塞timeout時(shí)間如果還是沒有事件發(fā)生,方法會(huì)返回;

nginx在設(shè)置超時(shí)時(shí)間timeout時(shí),會(huì)從上面說的記錄超時(shí)定時(shí)器的紅黑樹中查找最近要到時(shí)的節(jié)點(diǎn),以此作為epoll_wait的超時(shí)時(shí)間,如下面代碼所示;

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);}

同時(shí)nginx在每次循環(huán)的最后,會(huì)從紅黑樹中查看是否有事件已經(jīng)過期,如果過期,標(biāo)記timeout=1,并調(diào)用事件的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) { //當(dāng)前事件已經(jīng)超時(shí)   ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));    ev->timedout = 1;    ev->handler(ev);    continue;  }   break; }}

nginx就是通過上面的方法實(shí)現(xiàn)了socket事件的處理,定時(shí)事件的處理;

ngx_http_limit_req_module模塊解析

ngx_http_limit_req_module模塊是對(duì)請(qǐng)求進(jìn)行限流,即限制某一時(shí)間段內(nèi)用戶的請(qǐng)求速率;且使用的是令牌桶算法;

3.1配置指令

ngx_http_limit_req_module模塊提供一下配置指令,供用戶配置限流策略

//每個(gè)配置指令主要包含兩個(gè)字段:名稱,解析配置的處理方法static ngx_command_t ngx_http_limit_req_commands[] = {  //一般用法:limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; //$binary_remote_addr表示遠(yuǎn)程客戶端IP; //zone配置一個(gè)存儲(chǔ)空間(需要分配空間記錄每個(gè)客戶端的訪問速率,超時(shí)空間限制使用lru算法淘汰;注意此空間是在共享內(nèi)存分配的,所有worker進(jìn)程都能訪問) //rate表示限制速率,此例為1qps { ngx_string("limit_req_zone"),  ngx_http_limit_req_zone,  },  //用法:limit_req zone=one burst=5 nodelay; //zone指定使用哪一個(gè)共享空間 //超出此速率的請(qǐng)求是直接丟棄嗎?burst配置用于處理突發(fā)流量,表示最大排隊(duì)請(qǐng)求數(shù)目,當(dāng)客戶端請(qǐng)求速率超過限流速率時(shí),請(qǐng)求會(huì)排隊(duì)等待;而超出burst的才會(huì)被直接拒絕; //nodelay必須與burst一起使用;此時(shí)排隊(duì)等待的請(qǐng)求會(huì)被優(yōu)先處理;否則假如這些請(qǐng)求依然按照限流速度處理,可能等到服務(wù)器處理完成后,客戶端早已超時(shí) { ngx_string("limit_req"),  ngx_http_limit_req,  },  //當(dāng)請(qǐng)求被限流時(shí),日志記錄級(jí)別;用法:limit_req_log_level info | notice | warn | error; { ngx_string("limit_req_log_level"),  ngx_conf_set_enum_slot,  },  //當(dāng)請(qǐng)求被限流時(shí),給客戶端返回的狀態(tài)碼;用法:limit_req_status 503 { ngx_string("limit_req_status"),  ngx_conf_set_num_slot, },};

注意:$binary_remote_addr是nginx提供的變量,用戶在配置文件中可以直接使用;nginx還提供了許多變量,在ngx_http_variable.c文件中查找ngx_http_core_variables數(shù)組即可:

static ngx_http_variable_t ngx_http_core_variables[] = {  { ngx_string("http_host"), NULL, ngx_http_variable_header,  offsetof(ngx_http_request_t, headers_in.host), 0, 0 },  { ngx_string("http_user_agent"), NULL, ngx_http_variable_header,  offsetof(ngx_http_request_t, headers_in.user_agent), 0, 0 }, …………}

3.2源碼解析

ngx_http_limit_req_module在postconfiguration過程會(huì)注冊(cè)ngx_http_limit_req_handler方法到HTTP處理的NGX_HTTP_PREACCESS_PHASE階段;

ngx_http_limit_req_handler會(huì)執(zhí)行漏桶算法,判斷是否超出配置的限流速率,從而進(jìn)行丟棄或者排隊(duì)或者通過;

當(dāng)用戶第一次請(qǐng)求時(shí),會(huì)新增一條記錄(主要記錄訪問計(jì)數(shù)、訪問時(shí)間),以客戶端IP地址(配置$binary_remote_addr)的hash值作為key存儲(chǔ)在紅黑樹中(快速查找),同時(shí)存儲(chǔ)在LRU隊(duì)列中(存儲(chǔ)空間不夠時(shí),淘汰記錄,每次都是從尾部刪除);當(dāng)用戶再次請(qǐng)求時(shí),會(huì)從紅黑樹中查找這條記錄并更新,同時(shí)移動(dòng)記錄到LRU隊(duì)列首部;

3.2.1數(shù)據(jù)結(jié)構(gòu)

limit_req_zone配置限流算法所需的存儲(chǔ)空間(名稱及大?。?,限流速度,限流變量(客戶端IP等),結(jié)構(gòu)如下:

typedef struct { ngx_http_limit_req_shctx_t *sh; ngx_slab_pool_t    *shpool;//內(nèi)存池 ngx_uint_t     rate; //限流速度(qps乘以1000存儲(chǔ)) ngx_int_t     index; //變量索引(nginx提供了一系列變量,用戶配置的限流變量索引) ngx_str_t     var; //限流變量名稱 ngx_http_limit_req_node_t *node;} ngx_http_limit_req_ctx_t; //同時(shí)會(huì)初始化共享存儲(chǔ)空間struct ngx_shm_zone_s { void      *data; //data指向ngx_http_limit_req_ctx_t結(jié)構(gòu) ngx_shm_t     shm; //共享空間 ngx_shm_zone_init_pt  init; //初始化方法函數(shù)指針 void      *tag; //指向ngx_http_limit_req_module結(jié)構(gòu)體};

limit_req配置限流使用的存儲(chǔ)空間,排隊(duì)隊(duì)列大小,是否緊急處理,結(jié)構(gòu)如下:

typedef struct { ngx_shm_zone_t    *shm_zone; //共享存儲(chǔ)空間   ngx_uint_t     burst;  //隊(duì)列大小 ngx_uint_t     nodelay; //有請(qǐng)求排隊(duì)時(shí)是否緊急處理,與burst配合使用(如果配置,則會(huì)緊急處理排隊(duì)請(qǐng)求,否則依然按照限流速度處理)} ngx_http_limit_req_limit_t;

Nginx源碼,nginx,限流模塊

前面說過用戶訪問記錄會(huì)同時(shí)存儲(chǔ)在紅黑樹與LRU隊(duì)列中,結(jié)構(gòu)如下:

//記錄結(jié)構(gòu)體typedef struct { u_char      color; u_char      dummy; u_short      len; //數(shù)據(jù)長(zhǎng)度 ngx_queue_t     queue;  ngx_msec_t     last; //上次訪問時(shí)間   ngx_uint_t     excess; //當(dāng)前剩余待處理的請(qǐng)求數(shù)(nginx用此實(shí)現(xiàn)令牌桶限流算法) ngx_uint_t     count; //此類記錄請(qǐng)求的總數(shù) u_char      data[1];//數(shù)據(jù)內(nèi)容(先按照key(hash值)查找,再比較數(shù)據(jù)內(nèi)容是否相等)} ngx_http_limit_req_node_t; //紅黑樹節(jié)點(diǎn),key為用戶配置限流變量的hash值;struct ngx_rbtree_node_s { ngx_rbtree_key_t  key; ngx_rbtree_node_t  *left; ngx_rbtree_node_t  *right; ngx_rbtree_node_t  *parent; u_char     color; u_char     data;};  typedef struct { ngx_rbtree_t     rbtree; //紅黑樹 ngx_rbtree_node_t    sentinel; //NIL節(jié)點(diǎn) ngx_queue_t     queue; //LRU隊(duì)列} ngx_http_limit_req_shctx_t; //隊(duì)列只有prev和next指針struct ngx_queue_s { ngx_queue_t *prev; ngx_queue_t *next;};

思考1:ngx_http_limit_req_node_t記錄通過prev和next指針形成雙向鏈表,實(shí)現(xiàn)LRU隊(duì)列;最新訪問的節(jié)點(diǎn)總會(huì)被插入鏈表頭部,淘汰時(shí)從尾部刪除節(jié)點(diǎn);

Nginx源碼,nginx,限流模塊

ngx_http_limit_req_ctx_t *ctx;ngx_queue_t    *q; q = ngx_queue_last(&ctx->sh->queue); lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);//此方法由ngx_queue_t獲取ngx_http_limit_req_node_t結(jié)構(gòu)首地址,實(shí)現(xiàn)如下: #define ngx_queue_data(q, type, link) (type *) ((u_char *) q - offsetof(type, link)) //queue字段地址減去其在結(jié)構(gòu)體中偏移,為結(jié)構(gòu)體首地址

思考2:限流算法首先使用key查找紅黑樹節(jié)點(diǎn),從而找到對(duì)應(yīng)的記錄,紅黑樹節(jié)點(diǎn)如何與記錄ngx_http_limit_req_node_t結(jié)構(gòu)關(guān)聯(lián)起來呢?在ngx_http_limit_req_module模塊可以找到以代碼:

size = offsetof(ngx_rbtree_node_t, color) //新建記錄分配內(nèi)存,計(jì)算所需空間大小  + offsetof(ngx_http_limit_req_node_t, data)  + len; node = ngx_slab_alloc_locked(ctx->shpool, size); node->key = hash; lr = (ngx_http_limit_req_node_t *) &node->color; //color為u_char類型,為什么能強(qiáng)制轉(zhuǎn)換為ngx_http_limit_req_node_t指針類型呢? lr->len = (u_char) len;lr->excess = 0; ngx_memcpy(lr->data, data, len); ngx_rbtree_insert(&ctx->sh->rbtree, node); ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);

通過分析上面代碼,ngx_rbtree_node_s結(jié)構(gòu)體的color與data字段其實(shí)是無意義的,結(jié)構(gòu)體的生命形式與最終存儲(chǔ)形式是不同的,nginx最終使用以下存儲(chǔ)形式存儲(chǔ)每條記錄;

Nginx源碼,nginx,限流模塊

3.2.2限流算法

上面提到在postconfiguration過程會(huì)注冊(cè)ngx_http_limit_req_handler方法到HTTP處理的NGX_HTTP_PREACCESS_PHASE階段;

因此在處理HTTP請(qǐng)求時(shí),會(huì)執(zhí)行ngx_http_limit_req_handler方法判斷是否需要限流;

3.2.2.1漏桶算法實(shí)現(xiàn)

用戶可能同時(shí)配置若干限流,因此對(duì)于HTTP請(qǐng)求,nginx需要遍歷所有限流策略,判斷是否需要限流;

ngx_http_limit_req_lookup方法實(shí)現(xiàn)了漏桶算法,方法返回3種結(jié)果:

  • NGX_BUSY:請(qǐng)求速率超出限流配置,拒絕請(qǐng)求;
  • NGX_AGAIN:請(qǐng)求通過了當(dāng)前限流策略校驗(yàn),繼續(xù)校驗(yàn)下一個(gè)限流策略;
  • NGX_OK:請(qǐng)求已經(jīng)通過了所有限流策略的校驗(yàn),可以執(zhí)行下一階段;
  • NGX_ERROR:出錯(cuò)
//limit,限流策略;hash,記錄key的hash值;data,記錄key的數(shù)據(jù)內(nèi)容;len,記錄key的數(shù)據(jù)長(zhǎng)度;ep,待處理請(qǐng)求數(shù)目;account,是否是最后一條限流策略static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, ngx_uint_t hash, u_char *data, size_t len, ngx_uint_t *ep, ngx_uint_t account){ //紅黑樹查找指定界定 while (node != sentinel) {   if (hash < node->key) {   node = node->left;   continue;  }   if (hash > node->key) {   node = node->right;   continue;  }   //hash值相等,比較數(shù)據(jù)是否相等  lr = (ngx_http_limit_req_node_t *) &node->color;   rc = ngx_memn2cmp(data, lr->data, len, (size_t) lr->len);  //查找到  if (rc == 0) {   ngx_queue_remove(&lr->queue);   ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); //將記錄移動(dòng)到LRU隊(duì)列頭部     ms = (ngx_msec_int_t) (now - lr->last); //當(dāng)前時(shí)間減去上次訪問時(shí)間    excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000; //待處理請(qǐng)求書-限流速率*時(shí)間段+1個(gè)請(qǐng)求(速率,請(qǐng)求數(shù)等都乘以1000了)    if (excess < 0) {    excess = 0;   }    *ep = excess;    //待處理數(shù)目超過burst(等待隊(duì)列大?。?,返回NGX_BUSY拒絕請(qǐng)求(沒有配置burst時(shí),值為0)   if ((ngx_uint_t) excess > limit->burst) {    return NGX_BUSY;   }    if (account) { //如果是最后一條限流策略,則更新上次訪問時(shí)間,待處理請(qǐng)求數(shù)目,返回NGX_OK    lr->excess = excess;    lr->last = now;    return NGX_OK;   }   //訪問次數(shù)遞增   lr->count++;    ctx->node = lr;    return NGX_AGAIN; //非最后一條限流策略,返回NGX_AGAIN,繼續(xù)校驗(yàn)下一條限流策略  }   node = (rc < 0) ? node->left : node->right; }  //假如沒有查找到節(jié)點(diǎn),需要新建一條記錄 *ep = 0; //存儲(chǔ)空間大小計(jì)算方法參照3.2.1節(jié)數(shù)據(jù)結(jié)構(gòu) size = offsetof(ngx_rbtree_node_t, color)   + offsetof(ngx_http_limit_req_node_t, data)   + len; //嘗試淘汰記錄(LRU) ngx_http_limit_req_expire(ctx, 1);    node = ngx_slab_alloc_locked(ctx->shpool, size);//分配空間 if (node == NULL) { //空間不足,分配失敗  ngx_http_limit_req_expire(ctx, 0); //強(qiáng)制淘汰記錄   node = ngx_slab_alloc_locked(ctx->shpool, size); //分配空間  if (node == NULL) { //分配失敗,返回NGX_ERROR   return NGX_ERROR;  } }  node->key = hash; //賦值 lr = (ngx_http_limit_req_node_t *) &node->color; lr->len = (u_char) len; lr->excess = 0; ngx_memcpy(lr->data, data, len);  ngx_rbtree_insert(&ctx->sh->rbtree, node); //插入記錄到紅黑樹與LRU隊(duì)列 ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);  if (account) { //如果是最后一條限流策略,則更新上次訪問時(shí)間,待處理請(qǐng)求數(shù)目,返回NGX_OK  lr->last = now;  lr->count = 0;  return NGX_OK; }  lr->last = 0; lr->count = 1;  ctx->node = lr;  return NGX_AGAIN; //非最后一條限流策略,返回NGX_AGAIN,繼續(xù)校驗(yàn)下一條限流策略  }

舉個(gè)例子,假如burst配置為0,待處理請(qǐng)求數(shù)初始為excess;令牌產(chǎn)生周期為T;如下圖所示

Nginx源碼,nginx,限流模塊

3.2.2.2LRU淘汰策略

上一節(jié)叩痛算法中,會(huì)執(zhí)行ngx_http_limit_req_expire淘汰一條記錄,每次都是從LRU隊(duì)列末尾刪除;

第二個(gè)參數(shù)n,當(dāng)n==0時(shí),強(qiáng)制刪除末尾一條記錄,之后再嘗試刪除一條或兩條記錄;n==1時(shí),會(huì)嘗試刪除一條或兩條記錄;代碼實(shí)現(xiàn)如下:

static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n){ //最多刪除3條記錄 while (n < 3) {  //尾部節(jié)點(diǎn)  q = ngx_queue_last(&ctx->sh->queue);  //獲取記錄  lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);     //注意:當(dāng)為0時(shí),無法進(jìn)入if代碼塊,因此一定會(huì)刪除尾部節(jié)點(diǎn);當(dāng)n不為0時(shí),進(jìn)入if代碼塊,校驗(yàn)是否可以刪除  if (n++ != 0) {    ms = (ngx_msec_int_t) (now - lr->last);   ms = ngx_abs(ms);   //短時(shí)間內(nèi)被訪問,不能刪除,直接返回   if (ms < 60000) {    return;   }       //有待處理請(qǐng)求,不能刪除,直接返回   excess = lr->excess - ctx->rate * ms / 1000;   if (excess > 0) {    return;   }  }   //刪除  ngx_queue_remove(q);   node = (ngx_rbtree_node_t *)     ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));   ngx_rbtree_delete(&ctx->sh->rbtree, node);   ngx_slab_free_locked(ctx->shpool, node); }}

3.2.2.3 burst實(shí)現(xiàn)

burst是為了應(yīng)對(duì)突發(fā)流量的,偶然間的突發(fā)流量到達(dá)時(shí),應(yīng)該允許服務(wù)端多處理一些請(qǐng)求才行;

當(dāng)burst為0時(shí),請(qǐng)求只要超出限流速率就會(huì)被拒絕;當(dāng)burst大于0時(shí),超出限流速率的請(qǐng)求會(huì)被排隊(duì)等待 處理,而不是直接拒絕;

排隊(duì)過程如何實(shí)現(xiàn)?而且nginx還需要定時(shí)去處理排隊(duì)中的請(qǐng)求;

2.2小節(jié)提到事件都有一個(gè)定時(shí)器,nginx是通過事件與定時(shí)器配合實(shí)現(xiàn)請(qǐng)求的排隊(duì)與定時(shí)處理;

ngx_http_limit_req_handler方法有下面的代碼:

//計(jì)算當(dāng)前請(qǐng)求還需要排隊(duì)多久才能處理delay = ngx_http_limit_req_account(limits, n, &excess, &limit);//添加可讀事件if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR;}r->read_event_handler = ngx_http_test_reading;r->write_event_handler = ngx_http_limit_req_delay; //可寫事件處理函數(shù)ngx_add_timer(r->connection->write, delay); //可寫事件添加定時(shí)器(超時(shí)之前是不能往客戶端返回的)

計(jì)算delay的方法很簡(jiǎn)單,就是遍歷所有的限流策略,計(jì)算處理完所有待處理請(qǐng)求需要的時(shí)間,返回最大值;

if (limits[n].nodelay) { //配置了nodelay時(shí),請(qǐng)求不會(huì)被延時(shí)處理,delay為0 continue;} delay = excess * 1000 / ctx->rate; if (delay > max_delay) { max_delay = delay; *ep = excess; *limit = &limits[n];}

簡(jiǎn)單看看可寫事件處理函數(shù)ngx_http_limit_req_delay的實(shí)現(xiàn)

static void ngx_http_limit_req_delay(ngx_http_request_t *r){  wev = r->connection->write;  if (!wev->timedout) { //沒有超時(shí)不會(huì)處理   if (ngx_handle_write_event(wev, 0) != NGX_OK) {   ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);  }   return; }  wev->timedout = 0;  r->read_event_handler = ngx_http_block_reading; r->write_event_handler = ngx_http_core_run_phases;  ngx_http_core_run_phases(r); //超時(shí)了,繼續(xù)處理HTTP請(qǐng)求}

4.實(shí)戰(zhàn)

4.1測(cè)試普通限流

1)配置nginx限流速率為1qps,針對(duì)客戶端IP地址限流(返回狀態(tài)碼默認(rèn)為503),如下:

http{ limit_req_zone $binary_remote_addr zone=test:10m rate=1r/s;  server {  listen  80;  server_name localhost;  location / {   limit_req zone=test;   root html;   index index.html index.htm;  }}

2)連續(xù)并發(fā)發(fā)起若干請(qǐng)求;3)查看服務(wù)端access日志,可以看到22秒連續(xù)到達(dá)3個(gè)請(qǐng)求,只處理1個(gè)請(qǐng)求;23秒到達(dá)兩個(gè)請(qǐng)求,第一個(gè)請(qǐng)求處理,第二個(gè)請(qǐng)求被拒絕

xx.xx.xx.xxx - - [22/Sep/2018:23:33:22 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:33:22 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:33:22 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:33:23 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:33:23 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"

4.2測(cè)試burst

1)限速1qps時(shí),超過請(qǐng)求會(huì)被直接拒絕,為了應(yīng)對(duì)突發(fā)流量,應(yīng)該允許請(qǐng)求被排隊(duì)處理;因此配置burst=5,即最多允許5個(gè)請(qǐng)求排隊(duì)等待處理;

http{ limit_req_zone $binary_remote_addr zone=test:10m rate=1r/s;  server {  listen  80;  server_name localhost;  location / {   limit_req zone=test burst=5;   root html;   index index.html index.htm;  }}

2)使用ab并發(fā)發(fā)起10個(gè)請(qǐng)求,ab -n 10 -c 10 http://xxxxx;

3)查看服務(wù)端access日志;根據(jù)日志顯示第一個(gè)請(qǐng)求被處理,2到5四個(gè)請(qǐng)求拒絕,6到10五個(gè)請(qǐng)求被處理;為什么會(huì)是這樣的結(jié)果呢?

查看ngx_http_log_module,注冊(cè)handler到NGX_HTTP_LOG_PHASE階段(HTTP請(qǐng)求處理最后一個(gè)階段);

因此實(shí)際情況應(yīng)該是這樣的:10個(gè)請(qǐng)求同時(shí)到達(dá),第一個(gè)請(qǐng)求到達(dá)直接被處理,第2到6個(gè)請(qǐng)求到達(dá),排隊(duì)延遲處理(每秒處理一個(gè));第7到10個(gè)請(qǐng)求被直接拒絕,因此先打印access日志;

第2到6個(gè)請(qǐng)求米誒秒處理一個(gè),處理完成打印access日志,即49到53秒每秒處理一個(gè);

xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:49 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:50 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:51 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:52 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [22/Sep/2018:23:41:53 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"

4)ab統(tǒng)計(jì)的響應(yīng)時(shí)間見下面,最小響應(yīng)時(shí)間87ms,最大響應(yīng)時(shí)間5128ms,平均響應(yīng)時(shí)間為1609ms:

    min mean[+/-sd] median maxConnect:  41 44 1.7  44  46Processing: 46 1566 1916.6 1093 5084Waiting:  46 1565 1916.7 1092 5084Total:   87 1609 1916.2 1135 5128

4.3測(cè)試nodelay

1)4.2顯示,配置burst后,雖然突發(fā)請(qǐng)求會(huì)被排隊(duì)處理,但是響應(yīng)時(shí)間過長(zhǎng),客戶端可能早已超時(shí);因此添加配置nodelay,使得nginx緊急處理等待請(qǐng)求,以減小響應(yīng)時(shí)間:

http{ limit_req_zone $binary_remote_addr zone=test:10m rate=1r/s;  server {  listen  80;  server_name localhost;  location / {   limit_req zone=test burst=5 nodelay;   root html;   index index.html index.htm;  }}

2)使用ab并發(fā)發(fā)起10個(gè)請(qǐng)求,ab -n 10 -c 10 http://xxxx/;

3)查看服務(wù)端access日志;第一個(gè)請(qǐng)求直接處理,第2到6個(gè)五個(gè)請(qǐng)求排隊(duì)處理(配置nodelay,nginx緊急處理),第7到10四個(gè)請(qǐng)求被拒絕

xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [23/Sep/2018:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"

4)ab統(tǒng)計(jì)的響應(yīng)時(shí)間見下面,最小響應(yīng)時(shí)間85ms,最大響應(yīng)時(shí)間92ms,平均響應(yīng)時(shí)間為88ms:

    min mean[+/-sd] median maxConnect:  42 43 0.5  43  43Processing: 43 46 2.4  47  49Waiting:  42 45 2.5  46  49Total:   85 88 2.8  90  92

總結(jié)

本文首先分析常用限流算法(漏桶算法與令牌桶算法),并簡(jiǎn)單介紹nginx處理HTTP請(qǐng)求的過程,nginx定時(shí)事件實(shí)現(xiàn);然后詳細(xì)分析ngx_http_limit_req_module模塊的基本數(shù)據(jù)結(jié)構(gòu),及其限流過程;并以實(shí)例幫助讀者體會(huì)nginx限流的配置及結(jié)果。至于另一個(gè)模塊ngx_http_limit_conn_module是針對(duì)鏈接數(shù)的限流,比較容易理解,在此就不做詳細(xì)介紹。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持VEVB武林網(wǎng)。


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到服務(wù)器教程頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
中文字幕欧美国内| 日本一区二区动态图| 海角国产乱辈乱精品视频| 成人免费av在线| 奇米影音第四色| 麻豆精品视频在线观看| 亚洲女人****多毛耸耸8| 99视频+国产日韩欧美| 黄色网址在线免费| 欧美88888| 你懂的免费网站| 五月天丁香视频| 亚洲v日韩v欧美v综合| 久草免费在线| 国产成人在线播放视频| 91视频免费观看网站| 一区二区三区四区精品| 亚洲成av人在线观看| 国产成人午夜精品影院观看视频| 色噜噜色狠狠狠狠狠综合色一| 图片婷婷一区| 奇米777日韩| 日韩av电影资源网| 91国内精品白嫩初高生| 亚洲人成亚洲人成在线观看图片| 爱情岛论坛亚洲首页入口章节| 欧美一区三区三区高中清蜜桃| 国产影视一区二区| 黄色小视频免费在线观看| 欧美日韩国产不卡在线看| 天堂网在线中文| 中文字幕精品视频在线| 久色国产在线| 欧美极品少妇无套实战| 7777精品久久久大香线蕉小说| 亚洲1卡2卡3卡4卡乱码精品| 国产欧美日韩在线视频| 欧美一二三不卡| 国产精品高清无码| 人妻夜夜爽天天爽| 亚洲尤物视频在线| 99精品久久只有精品| 国产最新视频在线| 国产在线视频网站| 久久久久久这里只有精品| 国产精品2024| 欧美伊人久久久久久午夜久久久久| 99麻豆久久久国产精品免费优播| 亚洲综合在线网| 成人在线观看网址| 翔田千里亚洲一二三区| xxx欧美精品| 欧美男女性生活在线直播观看| 一区二区激情| 男女视频在线观看免费| 欧美特级特黄aaaaaa在线看| www.xxx麻豆| 亚洲欧美电影一区二区| 51国产偷自视频区视频| 青青青手机在线视频| 桃乃木香奈和黑人aⅴ在线播放| 天堂av网在线| 成人情趣视频网站| 免费一区二区三区四区| 国产色综合一区二区三区| 日本不卡久久| 日本黄色免费网站| 亚洲一二三精品| 91麻豆精品在线观看| 亚洲精品精品亚洲| 国产精品久久久久久久久久免费看| 亚洲精品综合精品自拍| 五月天婷亚洲天综合网鲁鲁鲁| 三级理论午夜在线观看| 制服丝袜亚洲精品中文字幕| 性xxxx18| 久久国产视频精品| 欧美日韩一区二区三区四区在线观看| 国产理论片在线观看| 成人ar影院免费观看视频| 亚洲一区在线电影| 欧美日本一区二区在线观看| 1313精品午夜理伦电影| 亚洲自拍偷拍网| 青椒成人免费视频| 美国一级片在线免费观看视频| 永久免费av片在线观看全网站| 中国女人内谢69视频| 精品淫伦v久久水蜜桃| 热门国产精品亚洲第一区在线| 欧美日韩国产精品一区二区三区四区| 国产精品视频一区二区三区不卡| 国产 欧美 日韩 在线| 午夜精品一区二区三| 日韩av黄色在线观看| 亚洲精品一区二区三区影院忠贞| 国产免费不卡av| 国产欧美日韩视频一区二区三区| 性疯狂做受xxxx高清视频| 国产小视频你懂的| 欧美日韩mv| 欧美—级a级欧美特级ar全黄| 91高清国产视频| 国产精品91免费在线| 精国产品一区二区三区a片| 亚洲在线精品视频| gv天堂gv无码男同在线观看| 欧美伦理一区二区| 久久久久人妻一区精品色欧美| 久久久久高潮毛片免费全部播放| 日本成人在线一区| 欧美日韩偷拍视频| 狠狠色伊人亚洲综合网站l| 嫩草成人www欧美| av资源网在线观看| 三级网站免费观看| 欧美精三区欧美精三区| 69国产精品视频免费观看| 久久韩剧网电视剧| 精品三级久久久久久久电影聊斋| 免费在线视频欧美| 成人综合在线观看| 91精品国产乱码久久久张津瑜| 成年人晚上看的视频| 伊人久久亚洲影院| 欧美成人三级在线播放| 成人毛片免费在线观看| 亚洲视频在线观看三级| 久久久av免费| 一区二区国产精品视频| 久久久久久久久久码影片| 一区二区三区视频国产日韩| 亚洲精品久久久狠狠狠爱| 亚洲精华国产欧美| 偷偷www综合久久久久久久| 国产精品十八以下禁看| 国产精品一级二级三级| 国产精品毛片久久久久久| 一个色综合导航| 国产偷拍一区二区| 日韩一区二区三区高清在线观看| 成人性生交大片免费看中文视频| 中文字幕精品—区二区| 国产精品每日更新在线播放网址| 免费xxxxx网站中文字幕| 黄色一级视频免费| 国产成人在线免费看| 五月天最新网址| 亚洲视频免费一区| 欧美日韩在线观看视频小说| 久久亚区不卡日本| 在线影视一区二区三区| 亚洲欧洲在线观看| 91啪九色porn原创视频在线观看| 亚洲成av人片在线观看无码| 日本视频精品一区| 97不卡在线视频| 十大黄色软件免费看| 精品国产免费久久| 性插视频在线观看| 天天摸在线视频| 久久久久国产精品嫩草影院| 日韩欧美精品网址| 在线观看一区二区三区视频| 天天摸夜夜添狠狠添婷婷| 久久无码av三级| 日韩国产精品亚洲а∨天堂免| 日本成人中文字幕在线| 国产成人三级一区二区在线观看一| 国产又粗又大又爽的视频| 亚洲欧洲日夜超级视频| www.九色.com| 成人免费播放器| 国内av免费观看| 国产一卡2卡3卡免费网站| 欧美大片免费观看网址| 欧美性猛交丰臀xxxxx网站| 香港日本韩国三级网站| 91精品国产综合久久精品图片| 日韩欧美黄色动漫| 99自拍视频在线观看| 99re66热这里只有精品4| 97久久亚洲| 精品久久国产视频| 国产在线视频网址| 亚洲 国产 欧美一区| 色综合一个色综合| 免费成人动漫| 精品国产国产综合精品| 日韩激情一区二区三区| 少妇精品无码一区二区免费视频| 久久久91精品国产一区二区精品| 亚洲美女爱爱视频| 97精品在线播放| 国产一区精品二区| 日韩av三级在线观看| 99久久一区三区四区免费| 国产中文字幕久久| 亚洲第一偷拍| 久久久久国产精品免费免费搜索| 9191在线播放| 成人黄色中文字幕| 精品视频一区二区三区四区五区| 亚洲日韩中文字幕一区| 视频二区欧美毛片免费观看| 欧美午夜视频在线| 久久黄色影院| 精品人妻一区二区免费视频| 成人高清在线| 久热精品视频在线观看一区| 精品自拍视频在线观看| 精品一区毛片| 欧美精品v日韩精品v国产精品| 5566先锋影音夜色资源站在线观看| 91成人在线免费视频| 懂色av一区二区三区四区五区| 男女爱爱视频网站| 国产亚洲精品久久久久久豆腐| 最新av网站在线观看| 蜜臀精品一区二区三区在线观看| 久久嫩草精品久久久久| 成人xxxxx| 成人3d动漫网站| 欧美精品一区二区在线播放| 精品成人av一区二区三区| 亚州欧美在线| 男女啪啪网站| 色猫av在线| 在线小视频网址| 国产在线观看av| 天堂网av成人| 成人在线播放av| 欧美在线一卡| 欧美另类高清zo欧美| 亚洲男人天堂2020| 久久婷婷国产91天堂综合精品| 日韩在线免费观看视频| av亚洲精华国产精华精华| 日本在线看片免费人成视1000| 中文字幕在线视频网| 色播五月激情综合网| 永久免费看av| 情侣黄网站免费看| 日韩黄色免费电影| 免费男女羞羞的视频网站中文子暮| 400部精品国偷自产在线观看| 日韩精品一区二区三区免费视频| 在线看三级网站视频| 国产69精品久久久久久久| 视频一区在线视频| 成人97精品毛片免费看| 澳门黄色一级片| 日韩成人午夜| 黄色成人精品网站| 国产欧美日韩一区二区三区在线观看| 另类视频在线观看+1080p| 国产精品二区影院| 日本精品免费一区二区三区| 亚洲欧洲国产精品久久| 国产真实老熟女无套内射| 国产精品18毛片一区二区| 制服.丝袜.亚洲.另类.中文| 成黄免费在线| 欧美极品视频一区二区三区| 在线亚洲观看| 亚洲成人激情在线观看| 在线女人免费视频| 在线视频成人| 亚洲涩涩在线观看| 亚洲一卡二卡三卡四卡无卡网站在线看| 精品福利一区二区三区| 国产欧美精品| 成人久久18免费网站漫画| 综合色婷婷一区二区亚洲欧美国产| 一区二区在线免费视频| 欧美一区二区视频在线观看2022| 毛片在线播放a| 欧美成人a视频| 中文字幕资源网在线观看| 91精品婷婷色在线观看| 三级福利视频| 日韩精品欧美激情| 老熟妇精品一区二区三区| 小泽玛利亚av在线| 亚洲欧美另类图片小说| 公交车强行挺进岳身体| av在线这里只有精品| 第四色在线一区二区| 亚洲人成毛片在线播放| 777奇米888色狠狠俺也去| julia中文字幕一区二区99在线| 国产社区精品视频| 欧美日韩国产bt| 免费av高清| 日韩av一区二区在线影视| 色偷偷久久人人79超碰人人澡| 色播在线观看| 青草av在线| 57pao成人国产永久免费| 国产成人综合久久| 欧美影院视频| 国产夫妻在线| 国产mv久久久| 日韩精品电影网| 日韩专区在线| 欧美人在线观看| 国产精品人成在线观看免费| 日本最黄一级片免费在线| 熟女人妇 成熟妇女系列视频| 久久黄色片视频| 日韩美女在线播放| 性视频1819p久久| 亚洲第一色中文字幕| 思思99热久久精品在线6| 国产树林野战在线播放| 欧美性猛交xxxx免费看久久久| 国内精品久久久久影院 日本资源| 中文字幕人成高视频| 性爽视频在线| 少妇饥渴放荡91麻豆| 日本在线一区| 日韩av高清不卡| 国产亚洲福利社区一区| 国产剧情在线视频| 久久精品女人| 成人午夜激情av| 红桃成人av在线播放| 日日夜夜视频|