Udp 并发问题分析与总结
0、前言
最近在做一个项目,从架构来说可以归结为实现一个udp的Server。业务逻辑上的问题已经清楚,现在需要实现服务的高并发。
一、Libev对于网络高并发的优势
在调研初期,主要着眼于“网络高并发”,这样比较自然的查到了Libevent和Libev。以Libev为例,它是一款高性能的时间循环库。Libev之所以长于解决网络高并发问题,主要是因为其支持epoll、select等多路复用机制。select的操作是线性复杂的,在使用select时,当文件描述符的数量较大后(一般以1024为界限),就需要再起额外的线程。而epoll的操作是常数的,所以无论有多少个文件描述符,都不影响其性能。不过,一般epoll仍然需要和多线程配合。常见的做法是,主进程通过epoll机制观察有无新的请求,当有新的请求,就由一个工作线程进行处理。
二、tcp并发与udp并发的区别
Libev适用于网络高并发,主要由于它支持epoll、select等多路复用机制,加之与多线程配合,性能较好。但是无论是epoll还是select,在观察有无数据就绪时,都是针对多个文件描述符。如果只有一个文件描述符,那么进程只要观察那一个文件描述符即可。在网络编程中,一个Socket对应一个文件描述符。Tcp协议的server在监听端口前初始化一个socket,每有一个新的连接,就新建一个socket。因此当tcp服务器面对高并发请求时,实际上有多个socket,也就是有多个文件描述符。Libev适用于这种模式。Udp协议的Server没有真正意义上的“连接”的概念,在监听端口和响应请求时都只有一个socket,也就只有一个文件描述符。因此,对于udp服务器,Libev实际上意义不大。虽然Libev支持网络IO事件,也就支持udp协议,但是在udp socket中直接使用epoll效果不佳,测试发现由于存在额外开销,将小幅度降低性能。
三、udp并发的常规思路
大部分udp服务器是顺序迭代的,服务器等待客户端请求,然后读取请求,处理请求,发回响应。但是,当处理客户端请求需要很长时间,就需要考虑某种形式的并发。一个“长处理”可以理解为处理请求的时间明显大于发送请求的时间。
并发的常见思路是使用多线程。服务器在读取一个新请求之后,可以交由一个线程处理,该线程在处理之后直接将响应内容发给客户端。另一方面,udp服务器和多个客户端交互,但是却没有多个socket。典型的解决方案是,服务器为每个客户端创建一个新的socket,并绑定一个新的端口。客户端以后就通过这个新的socket与服务器通信,获得响应。总结来说,udp并发服务器,针对多个客户端,可以创建多个socket;针对多个请求,可以使用多线程(线程池)进行处理。
四、本项目与高并发
本项目需要实现一个udp服务器,其特点是多客户端,服务为“短处理”,并且需要进行写操作。这里不对项目细节做过多阐述。第三部份有所阐述,在服务为“长处理”时,并发有其必要性。但是本项目是短处理,又需要加锁。这样来看,多线程并发就没有很强的必要性。另外Libev支持的多路复用机制主要针对互联网应用,动辄上万个并发连接。而项目属于节点通信,客户端预计在几十和上百之间。经过少量节点实验,加入Libev反而小幅度降低性能。因此,我准备不加入Libev和多线程,使用单线程实现系统。目前的demo系统,测试性能最佳。
那不加并发能不能顶住流量压力呢?这最终还需要实验验证,不过我初步推断应该可以。我使用5个客户端(客户端数量偏少)模拟并发测试,每微秒发送一个请求,服务器出现少量丢包。真实环境是一个几十M的文件对应一个几byte的请求,这样的数据比例导致请求不会过于频繁。当有数据包丢失,也可以通过加入重传机制解决。