在海量数据的收集中,大部分网络日志都是通过udp协议传输的,所以为了保证日志的完整性,udp的收包性能显得极其重要,所以对此主题进行一系列的研究。
操作系统:linux 3.10.0-229.el7.x86_64
网络带宽:千兆网
机器内存:32GB
处理器:4核
硬盘空间:1T
硬盘IO:实测300MB/S
JDK版本:1.8
1、报文大小:870b
测试对象:java udp,用户层抓包,内核抓包
测试结果:
速率PPS | bio-udp | 用户层收包(tcpdump) | 内核收包(pf-ring) |
3200 | 100% | 100% | 100% |
6400 | 99.17% | 100% | 100% |
13000 | 98.87% | 99.78% | 100% |
从表格中可以看出在低速pps情况下,用户层收包和内核收包相差并不大,java的udp收包和tcpdump性能也比较接近,三者差距看不出来。
2、报文大小125b
测试对象:syslog报文,不解格式收包,用户层收包,内核抓包
字节B | 速率PPS | syslog | bio-udp | 用户层收包(tcpdump) | 内核收包(pf-ring) |
125 | 6400 | 98.91% | 100% | 100% | 100% |
13000 | 98.18% | 99.82% | 100% | 100% | |
26000 | 61.89% | 97.44% | 100% | 100% | |
50000 | 32.15% | 94.79% | 99.71% | 100% | |
60000 | / | / | 99.43% | 100% | |
70000 | / | / | 98.73% | 100% | |
100000 | / | / | 76.67% | 100% | |
200000 | / | / | 42.06% | 100% | |
500000 | / | / | 37.73% | 99.98% |
降低报文大小,提高pps测试,可以发现:
1)收包率和报文大小关系不大,关键因素是报文发送速率。
2)syslog解析性能急速下降,大部分时间都花在解析工作上,导致丢包率非常高
3)内核收包在50Wpps下,依旧保持近100%的收包率,性能非常强劲
4)用户层抓包在7Wpps之后丢包率明显上升,可能受限于cpu中断
5)java 的udp收包和tcpdump差的并不多,其实原理是一样的,原因大概是日志的输出以及程序其他地方的性能损耗
6)此时,java 的udp收包最多做到1Wpps下逼近100%收包率
3、报文大小50b
测试对象:用户层抓包,内核抓包
字节B | 速率PPS | 用户层收包(tcpdump) | 内核收包(pf-ring) |
50 | 67W | 29.74% | 99.46% |
83W | 29.63% | 93.87% | |
100W | 29.42% | 79.18% |
为了看看内核抓包性能究竟有多强悍,把速率提高到了100Wpps,发现一个有意思的事情,tcpdump的丢包率在降到30%左右,一直维持稳定,这儿我也没想明白。内核抓包在80Wpps后,收包率也有明显的下滑趋势。可能是cpu性能的原因,看来内核抓包也做不到百万级抓包,按照这个测试结果,他的性能应该就是每秒80W级别的处理速度(pf-ring的dna模式或许可以达到真正的百万级)。
通过上面的测试发现,内核抓包有着无可比拟的优势,逼近每秒百万级的性能,但是由于实际使用中,限制太大对网卡型号要求太高,需要重新编译网卡驱动,需要root的权限,c语言开发等等限制条件,导致应用到实际项目中,适用场景太少且难度较大。反观tcpdump的性能,虽然无法和pf-ring比,但是也有每秒5W的速度,然而我们的程序udp收包只有每秒4W多的速度,在5wpps收包率只有94%,这意味着每一亿条报文,就会丢失600W,还是非常吓人的,所以这方面还是非常有必要去优化提高收包率的。
优化一:
使用NIO流来处理网络消息,测试报文125b
速率PPS | bio-udp | nio-udp | 用户层收包(tcpdump) |
6400 | 100% | 100% | 100% |
13000 | 99.82% | 100% | 100% |
26000 | 97.44% | 99.82% | 100% |
50000 | 94.79% | 99.23% | 99.71% |
优化结果:性能提升还是非常明显的,在5Wpps下,收包率提高了近5分点,意味着一亿条可以多收500W,但仍旧有几大十万的丢失,还是没有达到tcpdump的性能
优化二:
使用AIO流来处理网络消息,测试报文125b
速率PPS | bio-udp | aio-udp | 用户层收包(tcpdump) |
6400 | 100% | 100% | 100% |
13000 | 99.82% | 100% | 100% |
26000 | 97.44% | 99.76% | 100% |
50000 | 94.79% | 99.27% | 99.71% |
优化结果:测试结果发现几乎和nio没差,看来收包层面已经达到极限了。与tcpdump依旧有0.5的差距,分析之后,定位是报文落地磁盘导致了收包过程有中断
优化三:
借鉴零拷贝的思想,落地磁盘也采用内存映射的方式,mappedByteBuffer
优化结果:有稍许提升,依旧距离tcpdump较远
优化四:
直接采用内存队列的方式,让收包线程完全脱离写磁盘的职责
速率PPS | bio-udp | aio-udp | 内存映射+队列+bio | 用户层收包(tcpdump) |
6400 | 100% | 100% | 100% | 100% |
13000 | 99.82% | 100% | 100% | 100% |
26000 | 97.44% | 99.76% | 99.99% | 100% |
50000 | 94.79% | 99.27% | 99.69% | 99.71% |
优化结果:在26000PPS下,已经99.99%的收包率了,可以说保证能在2.5Wpps下不丢包。在5Wpps下也无限逼近tcpdump的性能,tcpdump我只测试了两次取得平均值,前者测试了6次取得平均值。先天条件如此,能用到的最新的技术全上了,到这个地步已经是极限了,不过应该可以应付99%的场景了。pf-ring以及tcpdump毕竟抓的只是pcap包,并不解析协议,所以这个已经是非常好的结果了。
最后附一张全测试的表格:
字节B | 速率PPS | syslog | bio-udp | nio-udp | 内存映射+队列+nio | 用户层收包(tcpdump) | 内核收包(pf-ring) |
869 | 3200 | / | 100% | / | / | 100% | 100% |
6400 | / | 99.17% | / | / | 100% | 100% | |
13000 | / | 98.87% | / | / | 99.78% | 100% | |
100% | |||||||
393 | 3200 | / | 100% | / | / | 100% | 100% |
6400 | / | 99.23% | / | / | 100% | 100% | |
13000 | / | 98.75% | / | / | 99.99% | 100% | |
26000 | / | 98.41% | / | / | 99.70% | 100% | |
3200 | 100% | 100% | 100% | 100% | 100% | 100% | |
125 | 6400 | 98.91% | 100% | 100% | 100% | 100% | 100% |
13000 | 98.18% | 99.82% | 100% | 100% | 100% | 100% | |
26000 | 61.89% | 97.44% | 99.82% | 99.99% | 100% | 100% | |
50000 | 32.15% | 94.79% | 99.23% | 99.69% | 99.71% | 100% | |
60000 | / | / | / | / | 99.43% | 100% | |
70000 | / | / | / | / | 98.73% | 100% | |
100000 | / | / | / | / | 76.67% | 100% | |
200000 | / | / | / | / | 42.06% | 100% | |
500000 | / | / | / | / | 37.73% | 99.98% | |
50 | 67W | / | / | / | / | 29.74% | 99.46% |
83W | / | / | / | / | 29.63% | 93.87% | |
100W | / | / | / | / | 29.42% | 79.18% |