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 中状况以及定位问题、解决问题的方法,但愿能给你们提供一些帮助。

相关文章
相关标签/搜索