前置
原来一直没有遇到过高并发的场景, 这一次双十一说实话挺忐忑的,心里慌得一批.
架构的话大致是用supervisor
来启动golang的服务,接着用nginx
做负载均衡
这个项目用的是go的iris框架
,主要是一个淘宝转链的接口,请求原来一天差不多300万的请求量,qps差不多就是150左右.
说实话原来没怎么压测过哈哈哈,觉得业务简单,感觉这个流量下来对go完全是没压力啊.
所以双十一的准备是加了台服务器.
谁知道10号那天8点多开始暴增.. 整天的数据差不多是850w,接近原来的3倍。 11点多一个小时的量接近100w.
请求日志过多
最刚开始没抗住的竟然是数据库。
这里将请求日志全部打到了数据库存下来了。 请求就像龙卷风, 一下子就把数据库的磁盘占满了(这个也真的是想不到),解决方案也是很简单,充钱解决了.
能用钱解决的那都不是事儿,这个日志当然该存还是得存。
不过也得开始想想日志该存哪里了。
日志这块的表的话这边是做了分表的,一个月一张表。 但是实际上还是很大,查询这些基本都得加上时间筛选,要不然根本查不动啊。做做group的话就很慢。
日志系统的话这边现在常用的话要么es要不clickhouse都可以。
总而言之。日志这边如果只是小流量的话随便存mysql, nosql等都可以。 但是大起来了还是乖乖的好。
数据库cpu也开始升起来了
这边像日志都是一个请求insert一次。当流量大起来的时候,存数据库可能会来不及。
虽然插入相对来说很快, 但是数据量大了毕竟也是个问题。每次请求都创建数据库连接.
这边有两种想法:
- 就是先走缓存,将插入的数据统一存redis, 等积累到了一定数量,再做批量插入。
- 将所有日志放到队列中,然后快速返回, 让worker去做后台执行, 也就是把日志的插入从业务代码中抽离出去。
当然最合适的话还是有个专门的日志系统吧。
nginx open too many files
发现很多请求超时, 这样是真的有点慌了。查看nginx error.log
, 发现有很多"Too Many Open Files"
然后 ulimit -n
查看生产环境的打开文件数限制, 发现没有限制..
查了一下, 改大了worker_rlimit_nofile这个参数,发现缓解了一下,grep 没发现这个报错了.
no live upstream while connecting to upstream
继续查看nginx日志. 发现nginx这边提高了上限之后, 果然把问题又抛给了上游。
这个报错的原因是nginx这边做负载的时候是用的轮循的方式去请求上游。
但是整个过程中仍然找不到一台可用的服务。看来问题貌似更严重了。这不是意味着所有的golang服务都挂了么??
supervisorctl status project-name
查看了一下服务状态,发现是running,但是整个服务的运行时间只要一分钟。断定应该是刚重启
看来问题果然是出在这里了, 查看了一下supervisor上这个项目的日志, 发现又开始报too many files这个错误了???
too many files
- 根据报错内容是报了一个本地文件打开过多, 基于此, 发现这个文件是在一个方法里面, 每次请求都要打开一次这个文件。
于是去改代码,将这个文件打开的内容放到全局声明里面, 这样就只需要调用一次。 - 代码上线后发现还是报了这个错误,检查代码发现原来是因为用了第三方的一个vendor, 这个文件做了http请求但是还是没有做Close,没有主动将资源释放出来
- 改了代码之后发现还是不行,但是男人怎么能说不行呢
临时方案
线上问题时间实在耗不起, 由于是通过supervisor启动,文件打开数也受到supervisor配置的限制,
调高了supervisor的minfds这个参数,提高可打开的文件数,先让重启的频率变低
[supervisord]
minfds=50000 ; (min. avail startup file descriptors;default 1024)
minprocs=50000 ; (min. avail process descriptors;default 200)
执行cat /proc/pid/limits|grep "open files"
查看进程的打开文件数限制, 发现已经变大了, 进程重启的时间已经变短了
最终问题
通过 lsof -p pid 这个命令查看进程打开的文件,发现很多tcp连接一直保持着(localhost:8888->ip:port)这种,难道是请求了什么接口,但是也是没有关闭连接? 毕竟现在用的http1.1默认是长连接
重新查看代码, 还是一无所获.
在本地起了测试环境的服务, 用wireshark抓包看看,还是没什么进展。然后想到是不是可能是远程连接但是没有关闭http请求? 到线上日志中去查了查。刚好我们记的日志中有ip这个字段, 查了两个发现确实是客户端的请求ip。
终于知道问题出在哪里了。客户端做请求的时候用的是长连接,导致服务端这边的连接一直在,占用着一个fd描述符,短时间又没关闭,所以导致打开的fd一直在增加,直到重启。
解决方案
改动代码,在所有的请求中加上请求头connection:close
,强制使用短连接。上了一下试试,发觉阔以。至此too many open file
的问题算是差不多解决了.
后来在看书的时候发现可以直接在nginx中配置keepalive
即可.
思考
这次的修复可能实际上并不复杂,但是实际上排查问题还是花了我一天的时间。归根结底还是在于基础知识不够扎实,像tcp等基本看看就过了,了解是了解, 但是用不起来。 有点事后诸葛亮的意味,问题查出来的时候才想到原来是这样这样。
实际出问题的时候可能也不太会想到这边来。思维被局限住了。另外给我的感受是可能平时大家写代码都差不多,技术修养看不太出来,真的到查问题的时候才能体现出价值来。
个人平时看中的是代码的可读性,会写各种语言的hello world,自己觉得编程能力差不多了,有点犯了形式主义错误,还是应该更注重技术的深度,还有解决问题的能力。毕竟代码差不多教程看看基本都会写了,
有疑问加站长微信联系(非本文作者)