Roger Chen's profileLearningBlogLists Tools Help

Blog


    4/20/2006

    Cindy 3.0b1 released

     
    修改记录:
    • 添加了hello world示例
    • 添加了简单的tcp/ip服务示例(echo/discard/daytime/chargen)
    • 添加了telnet示例(tcp/udp)
    • 更新了http server示例,支持列表目录
    • 为DefaultPacket添加了两种构造函数:DefaultPacket(ByteBuffer)/DefaultPacket(byte[])
    • 添加了SessionType类,并为Session/SessionAcceptor接口添加了getSessionType方法返回Session类型
    • 为Configuration类添加了set方法
    • 在SessionFactory类中加入了createSession(SessionType)和createSessionAcceptor(SessionType)两个方法,原来的createSocketSession/createDatagramSession/createSocketSessionAcceptor方法被deprecated
    • 修正DirectDispatcher.block中的bug,原实现在判断是否为核心线程方面存在Bug
    • 一些小的更改

    终于发布beta1了,API结构已经稳定下来了,原来使用cindy 3.0 alpha版本的用户请尽快升级到beta版,以后的版本发布只会更新bug或增加功能,但是不会影响到API结构。

     

    User Guide也同步升级,谢谢wei_cheng提出的很多好问题!

    4/4/2006

    CVS和Issue Tracker迁移到javaeye

    昨天抱怨了一通sourceforge的CVS服务后,Robbin非常热情的为我开放了javaeye的CVS服务,并且导入了我本地的CVS历史记录。同时javaeye还提供了ViewCVS、CVSTrac、JIRA以及Confluence,开发起来真是太方便了,非常感谢Robbin!(顺便抱怨一下,sourceforge提供的Issue Tracker系统的确不好用)
     
    我在googlegroup上也申请了一个Cindy的讨论组当作mailing lists,如果问题方便公开的话,建议大家还是发邮件到这里,这样方便知识共享。
     
    根据这些改动,Cindy的主页也做了相应更新。
    4/3/2006

    Sourceforge的稳定性还真有待提高

    因为以前版本都是我一个人开发,所以代码都是使用本地的CVS,一来稳定,二来速度快。现在庄表伟加入后,我想还是把代码check到sourceforge的CVS上,这样协同比较方便。

     

    可是自从我把Cindy 3.0a5版本的代码check到sourceforge上后,连续4天我都没有办法再连接上sourceforge的CVS服务(匿名用户的CVS服务一直可以,但是开发者的CVS一直没法连接上) :(发现没有CVS真是寸步难行。修改的地方越多担心就越大,无法提交,也无法恢复成CVS上的当前版本,慢慢的就不敢下手改代码了。

     

    实在没办法,等了它四天都没有修复,只能恢复成使用本地CVS了。将已经确认的改动提交进去后,就松了一口气,开始放心大胆的改代码了:)

    4/1/2006

    Selector.select(timeout) throws NullPointerException occasionally (win)

    今天本想对Cindy进行一些效率上的优化,尽量减少对Selector.select的调用以及传递给Selector的句柄数量,没想到意外又触发了nio的一个Bug:在Windows平台上,Selector.select调用可能抛出空指针异常。
     
    堆栈类似于:
    java.lang.NullPointerException
     at sun.nio.ch.WindowsSelectorImpl$FdMap.remove(WindowsSelectorImpl.java:76)
     at sun.nio.ch.WindowsSelectorImpl$FdMap.access$3000(WindowsSelectorImpl.java:66)
     at sun.nio.ch.WindowsSelectorImpl.implDereg(WindowsSelectorImpl.java:523)
     at sun.nio.ch.SelectorImpl.processDeregisterQueue(SelectorImpl.java:131)
     at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:121)
     at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69)
     at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80)
    我机器上有四个Java版本,在Sun JDK 1.4.2_05、Sun JDK 1.5.0_02、JRockit 1.5.0_04上都不会出现问题,只有在Sun JDK 1.5.0_06上面能重现Bug。反编译sun.nio.ch.WindowsSelectorImpl,可以看到其FdMap.remove中忘记判断空了(红色代码部分):
     
          private MapEntry remove(SelectionKeyImpl selectionkeyimpl)
            {
                Integer integer = new Integer(selectionkeyimpl.channel.getFDVal());
                MapEntry mapentry = (MapEntry)get(integer);
                if(mapentry.ski.channel == selectionkeyimpl.channel)
                    return (MapEntry)remove(integer);
                else
                    return null;
            }
     
    到Sun的Bug database中一查,早已有人提交了该Bug,这个Bug目前只在mustang b75版本中得到了解决。
     
    我看了看Sun以前版本的实现,虽然不会出现该Bug,但是恐怕会有其他的问题。Sun可能是想修改代码来解决原来的一些Bug,没想到引入了一个新Bug:(虽然Sun bug database中已经有了解决方案,但是绝大部分的用户应该都不会自己去修改Sun的源码然后打包的。
     
    目前发布的这个Cindy版本不会受到这个Bug的影响。鉴于Sun目前还没有将该Bug的修正放到Java 5.0的升级包中,所以我也暂时取消对Cindy效率上的一些优化(由于这些优化才触发了NIO的Bug),等到它升级包发布出来后再另行考虑。
    3/27/2006

    Cindy 3.0a5 released

     
    修改记录:
    • 修正ant脚本中未使用fork加载http server的bug
    • 将内部BufferCache接口改名为BufferPool
    • 在Session接口中,使用getAttribute/setAttribute/removeAttribute代替原来的getAttachment/setAttachment方法(谢谢wei_cheng和Arbow)
    • 提高了DefaultBufferPool的实现效率,分配和释放时根据Buffer大小使用不同的锁
    • 重新设计了SessionAcceptor接口,统一了原来的SocketSessionAcceptor和ServerSocketChannelSession
    • 修正了AbstractChannelSession中的分发事件顺序可能不一致的Bug
    • 为ServerSocketChannelSession添加了多次accept行为
    • 添加了acceptor/session.socketSession/session.datagramSession的运行期配置
    • 重载了AbstractBuffer类的equals方法
    • 一些小的改进

    比较重要的几点改进:

    1. 为ServerSocketChannelSession添加了多次accept行为。原来是触发了OP_ACCEPT后只accept一次,然后继续监听OP_ACCEPT;而现在则是触发了OP_ACCEPT后一直accept,直到accept返回为null,再继续监听OP_ACCEPT。加入这一行为后,在未达到临界点前,系统的性能大约提高了10%左右。可参考HP上的一篇文档:Exploring the Performance of Select-based Internet Servers
    2. 重新设计了SessionAcceptor接口,统一了原来的SocketSessionAcceptor和ServerSocketChannelSession两种不同的模型。应用不再需要循环accept了,在默认情况下使用NonBlockingSessionAcceptor(即相当于Cindy 3.0a4版中基于session的acceptor),性能比原来有所提升。具体改进可见cindy中的示例代码。
    3. 修正了AbstractChannelSession中的分发事件顺序可能不一致的Bug。分发事件顺序的Bug一直在修正,老是解决了老问题后又发现了新问题:(,不过目前看来新问题出现的越来越少了:)

    比如如下代码:

    startFuture.setSucceed(true);
    getSessionFilterChain(false).sessionStarted();

    第一行代码使得Future完成事件被触发,第二行代码使得SessionFilterChain上的sessionStarted事件被触发。看上去没有任何问题,是吗?

    可是,如果加入这么一个FutureListener:

    startFuture.addFutureListener(new FutureListener() {
         public void futureCompleted(...) {
               session.close();
         }
    });

    在使用DirectDispatcher的情况下,事件的触发顺序就变成了: Start future completed(上面的第一行代码) --> session close --> session closed --> session started(上面的第二行代码),出现了明显的错误。

    所以这次Cindy 3.0a5中把所有这种顺序派发的事件全部改在Dispatcher内完成,比如上面的代码就改成了:

                // keep dispatch order
                dispatch(new Runnable() {

                    public void run() {
                        startFuture.setSucceeded(true);
                        getSessionFilterChain(false).sessionStarted();
                    }
                });

    可以参考DirectDispatcher中的代码,就能了解为什么这么做就能保持事件分发顺序。

    3/25/2006

    目标

    最近常被问到的一个问题是:做高效率的网络应用都是用C/C++,为什么要用Java?我也一直在思考这个问题,这牵涉到Cindy的定位,以及将来的发展方向。
     
    如果存在一个这样的假设:C/C++和Java在网络处理方面拥有接近的运行效率,你会选择用什么语言开发网络应用?我的答案会是Java。为什么?因为对于大部分普通程序员而言(某些C/C++高手除外),在业务逻辑的开发效率上,Java要超出C/C++许多,这也正是Java流行起来的原因。
     
    但是遗憾的是,这个假设目前还不存在:( JSR 203提议在Java中加入对操作系统本身异步IO的支持,但没有人推动;IBM推出过aio4j,不开源,而且在2004年就停止了更新。所以目前在你所看到的现实中,这类应用仍旧是C/C++的天下。
     
    虽然假设并不存在,但需求是切切实实存在的:大家对Java网络方面的效率有着更高的期望。比如Tomcat,在它的5.5版本中有基于ARP的JNI实现;比如Resin以及其他很多商业WEB服务器,都有JNI的实现。但是遗憾的是,这些JNI并不是一个独立的网络框架,所以虽然这些人都在自己发明轮子,但是其他人很难用上他们发明好的轮子。
     
    既然有需求,而且需求无法得到满足,那么这里就有机会。
     
    做为一个比较资深的Java程序员,做为一个Java网络框架的开发者,我所关心的是:能不能让这个假设成为现实?或者能不能缩小它们的差距,给Java这边加些砝码?这就是Cindy的长远目标。目前虽然我的能力离实现这个目标还有差距,不过只要目标清晰,一直往前走,总是能实现的。
     
    希望能有志同道合者和我一起来实现这个目标:)
    3/14/2006

    Cindy 3.0a5计划加入Qos控制

    本来打算下一个版本就b1了,不过这两天发现一个非常值得改进的地方:发送的Qos控制,而且该改动会影响一些现有的API,如send(Object, int)/flush(Object,int)等等,所以还是下一个版本还会是alpha版本。

     

    在a4的实现中,Qos控制是由一个PriorityQueue来完成的,根据不同的优先级权重来调整发送顺序。但是该实现只是一种特定的Qos控制,我的计划是把Qos控制这部分分离出来,可以由用户来进行设置。

    比如Concurrent包中有Delayed接口和DelayQueue实现,DelayQueue能够让添加进去的Delayed对象在指定时间后才能被取出,这种Qos控制可以用于控制包发送间隔时间。对于类似WEB、FTP这类应用,发送速度是越快越好,Qos要求可能不明显;但对于流媒体这类应用,并不是发送速度越快越好,只要保证流媒体能够流畅观看即可,发送速度过快反而可能导致其他人无法流畅观看,这种Qos控制就非常有用。

     

    但是Concurrent包的Queue/BlockingQueue接口并不能直接用于Cindy的Qos控制,因为它们只提供了阻塞和轮询两种处理方式,这种开销对Cindy来说是不可接受的,所以需要实现队列的回调方法。另外可配置性的Qos控制怎么与现有的发送API、SessionFilter、Packet接口等结合起来,仍在考虑之中,希望能想出一个比较可行的解决方法来。

    3/11/2006

    Cindy 3.0a4 效率测试

    硬件环境:

    服务器和客户端为同一台机器:XEON 3.0G/2G/SUSE 10.0/Kernel 2.6.13-15-smp

    软件环境:

    Java虚拟机均采用BEA JRockit,版本号为R26.0.0-189-53463-1.5.0_04-20051122-2040-linux-ia32,运行时参数均指定为-Xms512m -Xmx512m。

    除Resin外,所有server的backlog都设为10000(未找到Resin中设置该选项的位置)。QuickServer虽然提供了file server,但是由于其发送完成后不关闭连接,用AB无法测试。

    • Echo web server
      • Cindy 2.4.4 http server
      • Cindy 3.0a4 http server (use SocketSessionAcceptor)
      • Cindy 3.0a4 http server (use ServerSocketChannelSession)
      • MINA 0.9.2 http server
      • QuickServer 1.4.7 echo web server (blocking mode, disable log and admin)
      • QuickServer 1.4.7 echo web server (non-blocking mode, disable log and admin)
    • File web server
      • cindy 3.0a4 http server (use SocketSessionAcceptor)
      • cindy 3.0a4 http server (use ServerSocketChannelSession)
      • tomcat 5.5.15 (disable log)
      • jetty 5.1.10 (disable log)
      • resin 3.0.18 (disable log)
      • apache 2.0.54
      • lighttpd 1.4.11 (disable log)
      • thttpd 2.25b (disable log)

    测试工具:

    Apache Benchmark,2.0.40-dev版本。

    先使用concurrent level 10,测试10w(大文件测试2k个)个请求热身,结果不记入统计。随后分别使用concurrent level 1/10/100/500/1000,测试20w(大文件测试1w个)个请求,对结果进行统计。

    测试结果

    表格中为Requests per second,数值越高越好。

    Echo web server:

    Concurrent  Cindy 2.4.4 Cindy 3.0a4 (acceptor) Cindy 3.0a4 (session) MINA 0.9.2 QS 1.4.7 (blocking) QS 1.4.7(non-blocking)
    1 3376 2760 3062 failed 2299 failed
    10 4882 4581 4561 failed 3244 1890
    100 4998 4903 4494 failed 1425 1890
    500 4891 4528 4847 failed 1338 1353
    1000 4546 4018 4552 failed 1192 1313

     

    echo

     

    File web server (small file 100 bytes):

    Concurrent Cindy 3.0a4 (acceptor) Cindy 3.0a4 (session) Tomcat 5.5.15 Jetty 5.1.10 Resin 3.0.18 Apache 2.0.54 lighttpd 1.4.11 thttpd 2.25b
    1 2444 2641 3294 2308 3809 2982 4540 3145
    10 3812 3823 4275 3177 5231 3563 6044 4156
    100 4193 3673 4578 3221 4257 3579 5849 4215
    500 3759 3737 3896 3145 4309 3385 4888 4104
    1000 3776 3773 3858 2987 4040 2685 4041 3986

     

    small

     

    File web server (small file 100 bytes, keep alive):

    Concurrent Cindy 3.0a4 (acceptor) Cindy 3.0a4 (session) Tomcat 5.5.15 Jetty 5.1.10 Resin 3.0.18 Apache 2.0.54 lighttpd 1.4.11 thttpd 2.25b
    1 5119 timeout 7023 4980 3720 5771 8824  -
    10 5275 timeout 6878 5289 3753 6602 13441  -
    100 6322 timeout 9609 3947 4041 6391 12951  -
    500 5237 timeout 5952 5234 4331 6292 12495  -
    1000 5612 timeout 5478 4318 4046 3367 11696  -

     

     

    <Psmallk

     

    File web server (large file 947111 bytes):

    Concurrent Cindy 3.0a4 (acceptor) Cindy 3.0a4 (session) Tomcat 5.5.15 Jetty 5.1.10 Resin 3.0.18 Apache 2.0.54 lighttpd 1.4.11 thttpd 2.25b
    1 299 280 163 128 251 385 280 340
    10 257 255 203 (62 failed) 213 253 366 318 365
    100 237 240 190 (9991 failed) 189 226 339 296 288
    500 218 214 183 (46 failed) 186 (1 failed) 211 305 281 286
    1000 196 182 167 (23 failed) 182 (43 failed) 177 258 238 240

     

    large

     

    总结

    先解释一下为什么Cindy 3.0a4的测试会有两种:一种是基于SocketSessionAcceptor的,一种是基于ServerSocketChannelSession的。在开发机器上测试时,这两种类型的测试结果有较大的差距,基于Acceptor的测试实际上同时有两个线程在运行,一个线程不停的accept,然后另一个核心线程做相应的处理;而基于Session的测试实际上只有一个线程在运行,就是核心线程既负责accept,也负责处理。在赛扬2.4的机器上,第一种的测试成绩大约为650#/sec,而第二种的测试成绩大约能到900#/sec。鉴于这两者在开发机器上的性能差距,所以在服务器上也做了这两种测试。

    另外,由于这种测试绝大部分时间都是消耗在网络I/O上面,业务逻辑的处理用时非常少,所以只有在使用DirectDisptcher时才拥有最好的响应时间,多开线程会使响应时间大幅下降。也正是这个原因,前两种类型在开发机上的测试结果有如此大的差距。不过由于这次测试机器的CPU是XEON,双核,所以这两种类型的测试结果差距不明显。

     

    在Echo web server的测试中,MINA 0.9.2在完成一个请求后就不再相应后续的请求了,可能是代码中存在的一些Bug所导致,所以这次测试对MINA来说其实稍微有些不公平,我会等它在后续的版本修正了这个问题后再进行一次测试;本来在开发机上QuickServer的测试结果和Cindy的测试结果非常接近,在blocking模式下,性能甚至稍微超出Cindy 3.0a4,没想到放到服务器上测试时会有这么大的差距;Cindy 2.4.4依旧傻快傻快,呵呵,不过它的模型没有Cindy 3.0这么容易扩充,而且Cindy 3.0在这种场景下和它的差距也是非常小的,所以还是推荐尽早升级到Cindy 3.0。

    基于小文件的测试,Resin和Tomat遥遥领先,阻塞I/O+线程池的效率充分发挥了出来。并且这种压力测试速度又快,一个连接非常短的时间就能被处理完,正好是阻塞I/O+线程池的优势所在。在concurrent较小的情况下,这种优势更加明显,在concurrent逐渐增大后,阻塞I/O和非阻塞I/O处理效率上就越来越接近了。在这个测试中Jetty的结果比较令人失望。

    Keep alive的连接测试中,基于Session的测试耗时太长被中途放弃,怀疑是由于单线程运行+Keep alive造成了始终在处理第一个连接所造成,这个还有待证实;Tomcat的表现更是突出,而在这个场景中本来应该是非阻塞I/O的强项所在。不过最大的concurrent也只到1000,在真实的环境中,如果concurrent持续增大,cindy的优势应该会更加突出才对。

    大文件的测试类似于Keep alive的测试,这种场景应该也是非阻塞I/O的优势场景。在该场景中,cindy总算体现了非阻塞I/O的优势,而tomcat的表现就有点失望了,失败数也太多了一点……我研究过Tomcat的源代码,没采用JNI的话它是基于阻塞I/O的实现,只能通过SO_TIMEOUt来中断对数据的读取和写入,所以它在运行时会通过计算服务器的负载来设置相应的SO_TIMEOUT,负载小于33%情况下采用默认的SO_TIMEOUT,33%到66%之间SO_TIMEOUT减半,66%到90%之间,SO_TIMEOUT变为默认的1/3,超过90%则急速降为默认的5%。我怀疑在大文件的测试当中,由于服务器线程负载太大,所以导致超时时间变为默认的5%,从而造成了这么多的失败数。

     

    总体来讲,在基于NIO的异步I/O框架中,我认为Cindy的效率应该算是很不错的了;阻塞I/O+线程池的做法在并发量少、连接时间短的应用中比NIO有着更高的效率,但是随着并发量的上升,两者的差距会逐渐缩小(再上升会不会反超?未经证实……)。但Java的网络层效率还是和C/C++的实现还是有着明显差距,高性能网络应用的首选还是C/C++。

    Cindy 3.0a4 released

    修改记录:
    • SSLFilter可以被用于任意基于流的Session
    • 支持分发线程池(-Dnet.sf.cindy.dispatcher.concurrent)
    • 添加了DirectDispatcher,在当前线程中分发,但是不支持在分发线程中进行阻塞操作(比如Future.complete)
    • 修正DefaultDispatcher中分发顺序可能与事件产生顺序不一致的Bug
    • 修正DefaultDispatcher中流量控制的Bug
    • 在Buffer接口中加入dump方法,方便调试时打印
    • 在Future接口中加入getSession方法
    • 增加了ServerSocketChannelSessionHandler接口
    • 将ExecutorFilter重命名为DispatcherFilter
    • 将HSHAReactor重命名为DefaultReactor(因为去掉了对Executor的支持后,和HSHA模式不匹配了)
    • 将CindyConstants重命名为Configuration,支持从配置文件中读取相应属性(-Dnet.sf.cindy.config=config file,默认为cindy.properties)
    • 将net.sf.cindy.message包拆分为net.sf.cindy.encoder和net.sf.cindy.decoder
    • 更新了HTTP Server示例,扩展成一个相对完成的Http Server
    • 一些小的改进和bug修正

    等待一段时间后准备进入beta阶段。

     

    Update: 发布后才发现http server的ant脚本有些问题。如果各位要测试http server,要么把ant脚本修改成fork一个新的java进程,要么命令行指定ant -Dparam=-acceptor http-server来运行。

    非常抱歉!

    3/7/2006

    又发现NIO的一个小Bug

    按照NIO的规范来说,对于非阻塞Channel,如果当前write方法无法发送任何字节,则应该返回0,而不是抛出异常。

    以前我就发现了它的一个Bug:在Java 1.4的虚拟机上,SocketChannel的write(ByteBuffer[])的JNI操作实现中没有检测某个返回值,抛出了java.io.IOException: A non-blocking socket operation could not be completed immediately异常。而这种情况下本应该返回0的。好在这个Bug不影响大局,一般只要绕过对该方法的调用即可,况且这个Bug已经在Java 5.0中得到了解决。

    不过今天又发现了它的一个Bug:SocketChannel的write(ByteBuffer)的JNI操作,在Window的实现上没有检测WSAENOBUFS的返回值,抛出了java.io.IOException: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full异常。而这种情况下也本应该返回0的。

    应该很少会有应用会触发这个异常,我之所以发现了这个异常也是运气:) 在Cindy简单的HttpServer示例中,图个方便简单的使用了内存映射文件,而且不管文件大小都是全部映射(我是示例Cindy的用法,千万不要在生产系统中这么做)。晚上测试一个200多M文件的下载,我的内存肯定是足够的,所以内存映射没有出现任何问题,但是一次性通过write方法发送200多M的Direct ByteBuffer,呵呵,这个现象就能重现出来了:)

     

    实际应用中应该不会有人象我这么玩的,呵呵,所以这个Bug影响倒也不大。对于应用来说,一方面是不要一次性发送这么大的数据,另一方面是增大Socket的发送缓冲区大小。对于Cindy而言,要对应用屏蔽这个Bug,即使应用发送这么大的数据也不要触发这个异常,倒是有一个简单的方法:把传递给write方法的ByteBuffer控制在一定的字节数内。

    即使应用要Cindy发送一个200M的ByteBuffer,也可以控制成每次调用write方法时,只传给它一个1M大的ByteBuffer,分200次发送即可。这样就可以简单的对应用屏蔽这个Bug了。

    3/6/2006

    Cindy 3.0a4 configuration

     Global configuration

    • net.sf.cindy.config (default: config.properties)
          Cindy config file
    • net.sf.cindy.enableJmx (default: false)
          Enable jmx support
    • net.sf.cindy.disableInnerException (default: false) 
          Disable inner exception dispatch

    Buffer configuration

    • net.sf.cindy.bufferCache (default: net.sf.cindy.buffer.DefaultBufferCache)
          BufferCache class name
    • net.sf.cindy.useDirectBuffer (default: false)
          Use direct buffer instead of heap buffer
    • net.sf.cindy.useLinkedBuffer (default: false)
          Use linked buffer instead of memory copy   

    Dispatcher configuration

    • net.sf.cindy.dispatcher (default: net.sf.cindy.session.dispatcher.DefaultDispatcher)
          Dispatcher class name
    • net.sf.cindy.dispatcher.keepAliveTime (default: 3000)
          Dispatcher keep alive time
    • net.sf.cindy.dispatcher.capacity (default: 1000)
          Dispatcher capacity (flow control)

    Session configuration

    • net.sf.cindy.session.timeout (default: 0)
          Session timeout (like SO_TIMEOUT)
    • net.sf.cindy.session.recvBufferSize (default: -1)
          SO_RCVBUF
    • net.sf.cindy.session.sendBufferSize (default: -1)
          SO_SNDBUF
    • net.sf.cindy.session.reuseAddress (default: false)
          SO_REUSEADDR
    • net.sf.cindy.session.tcpNoDelay (default: true)
          TCP_NODELAY
    • net.sf.cindy.session.soLinger (default: -1)
          SO_LINGER
    • net.sf.cindy.session.readPacketSize (default: 8192)
          Session read packet size
    • net.sf.cindy.session.nio.reactor (default: net.sf.cindy.session.nio.reactor.DefaultReactor)
          NIO Reactor class name

    Acceptor configuration

    • net.sf.cindy.acceptor.backlog (default: 100)
          Acceptor backlog
    • net.sf.cindy.acceptor.reuseAddress (default: false)
          SO_REUSEADDR

    For example, to enable jmx, use direct buffer, set dispatcher keep alive time to 10s and enable acceptor reuse address, you can specify configuration in system environment :

    -Dnet.sf.cindy.enableJmx=true
    -Dnet.sf.cindy.useDirectBuffer=true
    -Dnet.sf.cindy.dispatcher.keepAliveTime=10000
    -Dnet.sf.cindy.acceptor.reuseAddress=true

    or you can put cindy.properties on your classpath:

    enableJmx=true
    useDirectBuffer=true
    dispatcher.keepAliveTime=10000
    acceptor.reuseAddress=true

     

    3/2/2006

    Cindy 3.0a3 user guide released

     
    User Guide中的示例都是非常简单的示例,起个入门的作用。有了这个引子,再看看cindy源代码example目录下的示例,应该很容易就能上手。
     
    希望各位多提意见,这样才好根据你们的意见来做改进:)
     
    a4的主要目标是提高Cindy的易用性,比如通过运行期参数配置的Dispatcher线程池大小等等。如无意外的话应该两个礼拜后会release出来。
     
    BTW:我这边好像无法访问sourceforge各个项目的主页,即xxx.sourceforge.net。难道又被封了?
     
    Update: 晚上将原示例中简单的Echo Http Server扩充成为一个相对完整的基于文件的Http Server,然后又测试了一下效率,效率上居然也没有太多的降低。原来Requests Per Second差不多是4200#/sec,现在Requests Per Second差不多是3600#/sec。看来等a4版发布后,可以做一个比较完整的评测了,不仅可以和MINA等比较,还可以和现有的一些HttpServer进行比较(当然,它们的实现要完整的多,对它们本身的效率会有些影响,这点要考虑进去)
    2/27/2006

    Cindy 3.0a3 released

    下载地址: http://sourceforge.net/projects/cindy

    修改记录:

    • 添加了SSLFilter,不过仅能运行在Java 5.0以及以后的版本上
    • 添加了net.sf.cindy.session.dispatcher包,应用可以加入自定义的Dispatcher实现(通过-Dnet.sf.cindy.dispatcher=Dispatcher实现类 来指定)
    • 按照相反的顺序分发xxxSend/xxxSent事件,即排在前面的Filter后处理该事件
    • 在Buffer接口中添加了release/isReleased/isPermanent/setPermanent方法
    • 移除了BufferFactory的release方法
    • 在BufferFactory类中加入了wrap(Buffer[])方法
    • 在BufferBuilder类中加入了release方法
    • 移除了SessionHandler接口中的packetReceived/packetSent事件
    • 在SerialEncoder类中,用writeUnshared方法代替write方法
    • 在SerialDecoder类中,检测流的头部,是否为序列化流
    • 提高了LinkedBuffer效率(仍处于试验性质)
    • 提高了DefaultBufferCache效率
    • 在SocketSessionAcceptor接口中添加了backlog/reuseAddress/receiveBufferSize属性
    • 一些小的改进和Bug修正

    最大的改进就是加入了SSLFilter以及反向分发xxxSend/Sent事件。

    SocketSessionAcceptor中所增加的属性也是在实现网络服务器时经常要用到的,至于SocketSession、DatagramSession中经常要设置的属性由于数量太多,还是通过暴露出来的Socket和DatagramSocket来设置比较方便一点。

     

    SSLFilter还有一个小缺陷,由于Session没有提供beforeClose的一个事件出来,因此在Session关闭前不能发送SSL的close message。这样服务器端可能会抛出一个异常:

    javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack?

    这种实现不是太完美,不过完美的实现只能通过继承已有的SocketSession实现来解决,在close前发送close message,但这就缺乏扩展性。目前权衡下来暂时采用Filter的设计,如果应用真的在意这个close message,那么可以通过组合SSLFilter和SocketSession实现来加以解决。

    2/17/2006

    SessionFilter顺序

    在实现Cindy 3.0a3 SSLFilter的过程中,发现一个有意思的问题:不同类型的SessionFilter对于过滤事件的顺序有着不同的要求。
     
    假设有这么一个场景,通过SSL连接接收和发送数据,并统计所有的网络流量。
     
    对于接收而言,SessionFilter的顺序应该是:
    Received Packet --> StatisticFilter  --> SSLFilter --> Application Packet
    这样统计到的是真实的接收流量,应用拿到的也是解码后的数据。
     
    对于发送而言,SessionFilter的顺序则应该是:
    Application Packet --> SSLFilter --> StatisticFilter --> Send Packet
    可以看到,对于发送和接收而言,这两个Filter的顺序是相反的。如果这个场景再复杂一些,数据发送前需要先进行压缩,接收到数据后再解压缩,那么从发送到接收的整个流程是(红色是发送,蓝色是接收):
    Application --> ZipFilter --> SSLFilter --> StatisticFilter --> Network --> StatisticFilter --> SSLFilter --> ZipFilter --> Application
    可以看到在这些场景中,Filter在处理发送和接收事件上顺序是相反的。而在以前Cindy的设计里面,对于所有事件的分发都是按照相同的顺序,这可能造成困惑。
     
    目前我的想法是,针对应用层的Filter,将所有的send/sent事件分发按照Filter添加的顺序反向分发,其他的事件则按照添加顺序分发。但这种做法缺乏灵活性,因为这暗示了所有Filter需要的就是这种顺序(虽然目前除了系统级的Filter外,我还没想到什么样的Filter处理发送和接收事件的顺序需要保持一致)。另一种做法可以把所有事件都拆分开,指定每个事件的处理顺序,但这种做法言应该会过于繁琐。
     
    目前Cindy内系统级Filter对发送和接收事件分发的顺序需要保持一致。如果在上面这个流程图中加入系统级Filter,那么整个顺序是这样的(红色代表系统级Filter)
    发送:Application --> ExecutorFilter --> ZipFilter --> SSLFilter --> StatisticFilter --> PacketDecoderFilter --> SessionHandlerFilter
     
    接收:Network --> ExecutorFilter --> StatisticFilter --> SSLFilter --> ZipFilter --> PacketDecoderFilter --> SessionHandlerFilter
    虽然这种全部反向分发接收事件的做法还有待商榷,但可以肯定的是,该做法比原来所有顺序一致的做法要好,因为反向分发至少覆盖了大部分的应用类型。
     
    如果你来设计,你会怎么做呢?
    2/13/2006

    SessionDispatcher

    Jerry来信问了一些有关SessionDispatcher的问题,在征得其同意后,将来信和回信贴在这里,希望能给大家一些帮助。
     
    来信
     
    你好,看了你的cindy觉得很有意思,看到他一直在进步,也很让人高兴。
    看了新出的cindy3.0a2里面的代码,和3.0a1做了一下比较,发现了一些不同,所以想请教一下你的想法
    你的SessionDispatcher里面启动的是只有一个线程的线程池,很想了解这里能把线程池中的线程数目调大吗?
    从我的理解应该是可以的,这样就形成了处理NIO常用的1-N的线程模型,不知道理解是否有误?
    在Cindy3.0a1的HSHAReactor的start方法里面有如下的代码
    private synchronized void start() {
            if (selectThread != null)
                return;
            int minPool = CindyConstants.getMinThreadPoolSize();
            int maxPool = CindyConstants.getMaxThreadPoolSize();
            // keep event dispatched in order
            if (minPool == 0 && maxPool == 0)
                dispatcher = new DirectExecutor();
            else
             //Start a seperate dispatcher thread, maybe this will become a
    thread pool.
                dispatcher = Executors.newSingleThreadExecutor(threadFactory);
            lastSelectTime = new ElapsedTime();
    这个dispatcher处理的都是selector相关的东西。但是在Cindy3.0a2里面这部分代码去掉了,是发现了什么线程问题吗?
    3.0a1和a2的start方法上都有个synchronized关键字,里面启动了一个线程。
    这样的话,在这个内部线程死掉之前,这个方法是不能被别的线程访问的,对吗?觉得这样的处理方法很tricky,呵呵。
    请不吝赐教,谢谢!:)
    回信
     
    Jerry,
     很高兴你对该项目抱有浓厚的兴趣,这里就你提出的问题做一些交流。

    1.SessionDispatcher线程数的调节

    SessionDispatcher的存在的目的并不是为了效率以及可扩展性,它存在的目的仅仅是为了保证结果的正确性,所以线程数没有调大的必要。

     
    Cindy中同时存在着同步方法和异步方法。异步的比如Listener、Filter一系列机制都是用于处理异步情况的,在相应的事件完成后再通知应用;同步的比如Future.complete(),会在该任务完成后才从该方法中返回。
     
    这里就牵涉到的问题就是:因为核心是异步的,如果应用在核心线程中调用了同步方法,则该同步方法将永远无法完成。
     
    所以SessionDispatcher的作用就是使得真正的核心线程永远也到达不了应用层,所有应用层的事件都是由SessionDispatcher中的线程来发送。同时,为了保证同步方法不阻塞SessionDispatcher,一旦应用层调用了同步方法,SessionDispatcher就会通过内置的线程池来选择另一个线程发送消息。
     
    举个例子。比如同时开启了10个Session,其中一个Session在sessionStarted事件中进行数据发送,并调用了Future.complete方法。
     session.addSessionHandler(new SessionHandlerAdapter() {
      public void sessionStarted(...) {
       session.send(someObj).complete();
      }
     });
     
    如果是核心线程进行事件的发送。那么调用complete后,这时并没有开始发送,所以线程被阻塞;并且由于核心线程被阻塞,真正的网络操作将永远无法执行,只能被一直阻塞。
     
    如果只是单线程进行事件发送。那么调用complete方法后,事件发送线程被阻塞,但核心线程还是可以完成网络操作,并把操作完成的事件添加到事件发送队列中。但是由于事件发送线程被阻塞,它也无法得到事件完成的消息,它也会一直阻塞在这里。并且,即使有办法恢复事件发送线程,但在它阻塞的过程中,其他9个Session的消息也没有办法发送,这会极大的降低吞吐量。
     
    目前的做法还是单线程分发事件,但一发现应用调用阻塞操作,就通过一个线程池获取另一个线程继续后面的事件分发;等到原线程从阻塞操作中恢复,再把线程放到线程池中。是Lead-Follows模型的应用。

    2.Reactor的start方法上使用synchronized关键字

    在该方法中使用synchronized关键字,并不会使在该方法中开启的线程也必须要拥有该锁。所以内部线程不死掉,该方法也能被调用。
     
    3.Reactor的线程池调节
    a2对这个的改动并不是线程问题,而是实际测试中发现这些代码并没有意义,不管在单CPU还是多CPU的情况下,效率只有降低,而没有提高,所以才去掉了这部分的代码。
     
    至于1-N的线程模型,由于Filter机制的提供,应用可以根据自己的需要做更细的调节。比如通过自己实现的Executor,再加上ExecutorFilter做N个分派线程;或者仅仅对messageReceived事件进行池化分派,等等。这些完全取决于应用自身的需要。但是需要注意的是,一旦通过自己实现的线程池进行分派后,在进行同步方法的调用时就要注意尽量不影响其他异步操作的进行。
    2/10/2006

    Cindy 3.0a2 released

     
    修改记录:
    • 添加了对JMX的支持 (通过运行时指定-Dnet.sf.cindy.useJmx)
    • 添加了流量控制
    • 提高了Buffer效率
    • 提高了PacketDecoderFilter效率
    • 将默认的PacketEncoder由ByteArrayPacketEncoder改为SimplePacketEncoder,PacketDecoder由ByteArrayPacketDecoder改为SimplePacketDecoder
    • Session对外事件中增加了objectSent事件
    • 修正了检测Session超时中的Bug
    • 往一个未连接的Session发送消息会返回一个未成功的Future(以前是抛出一个运行期异常)
    • Cindy所带的示例代码更新,提高了效率
    • 一些小的改进和Bug修正

    稳定性相对a1来说有很大的增强,不客气一点的说,比MINA也稳定的多。添加的第一和第二项功能也很有意思。

    增加了JMX支持,比如如果运行在Java 5.0下,可以在运行时指定-Dcom.sun.management.jmxremote -Dnet.sf.cindy.useJmx,然后通过jconsole观察Cindy的运行状态。

    增加了流量控制,使得内存溢出现象得到了控制。比如以前本机对本机发送文件,写文件操作相对网络接收速度慢很多,导致了接收到的Buffer在内存中越堆越多,造成内存溢出;还有软件调试时,在应用层设断点调程序,后面的网络处理还在继续,也会容易造成内存溢出。加入流量控制后,只要队列满了,后面的网络操作也慢了下来。虽然总体吞吐量是减少了,但是保证了系统的稳定性。

    这些可以调整的参数,都可以参见net.sf.cindy.util.CindyConstants中的声明。

     

    a3版本计划加入对SSL的支持,以及加入更多的JMX管理功能。当然,Bug修正是少不了的了:)

    1/27/2006

    Cindy 3.0 效率测试

    客户端环境:
    • AB 2.0.41-dev

    服务器环境,客户端和服务器在同一台机器上,不带任何参数运行:

    1. MINA 0.90示例所带的HttpServer(修改了MINA 0.90中ByteBuffer.wrap不正确的Bug)
    2. MINA 0.90示例所带的HttpServer修改版(修改了MINA 0.90中ByteBuffer.wrap不正确的Bug;不采用StreamIoHandler,而使用类似Cindy 3.0中HttpServer所采用的解析方式)
    3. Cindy 3.0某开发版(在3.0a1的基础上提高了Buffer效率)示例所带的HttpServer
    4. Cindy 2.4.4,采用类似Cindy 3.0中HttpServer所采用的解析方式

    测试方法:

    结果:

    • MINA 0.9 HttpServer

    Server Software: MINA
    Server Hostname: localhost
    Server Port: 8080
    Document Path: /
    Document Length: 71 bytes
    Concurrency Level: 60
    Time taken for tests: 239.031 seconds
    Complete requests: 50000
    Failed requests: 371
    (Connect: 0, Length: 371, Exceptions: 0)
    Total transferred: 6848802 bytes
    HTML transferred: 3523659 bytes
    Requests per second: 209.18
    Transfer rate: 28.65 kb/s received
    Connnection Times (ms)
      min avg max
    Connect: 0 3 515
    Processing: 78 281 235
    Total: 78 284 750

    • MINA 0.9 HttpServer修改版

    Server Software: MINA
    Server Hostname: localhost
    Server Port: 8080
    Document Path: /
    Document Length: 89 bytes
    Concurrency Level: 60
    Time taken for tests: 140.546 seconds
    Complete requests: 50000
    Failed requests: 0
    Total transferred: 8800000 bytes
    HTML transferred: 4450000 bytes
    Requests per second: 355.76
    Transfer rate: 62.61 kb/s received
    Connnection Times (ms)
      min avg max
    Connect: 0 1 421
    Processing: 31 166 172
    Total: 31 167 593

    • Cindy 3.0开发版HttpServer

    Server Software: Cindy
    Server Hostname: localhost
    Server Port: 8080
    Document Path: /
    Document Length: 89 bytes
    Concurrency Level: 60
    Time taken for tests: 116.390 seconds
    Complete requests: 50000
    Failed requests: 0
    Total transferred: 9050000 bytes
    HTML transferred: 4450000 bytes
    Requests per second: 429.59
    Transfer rate: 77.76 kb/s received
    Connnection Times (ms)
      min avg max
    Connect: 0 0 31
    Processing: 15 138 234
    Total: 15 138 265

    •  Cindy 2.4.4 HttpServer

    Server Software: Cindy
    Server Hostname: localhost
    Server Port: 8080
    Document Path: /
    Document Length: 89 bytes
    Concurrency Level: 60
    Time taken for tests: 91.687 seconds
    Complete requests: 50000
    Failed requests: 0
    Total transferred: 9050000 bytes
    HTML transferred: 4450000 bytes
    Requests per second: 545.33
    Transfer rate: 98.71 kb/s received
    Connnection Times (ms)
      min avg max
    Connect: 0 0 62
    Processing: 15 109 391
    Total: 15 109 453

    参数对比:

      MINA 0.90 HttpServer MINA 0.90 HttpServer 修改版 Cindy 3.0 HttpServer Cindy 2.4.4 HttpServer
    Failed requests 370 0 0 0
    Time taken for tests(s)  239.031 140.546 116.390 91.687
    Requests per second 209.18 355.76 429.59 545.33
    Average  connection times(ms) 284 167 138 109

     

    简单结论:

    该测试仅代表某一特定并发量小且连接时间短的场景。在该场景下,Cindy 3.0的处理效率大概比MINA高出20%左右,而Cindy 2.x的处理效率比3.0要高出27%,这让我有点惊讶。

    我猜测可能在这么简单的场景下,3.0的Buffer Cache完全发挥不了作用,反而2.x中每个连接一个固定的缓存节约了从Buffer Cache中查找和释放的开销。我也测试了Tomcat 5.5的表现,结果和Cindy 2.2.4比较接近。Tomcat可是完整的HTTP实现,不像这些半吊子的HttpServer,而且据我的了解Tomcat 5.5还是采用的是阻塞模型。实际上在并发量不大和长时间连接的情况下,阻塞模型比非阻塞模型效率更好,所以不要简单的认为采用了nio效率就一定比io要强:)

    这个结果也表明,Cindy 3.0目前的版本还有非常大的优化空间:)

    该测试也说明了要慎用MINA的StreamIoHandler,该Handler把异步处理完全变成了同步处理,并且是用非阻塞来模拟阻塞,效率应该比不上传统的Java I/O模型。

     

    Update: 以上的结果在一台赛扬2.4的机器上测试得到,服务器和客户端都在同一台机器上,只有相对参考价值。在服务器和客户端均为XEON 3G的情况下,测试结果约为4000 Requests per second。

    1/25/2006

    cindy 3.0a1上传到sourceforge

    上传是比较困难,不过sourceforge的下载没有被封掉,所以如果要从sourceforge上下东西,直接从这个网址进去吧,也不用代理的。
     
    Cindy 3.0a1下载: 源代码  二进制文件
    1/24/2006

    Cindy 3.0a1 released

    由于GFW的成功封锁,没法上传到sourceforge,。随便找了个地方,不过只能传图片和文本,没办法,base64 encode了一下,下载回来base64 decode成zip文件即可。
     
    简单示例:
    new sun.misc.BASE64Decoder().decodeBuffer(new FileInputStream(
          "cindy_3.0a1_bin.htm"), new FileOutputStream("cindy-3.0a1-bin.zip")); 
    下载:
    更新:
    • 由于重新设计了Buffer体系,所以整个核心有所变化,由原来的以Message为核心改为了以Packet(等于Buffer + SocketAddress)为核心,粒度更细
    • 将原有通过Listener进行分发改为Filter进行分发
    • aio4j性能并没有宣称的那么好,而且项目一直没更新,所以暂停加入
    • Message接口由PacketDecoder和PacketEncoder所替代
     
    Alpha版本,肯定还有不少Bug和可以优化的地方,希望各位多加反馈:)
    12/24/2005

    Cindy 3.0中的Buffer设计

    俗话说兵马未动,粮草先行,Cindy 3.0版本连影子还没见着,Blog倒是放出了好几篇……
     
    为了以示区分,nio的ByteBuffer我简称为NB,cindy的Buffer我简称为CB。
     
    现在对NB的理解比上次写Redesign Buffer这篇Blog时要更深入一些,所以在CB的设计上观念有了一些变化:
    • 类似ByteArrayOutputStream/ByteArrayInputStream,容量不是固定的,可以自动扩展

    如果CB容量是不固定的,则在自动扩展时需要进行内存拷贝,并且在实现中判断是否需要进行扩展会有许多额外的判断,还无法精确匹配应用的需求,降低了效率;但是对于某些特定的应用,该功能确实能带来一些便利。

    所以在目前的设计中,CB的自动扩展并不是CB体系的一个基本功能,仅仅为一个特定实现。除了该实现外,其余的CB都不是自动扩展的,还是固定的capacity。

    • 在早期的Cindy 3.0实现中,曾提供了一种WrappedBuffer的CB实现,可以把NB数组包装成一个单一的CB,现在该实现被取消

    在我前面一篇Blog中可以看出,大部分应用在如果没有自己实现NB pool,正常情况下应该实例化成Non-direct NB,可是Non-direct NB聚集读写要比非聚集读写效率低。所以WrappedBuffer看上去节约了内存拷贝时间,可实际在写入和读取的时候要实例化不缓存的临时Direct NB,反而降低了速度。

    • 加入一种LinkedBuffer的CB实现,和WrappedBuffer不同的是,LinkedBuffer链接的是CB,而WrappedBuffer链接的是NB

    这种特殊类型的Buffer其实并不是为应用准备的,而是为Cindy中的Session MessageRecognizer准备的。

    在Cindy 2.x中,读数据的流程是(2.x还没有CB的设计):

    网络 --> Session内部缓存的NB --> MessageRecongizer读出数据识别成Message --> 应用

    可以看到,对于应用直接从网络中读取数据,中间多了一层内存拷贝(先拷贝到Session内部缓存中,再到应用)。但是通过这一间接层,把所有的网络情况处理和异常处理都交给框架来处理了,减少了应用的负担,所以多出来的这一层的好处大于坏处。

    在Cindy 3.x中,由于Filter机制,读数据的流程是:

    网络 --> 读出的CB块 --> 一系列的Filter转换后的CB块 --> Session内部缓存的CB,把读出来的CB块重组 --> MessageRecongizer读出数据识别Message --> 应用

    可以看到,相对Cindy 2.x来说,引入了许多间接层。那么效率能否不降低呢?

    首先在默认情况下,内置的Filter不会转换读出的CB块(除非你自己加入特定的Filter,如SSLFilter,把收到的数据包解密给普通数据包),所以在Filter转换后的CB块和原来的CB块是同一个,没有额外的开销。

    Session内部的CB将收到的一系列CB块重组(比如TCP连接,第一次收到了abc,MessageRecognizer没有识别出来,第二次收到了def,这次收到的def应该加在abc后面,即abcdef,再让MessageRecongizer识别),如果还是采用原始的设计,在这个地方就多了一次内存拷贝,而且如果缓存容量不够的话,还得重新生成一个容量足够的CB,再把收到的一系列CB放进去,效率肯定会有所降低。这个时候就是LinkedBuffer发挥威力的地方。

    LinkedBuffer把收到的CB包连接起来,对外看上去好像只有一个CB,不需要进行内存拷贝,也不会有缓存容量不够的情况出现(因为仅仅是把若干个CB链接起来)。MessageRecongizer直接从LinkedBuffer中读入数据,等到某CB块上的数据被Message读入完毕后,再把该CB块从链上去掉即可。

    所以即使中间多了两层,但是效率仍然不会降低。更何况中间多加的两层具有非常好的可扩展性,极端情况下,你甚至可以在Filter这一层就把接收到的CB直接拿出来使用,而不传给后面的Filter,这样连Cindy 2.x中那一次内存拷贝都省了:)

     

    在最后说一个不是太好的消息。原计划12月底发布3.0 a1版,现在看来是来不及了,甚至连Reactor模型我都还没由HSHA改成LF……况且月底圣诞、元旦两个比较大的节日,还是要多去享受享受生活的……

    在此向各位关注Cindy的朋友们致歉,这个版本应该会在1月中旬出来的,到时还请多多提一些建设性意见:)