在这篇文章中,我总结了最近处理的平常业务中的 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 中状况以及定位问题、解决问题的方法,但愿能给你们提供一些帮助。