论坛首页 综合技术论坛

SPServer : 一个基于线程池(包括HAHS和LF)的高并发 server 框架

浏览 57556 次
该帖已经被评为精华帖
作者 正文
   发表时间:2007-06-17  
我说的是我上面的想法,是reactor作底层;处理部分是半同步/半异步;requestRead这种算是proactor的做法,请求/回调,当然我那个想法里面回调可以是反应器所在的线程,也可以是用来处理的线程池,原理上和proactor是相近的。
0 请登录后投票
   发表时间:2007-06-18  
关于 MsgDecoder 再作一些说明。

class Protocol  
{  
    /** 
     * bytes: 要读取的字节数 
     * callback: 读取完成时的回调委托函数 
     * recvall: 是否要读取所有请求的字节数才回调 
     * threading: 是否交由线程池 
     * 
     * void delegate(char[] data, bool finished) 参数: 
     * data: 数据 
     * finished: 是否已经接收完成。对于recvall为true的,回调时这个参数肯定为true,否则可能为true或false 
     */  
    void requestRead(size_t bytes, void delegate(char[], bool) callback, bool recvall, bool threading);  
}


在 qiezi 的实现中,使用 requestRead 来指定需要读取的内容。这个函数的用意和 MsgDecoder 是一样的。
只不过 requestRead 里有指定是否采用 threading 的参数,MsgDecoder 没有。

当时考虑到要使用一个对象来实现 requestRead 的功能,主要是考虑到实际的环境中,有很多情况是无法用长度来判断的(比如 XMPP,LDAP等协议)。所以就使用一个 MsgDecoder 对象,在这个对象中可以实现任意复杂的协议解码。当然,使用对象来实现,也相应增加了复杂度。这个复杂度可以通过提供一些常用的 MsgDecoder 来简化。

同时,在具体的使用过程中,很多时候是否采用 threading ,不一定能够在读取内容之前就决定,而是需要在读取内容之后才能决定。曾经考虑在 MsgDecoder 中加入指定是否需要 threading 的接口。主要是担心在应用程序不断修改演进的过程中,可能原来不使用 block system call 的功能,后来又使用了,如果实现者忘记修改关于 threading 的设定,会导致整个程序出现问题。所以目前还是没有在 MsgDecoder 中加入这个功能。

0 请登录后投票
   发表时间:2007-06-19  
我大致看了一下代码,还有一点疑问,也是我比较关心的。

目前看到的很多网络服务器框架都有一个共同的问题,退出比较草率,直接结束掉事件循环,实际上有些正在接收、正在处理或正在发送中的操作都没有优雅地结束掉,所以实际在编写服务器程序时,我宁愿自己写这部分,使用每连接一线程方式做这种还是比较简单的,当然可能性能问题比较严重,不过最近做的工作都是TCP短连接的,同时忙的线程也不会太多,问题还不大。

通常在结束时先关闭侦听端口,再等待处理中的操作结束,这个结束当然包括它处理完了并且发送完回复,使用libevent的event_loopexit,或者是spserver的shutdown,我都没有看到这样一个优雅的关闭过程。我自己也使用了libevent,发现还是比较难操作,特别交给线程池处理的操作,使用event_loopexit可能不行,像spserver一样自己处理循环应该还是可以做到的,只需要在检测到退出标志后,关闭侦听端口,再等待队列处理空,然后结束掉线程池。
0 请登录后投票
   发表时间:2007-06-20  
引用
目前看到的很多网络服务器框架都有一个共同的问题,退出比较草率,直接结束掉事件循环。。。。。。

在检测到退出标志后,关闭侦听端口,再等待队列处理空,然后结束掉线程池


的确是这样。这两天正在看 apache 的代码,apache 在这方面应该是做的比较好的,通过发送信号,让 apache 优雅地结束掉。

spserver 在这方面的实现的确不够。
遇到的主要问题是:SP_Handler 的子类需要参与到这件事情上去。因为对于有些长连接的协议,如果 SP_Handler 不在处理过程中指示要结束连接,那么会导致队列总是不为空。上面提到 apache 实现了“优雅地结束”,其中有一个原因就在于 http 协议比较简单,一个 request 过来之后,返回一个 response 就可以了。而对于像 smtp 这类协议,如果在发出结束信号的时候,协议交互才刚刚开始,那么就需要等待这个连接进行完一次完整的 smtp 协议交互。这样等待的时间就不可预测了。
所以最终 spserver 对于这一点,采取了不作为的态度。这个也是一直在考虑改进的功能点。曾经考虑的解决方法是在 shutdown 方法中增加一个 timeout 时间,如果能在 timeout 时间内等到队列为空,那么优雅地结束。如果超过 timeout 时间,队列都不为空,那么就强行退出。不过还是不够完整。

要实现“优雅地关闭”,主要的考虑应该是想保证服务的可靠性?之前看过关于 XMPP 协议在可靠性方面的讨论,提到只在网络接口这一层保证可靠还不足于保证可靠性,而需要在协议的定义上来保证。可以参考这个帖子中引用的URL:http://iunknown.iteye.com/blog/57430

另外,如果需要最大限度地保证服务器的可靠性,那么最彻底的解决方法其实是服务器端把每个连接对应的处理状态保存到持久存储中。在这种情况下,即使服务器异常退出(core dump,或者直接被 kill -9 ),也能从持久存储中恢复处理的状态。就目前了解的情况来看,有些 SMTP 服务器是这样做的。曾经也考虑过 XMPP 的实现,如果要保证每条消息的可靠传输,那么需要在接收到消息之后,需要把消息保存起来,然后再进行转发,转发成功之后,删除消息。这种策略在 《POSA2》 关于 ACT (异步完成标记)的讨论中,也提到过。
0 请登录后投票
   发表时间:2007-06-20  
libevent把线程给阻隔了,通常操作系统提供的接口比如epoll/select还有各个socket操作函数都支持多个线程的,libevent把这个优点给掩盖了,当然使用它的理由也很充分,自己做超时处理还是比较麻烦,如果不做超时处理,我宁愿自己使用epoll了。多线程实现起来是点麻烦,ACE的线程池Reactor在处理每一个有事件的handle时,都会暂时清除掉该handle上的事件,所以它可以用多个线程跑事件循环,不过估计效率会比单线程低,事件部分还是单线程比较好。

spserver目前是自己处理事件循环的,使用event_dispatch应该也可以吧?自己处理有什么优点吗?当然其中一个好处是其它线程也可以调用shutdown。使用event_dispatch就会一直循环,这正是想要的。当需要退出时,只需要先关闭侦听端口,由于spserver使用了msgqueue,所以总是会有事件,等到各个线程处理完毕并且也没有其它WRITE事件了,就可以调用event_loopexit了。

持久存储是一个方案,不过通常服务器结束时都希望它把已经接收到的连接处理完毕,并不希望下次还要重启才能处理完成,重启后socket连接已经失效,就算把数据包给处理了也没办法回复。
0 请登录后投票
   发表时间:2007-06-20  
qiezi 写道
libevent把线程给阻隔了,通常操作系统提供的接口比如epoll/select还有各个socket操作函数都支持多个线程的,libevent把这个优点给掩盖了,当然使用它的理由也很充分,自己做超时处理还是比较麻烦,如果不做超时处理,我宁愿自己使用epoll了。多线程实现起来是点麻烦,ACE的线程池Reactor在处理每一个有事件的handle时,都会暂时清除掉该handle上的事件,所以它可以用多个线程跑事件循环,不过估计效率会比单线程低,事件部分还是单线程比较好。


libevent 只是把《Unix Network Programming》中提到的“迭代服务器程序”做了一个封装。
http://iunknown.iteye.com/blog/41077
也就是说,如果使用 libevent 来实现“迭代服务器程序”,那么是最自然,最方便的。
在 UNP 中还提到了其他的服务器程序架构,这些不同的服务器程序架构,对于 I/O 的处理流程都有所不同。
如果使用 libevent 来实现其他的服务器程序架构,那么就需要使用一些迂回的方法。比如:可以使用 libevent 来实现 ThreadPerConnection 的模型,每个线程一个独立的 event_base,而且这个 event_base 也可能就只有一个 IO 句柄。当然,如果迂回的太多,就会使得 libevent 带来的坏处超过好处,这个时候就不如放弃 libevent ,另寻解决方法。

qiezi 写道
spserver目前是自己处理事件循环的,使用event_dispatch应该也可以吧?自己处理有什么优点吗?当然其中一个好处是其它线程也可以调用shutdown。使用event_dispatch就会一直循环,这正是想要的。当需要退出时,只需要先关闭侦听端口,由于spserver使用了msgqueue,所以总是会有事件,等到各个线程处理完毕并且也没有其它WRITE事件了,就可以调用event_loopexit了。


spserver 可以使用 event_base_dispatch 。目前使用 event_base_loop( eventArg.mEventBase, EVLOOP_ONCE ) 主要就是为了能够控制程序自行退出。其实 event_base_dispatch 的实现也就一行代码:

int
event_base_dispatch(struct event_base *event_base)
{
  return (event_base_loop(event_base, 0));
}


qiezi 写道
持久存储是一个方案,不过通常服务器结束时都希望它把已经接收到的连接处理完毕,并不希望下次还要重启才能处理完成,重启后socket连接已经失效,就算把数据包给处理了也没办法回复。


通常持久存储记录的信息是一些比较高层次上的状态。比如对于 smtp 来说,记录的可能是“服务器是否已经把邮件完整接收了”这样的状态,而不是某个数据是否发送成功了。如果已经接收了,但是在正式投递的时候,smtp 服务器异常退出,那么在 smtp 重启之后,就能够从持久存储中获得相应的信息,然后重试之前未完成的任务。当然,smtp 可以这样做的原因,也在于 smtp 协议在协议层的规定:

引用
RFC2821
In sending a positive  completion reply to the end of data indication, the receiver takes full responsibility for the message (see section 6.1).


所以,对于服务器可靠性的保证,还在于协议的设计。
0 请登录后投票
   发表时间:2007-06-21  
iunknown 写道

当时考虑到要使用一个对象来实现 requestRead 的功能,主要是考虑到实际的环境中,有很多情况是无法用长度来判断的(比如 XMPP,LDAP等协议)。所以就使用一个 MsgDecoder 对象,在这个对象中可以实现任意复杂的协议解码。当然,使用对象来实现,也相应增加了复杂度。这个复杂度可以通过提供一些常用的 MsgDecoder 来简化。

目前我打算第一个基本实现的handle类(对应于twisted的Protocol)是样的:
class Protocol
{
    void dataReceived(void* p, size_t len);
    void requestThread();
}

如果是无法用长度来判断还接收多少数据的,就在Protocol上派生类处理就行了,想让线程处理时调用requestThread就行。

对于有长度前缀的,可以使用另一个基类:
class Proactor : public Protocol
{
    void dataReceived(void* p, size_t len)
    {
        取request列表,回调相应接口,根据要求调用requestThread
    }

    void requestRead(...);
}

可以看出来这个Proactor完全是在reactor上模拟的一个小玩意。
0 请登录后投票
   发表时间:2007-06-22  
引用
可以看出来这个Proactor完全是在reactor上模拟的一个小玩意。


呵呵,这两天重新看 POSA2 关于 reactor/proactor 的论述,理解的更多了一点。对于 spserver ,我也有这种看法。

reactor 和 proactor 的主要区别:
proactor 是由于异步操作的完成事件触发 handler 的执行,reactor 是由于产生了可以进行非阻塞操作的事件触发 handler 的执行。

libevent 可以说是一个 reactor 的实现。这一点通过对比 reactor 的 Event_Handler 和 libevent 的 callback ,可以和清楚地看到是非常类似的。两者在接口上,体现的是 IO 句柄是否可读或可写。

spserver 也就是相当于基于 libevent 的 reactor 封装了一个 proactor 出来。也是从对比 proactor 和的 Complete_Handler 和 SP_Handler 可以看出来。两者在接口上,体现的是异步操作的结果:proactor 是 Async_Result ,spserver 是 SP_Request->getMsgDecoder() 。

PS:上次提到的实现 connector 的想法,看来可以通过重构 spserver 为一个 proactor 而实现。
0 请登录后投票
   发表时间:2007-06-22  
connector最主要的挑战在于异步,另外还有个容易忽略的东西就是host解析,所以实际上connector有个2阶段异步操作。在twisted里面,它先发起一个解析host的异步操作,解析完成时再发起connect异步操作。

twisted的架构是非常先进的,我感觉作为reactor实现,它已经超过ACE这个原始却笨重的框架了,另外它还提供了很多延迟回调(异步)方法,这和它的语法上的优势有很大关系。
0 请登录后投票
   发表时间:2007-06-22  
引用
ACE这个原始却笨重的框架


从这两天读 reactor/proactor 的描述来看, ACE 的实现有一个比较大的问题就是不符合目前流行的 DI 要求。各个类之间存在循环依赖,即使在 POSA2 的书上是少量的代码和完整的文字描述,依然看得云里雾里。

引用
twisted的架构是非常先进的,我感觉作为reactor实现,它已经超过ACE这个原始却笨重的框架了,另外它还提供了很多延迟回调(异步)方法,这和它的语法上的优势有很大关系。


使用 python 也有几年的时间了,不过都只是用来做一些比 shell 脚本稍微复杂的工作。还没用 python 做过服务器开发。twisted 也久闻大名了,有时间要认真看看。
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics