HTTP 499 問題處理方法合集

前言

在這篇文章中,我總結了最近處理的平常業務中的 499 問題,其中詳細描述出現 499 的緣由、以及定位過程,但願對你們有所幫助。php

HTTP 499 狀態碼

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

4 種 HTTP 499 問題

一、服務端接口請求超時

在客戶端請求服務端接口時,有些接口請求確實很慢。我來隨便舉個例子: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 致使斷開鏈接

還有一種狀況是 nginx 致使的 499 問題。

nginx proxy_ignore_client_abort 參數致使的 499 問題

從上圖咱們能夠發現,request_time 很是小,不多是請求接口超時,那是什麼緣由致使的這個問題呢?

其實從圖中有些信息尚未展現出來,由於涉及到公司的具體接口請求,我在這裏描述一下現象:圖中請求時間很是接近的兩個請求,其實請求參數如出一轍。

解決方案

經過谷歌發現,有同窗遇到過這種狀況,就是連續兩次過快的 post 請求就會出現 499 的狀況,是 nginx 認爲這是不安全的鏈接,主動斷開了客戶端的鏈接。

解決方案是在 nginx 配置:

proxy_ignore_client_abort   on; 
複製代碼

這個參數的意思是 proxy 忽略客戶端的中斷,一直等待着代理服務器的返回,若是沒有執行錯誤,則記錄的日誌是 200 日誌,若是執行超時,記錄的日誌是 504 日誌。

將此參數設置爲 on 以後,線上觀察一段時間後,發現基本沒有 499 的問題了。

注意事項

  1. 該配置只對代理服務器起做用,好比有有兩臺 nginx 服務器,第一臺服務器反向代理到第二臺 nginx 服務器,第二臺 nginx 服務器指向 PHP 服務,那麼該參數只有配置在第一臺 nginx 服務器上纔會生效,在第二臺 nginx 服務器上配置不會生效。

  2. 配置該參數後,當客戶端斷開鏈接以後,服務器仍然會繼續執行,存在着拖垮服務器的風險。因此線上根據本身的狀況合理使用。

三、固定時間出現 499 問題

還有一種狀況是固定時間出現 499 問題。

固定時間出現 499 問題

能夠看到,上述出現 499 的狀況都比較固定,都是在凌晨 2 點以後的十分鐘內。

這個時候咱們通常快速想到的是是否是有什麼定時任務佔用資源了,事實上確實如此,不過定位到這個問題卻不太容易,下面說一下我是怎樣定位這個問題的。

定位步驟

  1. crontab 腳本:首先我想到的是凌晨兩點有定時任務腳本,佔用了資源。我查看了報錯的機器上面的全部 crontab 腳本,發現只有一個是在凌晨兩點附近的腳本,我執行了一下,發現很是快,並不會執行特別長時間、佔用資源、進而影響正常業務。

  2. 機器:接着我懷疑出問題的這幾臺機器有問題,而後我查看了機器的監控圖表(使用的是 falcon),發現凌晨兩點並無什麼異常狀況。(其實這個也能夠推斷出來,該服務出現 499 在多臺機器上,不可能全部機器都有問題,並且這臺機器上還部署着其餘項目,其餘項目也沒有問題,說明不太多是這臺機器有問題。)

  3. 數據庫:既然是這個服務有問題,我又查看了這個服務鏈接的主要數據庫的監控圖表(一樣是 falcon),也沒有發現什麼問題。(實際上是有問題的,不過我沒有察覺)

  4. nginx:既然上述都沒有問題,是否是上層的 nginx 有什麼問題。由於 upstream_response_time 根本沒有值,有多是該請求根本沒有到後端的 PHP 服務,是否是凌晨兩點的時候 nginx 有問題。最後發現我其實理解錯了,一樣涉及上述參數 proxy_ignore_client_abort,若是該參數沒有設置爲 on 的話,到服務端的請求不會繼續執行,upstream_response_time 記錄的就是 -,這個是沒問題的。說明 nginx 沒問題。

  5. 異常請求:那麼有沒有多是凌晨兩點的時候有一波異常請求。該請求爲 post 請求,正好 nginx 開啓了 post 日誌,經過 nginx 記錄的請求參數,從新拼裝了一下進行請求,發現沒有問題。說明和請求參數無關。

  6. 數據庫

    1. 到這裏基本上就能想到的方案基本上都想到了。只能再找找這些請求都有什麼共同點吧。
    2. 發現這些請求都請求了一個數據庫,而後我找了一個請求量最大的接口,打印了數據庫請求的執行時間,發現一個簡單的接口居然要執行 2 秒,並且一夜居然有十幾個超過 2 秒的請求。而咱們的超時時間是 1 秒,因此會致使 499 問題。
    3. 這裏不管是否是致使 499 的緣由,都存在問題,須要進行處理,而後我又從新看了一下 falcon 監控,發現凌晨兩點的時候有一臺機器的 IO 和 CPU 異常。我登陸了這臺機器查看了 error 日誌。
  7. error.log

    1. error.log 中我發現了 499 請求接口的 SQL 語句,發現錯誤信息以下 mycat Waiting for query cache lock
    2. 從這條日誌咱們發現這條 SQL 被鎖住了,並且等待的是 query cache lock,該鎖是一個全局鎖,這裏咱們不過多介紹。
    3. query cache 在 MYSQL 8.0 中已經不建議使用了,由於若是表只要更新的話,就會清空 query cache,對於頻繁更新的表來講,沒有太大用處,若是數據量少、更新不頻繁的表,直接查庫就能夠,也沒什麼意義。並且若是開啓 query cache,一個查詢請求過來,就得先去 query cache 中尋找,找不到的話,去數據庫中查找,查找完了再把數據放到 query cache 中。
    4. 我查看了一下該數據庫,果真開啓着 query cache,我覺得是該緣由致使的,只要關了就能夠,而後又查看了一下其餘數據庫,發現也開啓着 query cache,這下能夠證實 query cache 不是致使該問題的緣由。順便說一下,查看 query cache 是否開啓的命令爲 show variables like '%query_cache%';query_cache_type 值爲 ON 則開啓,值爲 OFF 則關閉。
    5. 通過了一堆亂七八糟的試驗後,最終將重點放在什麼佔用了查詢緩存上。翻看錯誤日誌,發現有以下一條 SQL(已處理):select A,B,C from test where id > 1 and id < 2000000。這條 SQL 查詢了 200 萬條數據,再把這些數據放入 query cache 中,確定會佔用 query cache 了。
  8. slow.log

    1. error.log 中沒法發現該條 SQL 的來源,應該不是業務的 SQL,業務不會請求這樣的 SQL,經過 slow.log 中查詢 200 萬的 SQL 的請求 IP 發現,該請求來源於 Hadoop 集羣機器,該機器中有同步線上數據到 HIVE 的腳本,發現該腳本配置的數據庫的 IP 便是出問題的 IP。
    2. 而後修改了數據庫配置,將 IP 換成了一個非線上使用的從庫的 IP,觀察了幾天,即上圖中後面三天,明顯發現 499 狀況變少了。

總結

該問題定位起來很是難,花了好長時間,基本上把本身可以想到的狀況都一一試驗了一下,若是 MYSQL 沒有問題的話,可能還會繼續往機器層面去定位問題,在這裏主要是給你們一些思路借鑑一下。

四、偶爾出現一下 499 問題

咱們平時也會看到一兩個 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 中狀況以及定位問題、解決問題的方法,但願能給你們提供一些幫助。

相關文章
相關標籤/搜索