技术实践(原创)

记录一次生产环境的堵塞异常分析

记录一次生产环境的堵塞异常分析

ZUUL网关链接后端。导致堵塞的原因分析

现象

网关堵塞,底层服务入口堵塞,页面无法打开。

思路

第一个想法 看日志,通过ELK+日志链路追踪,如果如下,分析不出问题,请求堵塞
在这里插入图片描述
分析监控也不是很明显
在这里插入图片描述
查看了服务的CPU 内存 磁盘,已经容器里面的空间都很正常;

进一步分析究其原因

环境意外堵塞,API Getway接受请求异常。日常的服务请求架构如下:
在这里插入图片描述
仔细分析日志发现在请求不可用之前的异常日志:
java.io.IOException: Broken pipe
at sun.nio.ch.FileDispatcherImpl.writev0(Native Method)
at sun.nio.ch.SocketDispatcher.writev(SocketDispatcher.java:51)
at sun.nio.ch.IOUtil.write(IOUtil.java:148)
at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:504)
at org.xnio.nio.NioSocketConduit.write(NioSocketConduit.java:184)
后端还在发送数据,前端就将这个连接关闭(也就是请求的处理时间超过了Nginx设置的超时时间)
结合业务分析代码在特殊情况下,我们运维管理员管理450个账户。而所有的账户信息都会在调用一次账户额度接口。
代码层面我们这边用的是并发流parallelStream 去处理。而parallelStream 用的是ForkJoinPool,工作窃取模式并不会按照队列的大小创建线程(窃取就是要窃取之前处理完任务,闲置的线程)。所以效率上达不到预期中的结果。响应时间自然就超过预期。

遇到这种问题解决方法

1.延长超时时间(简直是智障的操作,严重影响用户体验)
2.程序改造。缓存或者批量接口,减少服务之间的交互代价。将接口交互修改成批量操作,并发处理简单的逻辑(由于是ForkJoin模式,也不会导致频繁创建线程和销毁线程)。

很喜欢网上一个段话:就如同人憋尿憋的时间太长的话,会感到很不舒服,长期这样不但对身体不好,还。。。。甚至也可能会导致"管道破裂"。所以不要憋尿。

但是我们服务还有很多时候,并不是想象中的那么脆弱,除非是数据库堵塞。数据库堵塞或者事务没提交带来的冲击会更大,这种情况下我们要合理设置MYSQL数据库备份的时间和频率(一般选择业务低峰的时候,比如说凌晨 2-3点),而且要支持动态配置,如果遇上11.11 正好在夜里执行,那影响可想而知。事务未提交的话,我们这边遇到的都是自己写的开启事务,并没有合理的设置事务关闭。