Broken pipe异常分析及解决
Broken pipe异常分析报告
1.错误描述
ClientAbortException: java.io.IOException: Broken pipe
可能出现原因:
TCP服务端write数据时,收到SIGPIPE信号(连接已经终止)
场景:
- TCP握手尚未结束时,连接已经close;
- 服务端收到一次read,但write了多次;
- 连接通道被占满,新连接被拒绝时,client中断了所有连接。
2.分析过程
2.1.初步排查
每次出现该异常时,总是伴随/im/getUsercInfos.json接口的调用。
2.1.1具体现象
- 异常记录时间=接口请求记录时间(server连接write前记录)-(2至10)ms;
- 总是ios设备爆出;
- 总是h端爆出。
2.1.2分析
可能性一:
在请求该接口时,ios设备在某种情况下会中断该请求,导致TCP连接中server端无法向client端write数据。即在server端write之前连接已经close,write时出现异常。
由于只有h端出现,但h、b客户端代码一致,不太可能单独出现,故可能性较低。可能性二:
在第一次连接已经正常握手,并正常close后,server端在此执行write操作。即对一个对端已经关闭的socket调用两次write,报出SIGPIPE信号,导致异常。
由于只有ios设备有该问题,故可能性较低。可能性三:
请求接口本身问题或并发调用问题,引起了连接close或连接过多超限导致client端close连接。
目前acceptCount配置100,观察日志,报错时间区间中并没有如此大量的并发请求,且client端收到拒绝信息时,不确定是否会中断所有请求(理论上不会)。故可能性较低。
2.2.细致排查
伴随该异常的接口,入参和出参量较大(猎头端im对话列表1000-3000个是常态),接口处理时间长,且伴随不同程度的连续请求。
2.2.1具体现象
- 入参传入emNames数量600-4000个,约15000-100000字符长度;
- 出参返回约60000-500000字符长度;
- 执行时常约500-4000ms;
2.2.2分析
可能性一:
处理报文过长,client端无法解析/处理过大报文,导致本次及下次请求。
客户端系统对于大报文的处理问题需要测试和调研,故有可能性。可能性二:
处理时间过长,导致当client端并发请求时,当上次请求尚未完成,下次请求会close上次请求,以本次为准。
调研后发现client端是阻塞请求,但需要进行实际测试,故有可能性。可能性三:
处理时间过长,在握手过程中client自行中断了连接。
由于执行时间较长,频率较高,客户或设备自己可能触发kill进程或关闭连接等操作,故有可能性。
3.测试过程
3.1.场景设计
- 服务端:
返回参数量大、处理时间长的接口 - 客户端:
h端客户端;大量输入参数;并发调用接口的工具 - 接口:
URL:/im/getTests.json
入参:emNames (List,每条约24字符)
出参:PageForm
逻辑:根据emNames数量,组装并返回同等数量的PageForm(每条约250字符),根据需求sleep若干时间(3000-5000ms)。
3.2.测试报告
3.2.1.IOS
ios,4000参数,3次并发,服务端无sleep: 无问题
ios,4000参数,3次并发,服务端sleep 3000ms: 无问题
ios,2000参数,20次并发,服务端sleep 5000ms,中途client切出: 无问题
ios,2000参数,20次并发,服务端sleep 5000ms,中途client中断: 发现问题,完全符合异常现象
3.2.2.ANDROID
- android,2000参数,10次并发,服务端sleep 5000ms,中途client中断:发现问题,完全符合异常现象
4.结论
4.1.问题原因
client端用户在杀死进程时,接口的TCP请求尚未完成(未完成的原因是处理时间长)。
导致server端write数据时,收到SIGPIPE信号,抛出Broken pipe异常。
但由于已经杀死了进程,并不会对用户产生任何影响。
4.2.其他结论
- ios和android在处理1M以内(尚不清楚有没有最大值)大小报文时,没有问题
- ios和android均是阻塞请求,本次请求不会对上次请求造成影响,即使上次请求尚未完成
- ios切换至后台,短时间内(5000ms以内,最大值根据系统不同而不同)并不会中断已经存在的TCP连接。
- android切换至后台,并不会中断已经存在的TCP连接。
- ios、android杀死进程会一并关闭已经存在的TCP连接。
- android框架(公司基于google改造的框架)默认并发5线程一组。
5.解决方案
由于kill进程我们无法控制,故只能通过降低接口处理时间,减少用户kill进程时未完成的TCP连接数量。
具体:
- 会话列表翻页
- 会话列表限制展示数量
- 客户端分组获取会话列表数据