高性能网络服务器的关键在于并发,如何高效的使用多核心的服务器,让多个线程并发处理程度。
并发有多种方式,如基于时间的并行和基于空间的并行,当然也有其他提法,但在计算机内部,最本质的是时间(CPU)和空间(内存)这两种资源,各种并行的界限并没有那么明显。所谓基于时间的并行,有两只猫,共同看守一个大仓库,但一个工作,另一个睡觉,两者不同时工作。所谓基于空间的并行,将仓库分为两个部分,这样两只猫可以分别看守不同的部分,两只猫在工作不紧张的时候就可以睡觉。
对于网络并发程序,可以从各个角度进行优化,先谈单个UDP的服务器,再谈单个TCP服务器,最后再谈服务器集群。
典型的UDP服务器,例如DNS服务器,有一个缺点,只能通过其著名的服务端口发布给客户端,所有客户端的流量集中在单个端口上,采用单个线程读取端口的数据效率最高,在读套接字时采用任何形式的并发都会导致性能的下降,对于某些版本的内核,多线程收同一套接字将产生惊群现象,这主要是由操作系统的进程调度方式引起的。因而比较好的模式是单个线程收,然后再将数据分发到后台不同的处理线程,然而在接收线程和处理线程之间的的数据传递可能需要采用锁的方式,这样就导致程序性能下降(然而在采取分主的情境下,即单生产者和单消费者,可以采用无锁队列的方式来实现,这样就可以大幅提高程序性能),如果某种形式的锁,为了避免频繁的加锁解锁操作,比较好的改进是数据的批传递,这样可以数百倍的减少加解锁的调用,也可以大幅减少线程的上下文切换开销。
在数据的返回阶段,典型的做法是在处理线程处理完成后,再把数据传递到发送线程,再通过原来的接收套接字将数据返回。这样在数据的发送段,又遇到了单点瓶颈。与读套接字相同,在单个套接字上发送数据时,任何并发的写也会导致发送效率的下降,这样看来只好使用单个线程来进行发送。但如果我们有多个处理线程,它们将数据汇集到单个发送线程队列中,必然又遇到一个加锁解锁开销。虽然在同一个套接字上读和写是对系统而言是采用不同的缓冲区,收发性能可以互不影响,但我们为了极大的提升程序的性能,必须解决掉发送瓶颈。这就是采用地址重用,通过多个套接字并发的发送数据。如果客户端对服务器的发送端口有要求,我们必须把多个套按字绑定到同一地址和端口上多次,这样就产生了一个时序的问题。由于内核在实现时,可以接收数据的套按字,只有最后一次绑定的可以接收 。因此必须将发送套接字在接收套接字绑定前完成绑定,否则应用程序就收不到任何数据了。通过这样的处理,再进行某些热点的优化,UDP程序就可以做到收和发将千兆网卡跑满。当然如果你有更快的网卡,就可以以读写内存的速度来处理数据。
TCP服务器分为两种,短连接服务器如web服务器,还有一些特殊的长连接服务器。将分别进行讨论。
对于单个服务器而言,再强大也没有一群服务器强大,这样就涉及到集群的部署和负载均衡策略,最后如果服务器过多,还需要解决服务器的动态加入和退出,以及服务器安全防护问题。