最近生產環境出現502 報警較多,經過排查問題,有些問題還挺有意思。經過分析nginx 源碼,對查nginx 狀態碼來源可能會帶來必定啓發。本文基於1.6.2(主要是和生成環境對齊)。nginx
首先常見的錯誤碼,定義在ngx_http_request.h, 這裏有部分是client 引發的,有部分是upstream 引的,到底在什麼狀況下會引發下面這些問題?查問題從哪些方面入手?後端
#define NGX_HTTP_CLIENT_CLOSED_REQUEST 499 #define NGX_HTTP_INTERNAL_SERVER_ERROR 500 #define NGX_HTTP_NOT_IMPLEMENTED 501 #define NGX_HTTP_BAD_GATEWAY 502 #define NGX_HTTP_SERVICE_UNAVAILABLE 503 #define NGX_HTTP_GATEWAY_TIME_OUT 504 #define NGX_HTTP_INSUFFICIENT_STORAGE 507
access.log 會打req 的status, 須要去查status 賦值邏輯。debug
grep -r status= src|grep 502
後端狀態碼5xx 的邏輯基本在ngx_http_upstream.c 的 ngx_http_upstream_next 中,這裏是狀態碼的code
switch(ft_type) { case NGX_HTTP_UPSTREAM_FT_TIMEOUT: status = NGX_HTTP_GATEWAY_TIME_OUT; break; case NGX_HTTP_UPSTREAM_FT_HTTP_500: status = NGX_HTTP_INTERNAL_SERVER_ERROR; break; case NGX_HTTP_UPSTREAM_FT_HTTP_403: status = NGX_HTTP_FORBIDDEN; break; case NGX_HTTP_UPSTREAM_FT_HTTP_404: status = NGX_HTTP_NOT_FOUND; break;
這裏ft_type 和 status 有個對應關係,這裏ft_error NGX_HTTP_UPSTREAM_FT_TIMEOUT 跟504 ,NGX_HTTP_UPSTREAM_FT_HTTP_500 和 500 等有一對一對應關係,其餘的ft type 都使用502 。這裏就須要具體查下ft 的賦值狀況。get
#define NGX_HTTP_UPSTREAM_FT_ERROR 0x00000002 #define NGX_HTTP_UPSTREAM_FT_TIMEOUT 0x00000004 #define NGX_HTTP_UPSTREAM_FT_INVALID_HEADER 0x00000008 #define NGX_HTTP_UPSTREAM_FT_HTTP_500 0x00000010 #define NGX_HTTP_UPSTREAM_FT_HTTP_502 0x00000020 #define NGX_HTTP_UPSTREAM_FT_HTTP_503 0x00000040 #define NGX_HTTP_UPSTREAM_FT_HTTP_504 0x00000080 #define NGX_HTTP_UPSTREAM_FT_HTTP_403 0x00000100 #define NGX_HTTP_UPSTREAM_FT_HTTP_404 0x00000200 #define NGX_HTTP_UPSTREAM_FT_UPDATING 0x00000400 #define NGX_HTTP_UPSTREAM_FT_BUSY_LOCK 0x00000800 #define NGX_HTTP_UPSTREAM_FT_MAX_WAITING 0x00001000 #define NGX_HTTP_UPSTREAM_FT_NOLIVE 0x40000000 #define NGX_HTTP_UPSTREAM_FT_OFF 0x80000000
504, NGX_HTTP_GATEWAY_TIME_OUT 在ngx_http_upstream.c 中有幾處會賦值,源碼
if (downstream->write->timedout) { c->timedout = 1; ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_REQUEST_TIME_OUT); return; } if (upstream->read->timedout || upstream->write->timedout) { ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT); return; }
ngx_connection_t *c; c = u->peer.connection; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream process non buffered upstream"); c->log->action = "reading upstream"; if (c->read->timedout) { ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT); return; } ngx_http_upstream_process_non_buffered_request(r, 0);
c = u->peer.connection; rev = c->read; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream process body on memory"); if (rev->timedout) { ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT); return; }
三處都是從upstream 中取鏈接,而後讀或者寫超時,能夠看出504 的主要主要緣由,是讀寫下游超時。it
503 ,NGX_HTTP_SERVICE_UNAVAILABLE , grep 下就能夠發現,主要是在limit 限流模塊會出現,io
grep NGX_HTTP_SERVICE_UNAVAILABLE -r src src/http/modules/ngx_http_limit_req_module.c: NGX_HTTP_SERVICE_UNAVAILABLE); src/http/modules/ngx_http_limit_conn_module.c: NGX_HTTP_SERVICE_UNAVAILABLE);
源碼能夠比較清晰看出來經過 ngx_http_limit_req_merge_conf 這裏重置了狀態碼,而ngx_http_limit_req_merge_conf 會再 ngx_http_limit_conn_handler 中調用,這裏限流被命中則返回503event
static ngx_int_t ngx_http_limit_conn_handler(ngx_http_request_t *r) { ... if (r->main->limit_conn_set) { return NGX_DECLINED; } lccf = ngx_http_get_module_loc_conf(r, ngx_http_limit_conn_module); limits = lccf->limits.elts; for (i = 0; i < lccf->limits.nelts; i++) { //處理每一條limit_conn策略 } return NGX_DECLINED; }
502 相對比較複雜點,出現狀況比較多。grep 502 , NGX_HTTP_BAD_GATEWAY 等實現,ast
1,能夠看出ngx_resolve_start 在 resolve 階段,resolve 失敗會NGX_HTTP_BAD_GATEWAY
2, upstream->read/write 遇到eof / 0 /error 的時候會NGX_HTTP_BAD_GATEWAY, recv 系統調用返回n, 大於0時是讀寫字節數, 在接受到fin 的時候會返回0, 其餘錯誤的時候返回-1。這裏常見的一種錯就是,nginx 的下游掛了,會返回給上游一個fin,而後502 返回給client。
3,在upstream 鏈接階段,ngx_http_upstream_connect 鏈接下游失敗報錯會 傳 NGX_HTTP_UPSTREAM_FT_ERROR 給ngx_http_upstream_next 。
rc = ngx_event_connect_peer(&u->peer); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream connect: %i", rc); if (rc == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } u->state->peer = u->peer.name; if (rc == NGX_BUSY) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no live upstreams"); ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_NOLIVE); return; } if (rc == NGX_DECLINED) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; }
if (u->buffer.last == u->buffer.end) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "upstream sent too big header"); ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); return; }
499 相對而言就比較簡單了, NGX_HTTP_CLIENT_CLOSED_REQUEST 在client 訪問nginx 時,若是主動close 了,nginx 就會記錄 499,這個狀態碼不會返回給client,只本地記錄。