记录一次netty丢包的分析

问题背景

netty实现的tcp client和server,使用多线程(10线程,单批1000条数据,循环100次)发送数据,发现服务端接收到的数据总量和发送的数据量对不上。由于最开始的设计上就是有处理上限的,起初以为是数据量过大,并没有在意。
后面闲暇的时候,准备自测看一下,发现奇怪的是,收到的数据总是实际发送数量的80%,这就有点奇怪了

原因分析过程

1. 可能产生了死锁

查询资料,显示client同步发送(sync)时可能产生死锁。查看client日志,的确10个线程,最后只有8个线程输出了结束的日志.
使用jstack命令dump线程日志查看,并没有deadlock, 但是未打印日志的两个线程的确处于waitting状态,显示被locked。
修改为异步发送,线程状态正常,但是服务端依旧接收80%的数据。

2. 分析是否丢包

在client端outbound添加代码,打印发送数据量,发送方的确发送100w。
为方便wireshark抓包分析,client和server都修改为简单的发送序列号和解析收到的序列号并打印。
通过抓包分析,发现client的确发送了全部的数据,证明问题出在服务端

3. 猜测是服务端缓存满,触发丢包

上述测试过程,已经修改包大小很小,12byte一次,应该不存在这个可能性。
为验证,减少发包间隔,依旧丢包。
减少发送方线程数,1线程正常,5线程正常,8线程正常,10线程错误。
查看服务端日志,由于为nioeventloop配置了线程池,最终只创建了8个线程,所以造成了丢包。

4. 线程配置问题?

首先在想是否是多线程的线程安全问题?通过debug发现每个线程都创建了一个channel,所以应该不存在这个问题。
本着随便试试的想法,修改了线程池的参数,使core size=max poll size=20,数据接收正常!!
立即客户端创建20线程测试,也正常,最终server创建了12个worker线程,但是每个client都有独立的channel实例,
这里要注意一下,channel实例的pipeline,如果使用共享的pipeline,一定要注意线程安全!
至此,本问题解决。

5. 为什么会出现这个问题?

netty的nioeventloopgroup从线程池中取线程创建channel,而nioeventloopgroup就是nioeventloop的线程池,而nioeventloop就是一个线程,这个可以查看其构造函数的源码得知。所以如果client足够多,会最终分配worker大小的线程,然后多channel共享线程。但是线程池的线程数小于worker时,已经创建的nioeventloop一直占用线程,导致大于线程池可用线程数,小于worker数的几个nioeventloop一直不能创建,但是已经分配了channel,所以这几个线程绑定的client的数据全部丢失。

总结

  1. 建议使用异步发送,确保不会产生死锁
  1. 缓存满了丢包这个可能性比较低,不知道服务端netty的缓存什么时候释放,但是处理的效率还是很高的
  1. nio线程池配置为固定线程数,建议构造函数不要使用线程池了,使用线程数+threadfactory的配置

© Song 2015 - 2024