在這篇文章中,我總結了最近處理的平常業務中的 499 問題,其中詳細描述出現 499 的緣由、以及定位過程,但願對你們有所幫助。php
nginx 中的 src/http/ngx_http_special_response.c
文件中對 499 狀態碼進行了定義:mysql
ngx_string(ngx_http_error_494_page), /* 494, request header too large */
ngx_string(ngx_http_error_495_page), /* 495, https certificate error */
ngx_string(ngx_http_error_496_page), /* 496, https no certificate */
ngx_string(ngx_http_error_497_page), /* 497, http to https */
ngx_string(ngx_http_error_404_page), /* 498, canceled */
ngx_null_string, /* 499, client has closed connection */
複製代碼
從註釋上,咱們能夠看到 499 表示客戶端主動斷開鏈接。nginx
表面上 499 是客戶端主動斷開,然而在實際業務開發中,當出現 HTTP 499 狀態碼時,大部分都是因爲服務端請求時間過長,致使客戶端等的「不耐煩」了,所以斷開了鏈接。git
下面,來講一下,我在平常開發中遇到的 4 中 499 問題,以及相應的問題定位處理方法。github
在客戶端請求服務端接口時,有些接口請求確實很慢。我來隨便舉個例子:sql
select * from test where test_id in (2,4,6,8,10,12,14,16);
複製代碼
好比咱們有一張 test
表,表中有 500 萬條數據,咱們查詢了上述的一條 where in
SQL,而 test_id
字段並無索引,致使進行了全表掃描。這條 SQL 就很慢,請求個幾秒鐘都是很正常的。數據庫
若是客戶端設置了超時時間,到了超時時間就自動斷開,就會致使 499 問題,若是客戶端沒有設置超時時間,好比這條 SQL 請求了 5 秒鐘,而 php-fpm 的超時時間爲 3 秒,就會致使 502 問題。後端
解決這種問題很簡單,找到對應的慢請求,優化便可,通常狀況下都是慢 SQL,好比上面的例子,字段沒有索引,加個索引就行了。緩存
固然也存在有索引不走索引的狀況,好比我以前遇到的問題,MYSQL 選錯索引問題分析,就是即便有索引可是仍是沒走索引的問題。安全
這種狀況呢,就是接口是真的慢,不是偶然現象,是何時請求都慢,這個也最好解決,優化接口便可。
還有一種狀況是 nginx 致使的 499 問題。
從上圖咱們能夠發現,request_time
很是小,不多是請求接口超時,那是什麼緣由致使的這個問題呢?
其實從圖中有些信息尚未展現出來,由於涉及到公司的具體接口請求,我在這裏描述一下現象:圖中請求時間很是接近的兩個請求,其實請求參數如出一轍。
經過谷歌發現,有同窗遇到過這種狀況,就是連續兩次過快的 post 請求就會出現 499 的狀況,是 nginx 認爲這是不安全的鏈接,主動斷開了客戶端的鏈接。
解決方案是在 nginx 配置:
proxy_ignore_client_abort on;
複製代碼
這個參數的意思是 proxy 忽略客戶端的中斷,一直等待着代理服務器的返回,若是沒有執行錯誤,則記錄的日誌是 200 日誌,若是執行超時,記錄的日誌是 504 日誌。
將此參數設置爲 on 以後,線上觀察一段時間後,發現基本沒有 499 的問題了。
該配置只對代理服務器起做用,好比有有兩臺 nginx 服務器,第一臺服務器反向代理到第二臺 nginx 服務器,第二臺 nginx 服務器指向 PHP 服務,那麼該參數只有配置在第一臺 nginx 服務器上纔會生效,在第二臺 nginx 服務器上配置不會生效。
配置該參數後,當客戶端斷開鏈接以後,服務器仍然會繼續執行,存在着拖垮服務器的風險。因此線上根據本身的狀況合理使用。
還有一種狀況是固定時間出現 499 問題。
能夠看到,上述出現 499 的狀況都比較固定,都是在凌晨 2 點以後的十分鐘內。
這個時候咱們通常快速想到的是是否是有什麼定時任務佔用資源了,事實上確實如此,不過定位到這個問題卻不太容易,下面說一下我是怎樣定位這個問題的。
crontab 腳本:首先我想到的是凌晨兩點有定時任務腳本,佔用了資源。我查看了報錯的機器上面的全部 crontab 腳本,發現只有一個是在凌晨兩點附近的腳本,我執行了一下,發現很是快,並不會執行特別長時間、佔用資源、進而影響正常業務。
機器:接着我懷疑出問題的這幾臺機器有問題,而後我查看了機器的監控圖表(使用的是 falcon),發現凌晨兩點並無什麼異常狀況。(其實這個也能夠推斷出來,該服務出現 499 在多臺機器上,不可能全部機器都有問題,並且這臺機器上還部署着其餘項目,其餘項目也沒有問題,說明不太多是這臺機器有問題。)
數據庫:既然是這個服務有問題,我又查看了這個服務鏈接的主要數據庫的監控圖表(一樣是 falcon),也沒有發現什麼問題。(實際上是有問題的,不過我沒有察覺)
nginx:既然上述都沒有問題,是否是上層的 nginx 有什麼問題。由於 upstream_response_time
根本沒有值,有多是該請求根本沒有到後端的 PHP 服務,是否是凌晨兩點的時候 nginx 有問題。最後發現我其實理解錯了,一樣涉及上述參數 proxy_ignore_client_abort
,若是該參數沒有設置爲 on
的話,到服務端的請求不會繼續執行,upstream_response_time
記錄的就是 -
,這個是沒問題的。說明 nginx 沒問題。
異常請求:那麼有沒有多是凌晨兩點的時候有一波異常請求。該請求爲 post 請求,正好 nginx 開啓了 post 日誌,經過 nginx 記錄的請求參數,從新拼裝了一下進行請求,發現沒有問題。說明和請求參數無關。
數據庫:
error.log:
error.log
中我發現了 499 請求接口的 SQL 語句,發現錯誤信息以下 mycat Waiting for query cache lock
。query cache lock
,該鎖是一個全局鎖,這裏咱們不過多介紹。query cache
在 MYSQL 8.0 中已經不建議使用了,由於若是表只要更新的話,就會清空 query cache
,對於頻繁更新的表來講,沒有太大用處,若是數據量少、更新不頻繁的表,直接查庫就能夠,也沒什麼意義。並且若是開啓 query cache
,一個查詢請求過來,就得先去 query cache
中尋找,找不到的話,去數據庫中查找,查找完了再把數據放到 query cache
中。query cache
,我覺得是該緣由致使的,只要關了就能夠,而後又查看了一下其餘數據庫,發現也開啓着 query cache
,這下能夠證實 query cache
不是致使該問題的緣由。順便說一下,查看 query cache
是否開啓的命令爲 show variables like '%query_cache%';
。query_cache_type
值爲 ON
則開啓,值爲 OFF
則關閉。select A,B,C from test where id > 1 and id < 2000000
。這條 SQL 查詢了 200 萬條數據,再把這些數據放入 query cache
中,確定會佔用 query cache
了。slow.log:
error.log
中沒法發現該條 SQL 的來源,應該不是業務的 SQL,業務不會請求這樣的 SQL,經過 slow.log
中查詢 200 萬的 SQL 的請求 IP 發現,該請求來源於 Hadoop 集羣機器,該機器中有同步線上數據到 HIVE 的腳本,發現該腳本配置的數據庫的 IP 便是出問題的 IP。該問題定位起來很是難,花了好長時間,基本上把本身可以想到的狀況都一一試驗了一下,若是 MYSQL 沒有問題的話,可能還會繼續往機器層面去定位問題,在這裏主要是給你們一些思路借鑑一下。
咱們平時也會看到一兩個 499 的狀況,可是再從新執行一下該請求,響應速度很是快,根本沒法復現,這種偶爾出現的狀況,咱們通常能夠忽略。這裏呢,我和你們簡單說一下可能致使該問題的緣由。
MYSQL 會有將髒頁數據刷到磁盤的操做,這個咱們具體咱們會有一片單獨的文章介紹。在 MYSQL 執行刷髒頁的時候,會佔用系統資源,這個時候,咱們的查詢請求就有可能比平時慢了。
並且,MYSQL 還有一個機制,可能讓你的查詢更慢,就是在準備刷一個髒頁的時候,若是這個數據頁旁邊的數據頁恰好是髒頁,就會把這個」鄰居「也帶着一塊兒刷掉;並且這個把」鄰居「拖下水的邏輯還會繼續蔓延,也就是對於每一個鄰居數據頁,若是跟它相鄰的數據頁也仍是髒頁的話,也會放到一塊兒刷。
找」鄰居「這個優化機制在機械硬盤時代是頗有意義的,減小了尋道時間,能夠減小不少隨機 IO,機械硬盤的隨機 IOPS 通常只有幾百,相同的邏輯操做減小隨機 IO 就意味着系統性能的大幅度提高。可是如今 IOPS 已經不是瓶頸,若是「只刷本身」,就能更快的執行完必要的刷髒頁操做,減小 SQL 響應時間。
有趣的是,這個機制在 MYSQL 8.0 一樣被禁止了,該功能由 innodb_flush_neighbors
參數控制,1
表示有連刷機制,0
表示只刷本身。MYSQL 8.0 innodb_flush_neighbors
默認爲 0。
咱們來看一下咱們的線上配置:
mysql> show variables like "%innodb_flush_neighbors%";
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| innodb_flush_neighbors | 1 |
+------------------------+-------+
1 row in set (0.00 sec)
複製代碼
很不巧,咱們線上是開啓的,那麼就頗有可能忽然一個請求 499 啦。
上面咱們介紹了在平常業務開發中,會出現 499 的 4 中狀況以及定位問題、解決問題的方法,但願能給你們提供一些幫助。