问题描述
有用户提出通过udp协议发送数据到实时平台,所以考虑在flume接收节点添加udp source来接收udp请求
- flume配置如下
1 | a2.sources.syslog_udp_src.type=syslogudp |
- 系统环境如下
1 | CentOS Linux release 7.2.1511 |
在测试中发现当发送速度超过500/s时开始出现数据丢失,开始以为是网络丢包,但通过tcpdump抓包比对发现并没有丢包
netstat -su
命令显示有数据包接收失败1
2
3
4
5
6Udp:
4329216468 packets received
329761 packets to unknown port received.
301745872 packet receive errors
337569082 packets sent
RcvbufErrors: 1061789
packets received
表示application接收到的数据包的数量packets to unknown port
表示由于端口没有打开而丢失的数据包数据量,例如应用挂掉或者重启packet receive errors
表示接收数据包的异常的数量,需要注意的是error和接收失败的数据包并不是一对一的关系,即有一个数据包有可能会产生多个error
问题原因
当客户端发送一个udp请求到flume时,整个流转顺序是这样的1
Client -> Kernel UDP socket buffer(4MB) -> Flume UDP Source buffer(64KB) -> Flume Channel buffer(磁盘,file channel)
flume通过SyslogUDPSource接收UDP请求,底层是使用Netty的OioDatagramChannelFactory来创建服务器端channel,实现BIO(阻塞式IO)。BIO会对每个请求分配一个线程来处理,这种处理模式效率很低。经过测试,Flume UDP只能达到1100+的QPS,0.324MB/s
当client的发送速度大于flume udp source的接收速度时,数据开始在Kernel UDP socket buffer堆积,当buffer满了的时候,后续的请求就会被丢弃,发生packet receive errors
即使平均吞吐量没有超过flume udp的接收能力,但如果出现发送速度波动或者网络波动导致短时间内Kernel UDP socket buffer被填满,也会出现接收失败
解决方案
- 网上看了一下,有的建议扩大Kernel UDP socket buffer,比如50MB
1
2sysctl -w net.core.rmem_max=52428800
sysctl -w net.core.netdev_max_backlog=2000
但这只能在一定程度上避免波动的问题,如果发送速度大于接收速度,再大的缓存总会有满的时候
用NIO重写flume SyslogUDPSource,提高处理效率,目前并没有这么做的打算,因为采取了下一个方案
部署syslog-ng接收udp请求,并转发到flume tcp source。Syslog-ng是一个轻量框架,并不需要对数据进行过多的处理和缓存,可以直接对udp请求进行转发,并且使用的是多路复用的NIO,因此处理效率很高。即使是这样,经过压力测试发现也同样是有瓶颈,当qps大于24000时开始出现同样的packet receive errors,因为无论如何syslog-ng也是有处理上限的
1
22.5W QPS,丢包率0.008%
10W QPS,丢包率2.2%以上两种方案在数据量大到一定程度的时候都不可避免的丢包,所以如果可以的话,应该在数据发送之前就进行必要的过滤,把不需要的数据扔掉,降低请求数
最终的解决方案,也是我最推崇的:
放弃UDP,改用TCP