一种高效的网络服务器设计

  最近在做一个有关时空数据查询的分布式服务器的原型系统(主要是为了论文啦),其中需要实现一个比较高效的服务器。现将近一段时间的实验和服务的大概框架记录下来,希望能对分布式服务器感兴趣的同学有所帮助。 同时,希望各位提出各种建议,这样我能够进一步的改进。代码已经放在了github上, 写的比较烂,这里就不公布链接了(嘿嘿), 想讨论的同学请私信我~  以下是全部记录:

  在我们实现的分布式服务器中,每个节点都存有时空数据,这些节点上的时空数据利用RTree进行索引。每个节点同时保存着路由(邻居节点)信息。客户端可以向服务器端发送数据查询请求。服务器负责接收请求,并根据请求的类型进行相应的处理, 包括查询本地的数据,转发请求到邻居节点。

  时空数据是以数据流的形式源源不断的插入到服务器中。当某个节点的数据达到系统预定义的阈值时,该节点将分裂成两个节点。我们将计算数据的最优划分策略划分数据,并将划分的数据迁移到新的节点上。同时更新当前节点和新建节点的路由信息。

  针对上面的要求,我们的服务器需要满足以下两个目标:

  • 满足高并发的数据查询请求和数据传输请求
  • 同时处理长连接和短连接

  服务器中同时存在两种连接方式:长连接和短连接。长连接是指Client端与Server端先建立通讯连接,连接建立以后不断开,然后再进行报文的发送和接收。长连接常用于点对点的通讯,在服务器中,数据传输采用长连接。短连接是指Client端和Server端只有在进行一次报文收发操作时才进行通讯连接,操作完毕以后立即关闭连接。短连接一般针对一点对多点的通讯,如多个Client连接一个Server。在服务器中,数据查询请求采用短连接。

  常见的网络服务器采用以下几种方式:

  1. 一个线程服务一个客户端,使用阻塞I/O
  2. 一个线程服务多个客户端,使用非阻塞I/O
  3. 一个线程服务多个客户端,使用异步I/O

  第1种方式缺点在于当并发连接数过高时,操作系统同时处理几百个线程会有性能问题。而且在服务器硬件配置不改变的情况下,随着连接数的增加,将会导致性能急剧下降。第3种方式目前在Unix上还没有普遍的应用,因此,在我们的系统中并没有采用。第2种方式是将网络句柄设置为非阻塞模式,然后使用select或者poll IO多路复用技术来告知那个句柄已经有数据在等待处理。在此模型下,由系统内核来告知应用程序某个网络句柄的状态。这种模型也就是传统的IO多路复用模型,是目前大部分网络服务器的解决方案。

  我们的服务器是基于Linux开发的,在Linux上,实现IO多路复用有几个主要的技术,分别是select模型,poll模型和epoll模型。

  select模型最大的缺点是最大并发数限制,因为一个进程所打开的socket FD(文件描述符)是有限制的,默认是1024,因此select模型的最大并发数就被限制了。当然,我们可以通过修改系统参数增大最大并发数,然而由于select的每次调用都会线性扫描全部的socket FD集合。当socket FD增大时,会导致服务器效率线性下降。

  poll模型基本上和select在效率上是一样的,select的问题在poll模型中也没有被解决。

  epoll模型是目前比较优秀的IO多路复用模型,首先,epoll没有最大并发连接的限制,上限是整个系统最大可以打开的socket FD数目,这个数远大于2048,一般这个数目和系统内存关系很大,1G内存的机器的最大sockef FD数目可以达到10万左右。其次,epoll最大的优点在于它只关心活跃的连接,而跟连接总数无关。因此在实际的网络环境中,epoll的效率会远高于select和poll。

  鉴于epoll的优点,我们的服务器采用了epoll作为IO多路复用的基本技术。

  常见的基于epoll的设计模式主要为单线程的事件循环,用于一些非阻塞的业务逻辑开发是很高效的,然而,在我们的服务器开发中,涉及传输数据。转发请求的需求,耗时比较长。因此,单线程的epoll并不能满足我们的需要。下面用一个简单的例子来说明单线程模式下epoll的缺点。

  由于是单线程模型,当某个客户端的请求处理时间较长时,会影响服务器接收来自其他客户端的连接请求,进而影响整个服务器的并发性能。

  因此,单线程的epoll模型在我们的分布式服务器中并不适用。下面是我们服务器的设计方案:

          

                                     图2                               图3

  上面这种设计模式一般称为Reactor模式,Reactor模式是处理并发I/O比较常见的一种模式,中心思想是首先将所有要处理的I/O事件添加到一个中心的多路复用器上(epoll), 同时主线程阻塞在多路复用器上;一旦有连接到来或者是准备就绪,多路复用器将返回并将相应的I/O事件分发到对应的处理线程中。

  我们的服务器采用了多个Reactor,也就是多线程的epoll。 Reactor被分为main reactor和sub reactors,分别对应图2 和图3。每个reactor中都有独立的epoll来作为多路复用器。其中main reactor中的epoll负责监听连接请求,一旦有连接到来,利用一定的分发策略将连接socket加入到sub reactors的epoll中。对于每个sub reactor的epoll,主要的工作就是监听连接socket,一旦某个连接socket的I/O准备就绪,则通知相应的handler来接收数据并处理请求。

  通过使用非阻塞I/O的多路复用技术epoll,并将连接请求与连接建立的之后的逻辑分离,我们设计了基于Reactor设计模式的服务器,满足了高并发的处理数据查询请求与数据传输请求。并能够同时处理长连接与短连接。 同时,还有一些细节我们可以改进:

  • main reactor 到sub reactors的分发策略

  目前我们采用了Round-robin的方式,这样有可能产生负载不均衡的现象。 后面我们可以使用一定的策略,将main reactor接收到的连接请求分发到相对空闲的sub

  reactor中。保证整个系统的负载均衡。

  • sub reactor的多线程化

  对于每个sub reactor来说,这是单线程的。 我们同样可以将sub reactor进一步划分, 将数据的接收与请求的处理分离,请求的处理采用线程池的方式。这将进一步提高服务  器的并发能力。

 

  特此感谢ChinaUnix论坛的@linux_c_py_php一篇帖子给我带来的巨大帮助,友情链接:http://bbs.chinaunix.net/thread-4067753-1-1.html

 

  转载请注明出处: http://www.cnblogs.com/meibenjin/p/3604389.html 

 

posted @ 2014-03-17 05:02  AfterSummer  阅读(2903)  评论(13编辑  收藏  举报