女儿的出生中断了一段时间的开发。现在就高性能网络处理方式的问题做个总结

所谓高性能,我们这里指在大量并发连接时还有相当高的请求处理速度。提高单连接的请求处理速度这里不讨论。我们这里采用 p=处理速度/连接数 作为衡量性能的指标。

java中最传统的socket处理方法是同步socket,每个连接建立一个线程阻塞等待数据的到达,这种方式很适合于低连接数的应用,但是在大量连接时会由于频繁的线程间切换导致性能大幅降低。这种方式下只能优化线程创建时间,也就是使用线程池。

jdk1.4中引入了nio处理网络连接,实际上,sun对于nio的实现也仅仅是使用了select(见jdk源代码)。socket函数中的select实现了一个基于事件的解决方案,可以只用一个线程去等待多个socket事件的发生,在一定程度上解决了多线程切换带来的性能降低。它的缺点是一次只能处理一个事件,而且其监视的句柄也有限制。实际使用中它对性能的提升也非常有限。(winsock和bsd的select区别这里也暂不讨论)

纯java中能够使用的方法基本上只有上述两种,所以如果选择使用纯java,是很难应付高性能的应用的。所幸我们还有JNI.....

超越了java,我们不得不谈到本地平台的区别上来.....

windows平台下比较先进的模型是完成端口+重叠IO,完成端口可以看作增强版的select,它可以支持更大量的句柄,并且可以一次返回多个事件。重叠IO是另一个强大的工具,它允许用户异步的进行通讯操作,而且还能允许用户自己管理传输缓冲。使用这二者的组合,完全可以把所有端口的所有传输数据分成一个个等长的小块,让cpu进行基于定长数据块的处理。看起来像什么?是不是更像流水线?这种方式可以处理大量的连接而不会使性能降低很多。

linux平台下使用的是epoll,功能很像完成端口,也支持一次返回多个事件。只不过没有重叠io的支持,不知道这种模型赶不赶得上windows下的方案?

(补充一点:jdk1.5 update9 之后,linux下的nio已经支持了epoll)

无论是完成端口还是epoll,我们的程序中都不再需要大量的线程了,但是仅仅使用一个线程完成所有请求的处理这种方式也是不可取的,因为很多情况下,系统都可能会对某些资源进行等待,这样就会降低综合的连接处理速度。比较好的方法是按需使用线程,在不大可能出现等待的逻辑中使用单独线程完成,在可能出现等待的逻辑中建立少量线程用多条流水线的处理方式去完成任务。

使用完成端口或者epoll的解决方案,java在大连接处理上的能力将会大大提高,只不过这时候java就不会再成为项目中的主导,看起来会更像一个嵌入式的解释器...
评论
homk 2008-05-11
xgyxgy 写道
timerri 写道
xgyxgy 写道
timerri 写道
一般对java底层不了解的人都会反对jni,不过大可不必敌视它。因为java中对于底层的操作全部是通过jni实现的,包括那些耳熟能详的ServerSocket,FileInputStream等等...

对于超大访问量的系统,用纯java来做应该是糟糕的设计...除非硬件成本能大大低于软件成本.

J2EE的优势在于快速开发,而不在于性能优化。


超大访问量的系统不能用纯JAVA来做这种观点是非常错误的。怎样才算超大访问量?千万级别的算不算?我手头上就有N个N千万级别的系统,有些还是单机上千万级别的,纯JAVA打造。事实上,JDK1.4和JDK5.0与及现在的JDK6.0,性能上的提升是非常惊人的。现在在server端,没什么是纯JAVA做不了的。不止千万,亿级别的系统,同样可以用JAVA来做,而且性能不会比C/C++的要低。


对于超大访问量的系统,用纯java来做应该是糟糕的设计...除非硬件成本能大大低于软件成本.

请仔细看我这句话,我并没有说大型系统不能用java实现。
只不过,在同样的硬件配置下,使用纯java开发的系统将比经过c或c++性能低。要弥补这样的差距,就只能提高硬件配置达到。而在超大型系统中,提高硬件配置的代价是惊人的,一般情况下远远大于通过软件优化的方式的成本。所以,局限于纯java来解决是十分不明智的。

不要盲目相信java的性能,jdk中的实现大部分只是最通用的,而不是最优化的。某些时候,我们必须用自己的本地代码去替换掉jdk中的本地代码的实现以取得更优化的性能。


我明白你的意思,不过我还是觉得你的看法是错误的。对于超大访问量的系统,用纯JAVA来做,硬件成本会比用C/C++做的要高很多?这个是大错特错的!对于一个设计和实现得较好,超大访问量的系统,其最后的瓶颈,会在你的I/O层面,而不会在你的逻辑层。诚然,如果单纯密集型计算,单耗CPU的那种,JAVA会比C/C++的性能会低10% --20%,但是你有几个节点是密集型计算的?在当今的互联网和企业应用,密集型计算的节点,占不到你整个系统的5%。此时,再怎样讨论提升这个10%,已经毫无意义了。对于网络应用来看,单机的情况下,用纯JAVA来做,如果精益求精,其整体性能和C/C++实现的是不相上下的。大型系统最重要是看怎样负载均衡,怎样解决I/O,怎样做cluster。而JAVA在实现这些方面来说,有其天生的优势。在我所看过的团队,有很多花了几个月时间才用C/C++完成一个网络应用(当然都是非常高负载的),但还是较容易coreDump,维护方面较麻烦。而我们的团队可以花不到一个月的时间就完成这个,而且非常稳定,基本上没有出现问题。这两个实现的单机负载能力也不相上下,纯JAVA实现的并不差。我不认为两个团队的水平有很大差别,只是我认为用纯JAVA,会占了很多优势,在开发效率和稳定性上都不错。

windows下的应用我没有经验,也没有发言权了。以上所说的全是在*NIX的。


JDK1.6的性能已经提升了很多。最近在做一个高性能高并发的项目。Java的性能并不比c/c++差到哪里去。就目前的情况用于java做网络通信主要性能瓶颈在IO部分。在windows下性能比不上nix,在windows上并没有实现windows ICOP而linux是epoll。期待nio.2,jdk7中实现aio
julyboxer 2008-01-15
timerri 写道
julyboxer 写道
timerri 写道
一般对java底层不了解的人都会反对jni,不过大可不必敌视它。因为java中对于底层的操作全部是通过jni实现的,包括那些耳熟能详的ServerSocket,FileInputStream等等...

对于超大访问量的系统,用纯java来做应该是糟糕的设计...除非硬件成本能大大低于软件成本.

J2EE的优势在于快速开发,而不在于性能优化。


不赞成J2EE的优势是在于快速开发,想要追求速度的人肯定不会选用J2EE的。。J2EE是在于稳定。


J2EE实际上只是接口,它的稳定还是需要依赖于具体实现。各个厂商的J2EE实现我绝对不敢完全等同看待。
J2EE最大的优势就是统一了一些常用功能需求的接口,他更像一个工业标准而不是一个特定工具。


呵呵。。请不要跑题。。。 SSH或者JSH 这些能够说明快速开发吗? 如果想真正的快速开发 那用.net吧。。不用管它什么MVC 采用什么领域模型。。。一切都是浮云。。我要的只是快速开发。。
PS:三页必跑题。。如果三页没有跑题。。必定两页内跑题。。哈哈。。
skydream 2008-01-14
现在的jvm的gc算法已经有非常大的改进了,如果写程序时多采用小类加小函数,让类短暂使用后就直接失效,那么这些类的gc根本不需要full gc的。

写对gc友好的程序,似乎是个挺大的课题。

好像江南白衣的blog里面,我看到过一个类似的话题。可以去找找,写的就是如果针对目前新的gc机制来做到对gc友好。
pufan 2008-01-14
看这幅图,多cpu闲置只存在full gc的初始标记阶段(标记根对象),资源浪费应该是比较少的。

不过sun的话不能信,要用java做网游(MMORPG)的还真得仔细测试测试了,呵呵。
pufan 2008-01-14
Arbow 写道
pufan 写道
倒是多核后gc的代价,每个cpu不时都停那么两下资源严重浪费。


使用 -XX:+UseParNewGC 这种并行GC实现,能否有效减少多核GC中的暂停对资源的浪费呢?


http://www.javaeye.com/topic/91905

-XX:+UseParallelGC 在full gc时仍为单线程
-XX:+UseConcMarkSweepGC不错,full gc并发收集。
coolnight 2008-01-12
gc时每个cpu都停下? 或许以前是这样,但是现在,未必了....
gc的算法不断的在进步的说, gc是否影响性能,还需测试才能确认


pufan 写道
timerri 写道


jdk1.4中引入了nio处理网络连接,实际上,sun对于nio的实现也仅仅是使用了select(见jdk源代码)。socket函数中的select实现了一个基于事件的解决方案,可以只用一个线程去等待多个socket事件的发生,在一定程度上解决了多线程切换带来的性能降低。它的缺点是一次只能处理一个事件,而且其监视的句柄也有限制。实际使用中它对性能的提升也非常有限。(winsock和bsd的select区别这里也暂不讨论)



Selector.selectedKeys()返回的是事件集合,怎么会“只能处理一个事件”。

“实际使用中它对性能的提升也非常有限”这句话更不敢苟同了,java BIO与NIO的性能对比测试多了去了,结果是在处理长连接型应用中,NIO性能提升巨大!

java与c(++)的性能差距不在IO,封装了操作系统底层实现的java不会慢到哪里去,倒是多核后gc的代价,每个cpu不时都停那么两下资源严重浪费。
Arbow 2008-01-11
pufan 写道
倒是多核后gc的代价,每个cpu不时都停那么两下资源严重浪费。


使用 -XX:+UseParNewGC 这种并行GC实现,能否有效减少多核GC中的暂停对资源的浪费呢?
pufan 2008-01-11
timerri 写道


jdk1.4中引入了nio处理网络连接,实际上,sun对于nio的实现也仅仅是使用了select(见jdk源代码)。socket函数中的select实现了一个基于事件的解决方案,可以只用一个线程去等待多个socket事件的发生,在一定程度上解决了多线程切换带来的性能降低。它的缺点是一次只能处理一个事件,而且其监视的句柄也有限制。实际使用中它对性能的提升也非常有限。(winsock和bsd的select区别这里也暂不讨论)



Selector.selectedKeys()返回的是事件集合,怎么会“只能处理一个事件”。

“实际使用中它对性能的提升也非常有限”这句话更不敢苟同了,java BIO与NIO的性能对比测试多了去了,结果是在处理长连接型应用中,NIO性能提升巨大!

java与c(++)的性能差距不在IO,封装了操作系统底层实现的java不会慢到哪里去,倒是多核后gc的代价,每个cpu不时都停那么两下资源严重浪费。
coolnight 2008-01-10
本人就是用nio来写网游类的应用的, 不过不是多人在线rpg而是休闲类的。

jdk6中nio已经是epoll了, 性能非常好!
前面有人说的哦, 瓶颈在io, 确实如此。 我们的一个数据转发服务器, cpu不到10%就可以把网络带宽用光,
还是单cpu的情况。

jdk7里面,可能会对windows下面的完成端口进行支持。

所以我看到lz的这篇总结,觉得很惊讶。 在底层原理差不多的情况下, C++应该比java要快一点, 但是
条件是C++能写的和java的水平一样高。

其实也未必真要榨干每一滴性能, 够用就够了。 一台机器的性能,就算你把它榨干,也是永远不够的。
肯定要做集群、负载均衡、业务分隔之类, 这个时候你就发现用java的好处了。 而且榨干一台机器也是
不现实的,通常情况下,如果系统已经被充分利用,cpu 使用到一定比例以上, 那就是要考虑扩容的问题了,
稳定性问题也会随之而来。 java 程序比 c++ 程序更容易开发些,开发起来也更快,这很重要。

jni? 我觉得自己去封装, 应该是不会比sun封装来的更好更完善。
codeall 2008-01-10
mina/grizzly 怎么样?
yyjn12 2008-01-02
timerri 写道

J2EE的优势在于快速开发,而不在于性能优化。




这个观点很新颖嘛.呵呵.
有创意哟.
难道是说做企业应用,java比.net开发快,性能不如.net,
做网站 ,java比php开发快,性能不如php?
ken1984 2008-01-01
楼主对IOCP与EPOLL的理解有错误,不应该拿SELECT与它们相比较,根本就不是同个档次的。我一直在期待JAVA真正能实现EPOLL的那一天。这样我们网游服务器就可以尝试转到JAVA上了。还有JDK 6中为什么不是真的ET?有没应用JAVA的EPOLL的例子?
coolmenu 2007-12-30
facebook等大网站用php,只是做前端页面,中间件都是自己用c++开发的,facebook不是发布了一个thrift嘛?和 ICE有点像
zhugf000 2007-12-30
赞同楼主的意见。
目前JDK 6中已经有了部分epoll支持,但是不是那种真正的ET模式的支持。
本人在JSRB项目中(jsrb.sourceforge.net)实现了一个基于JNI+EPOLL的支持,如有兴趣可以深入探讨。
timerri 2007-12-21
[quot]对于一个设计和实现得较好,超大访问量的系统,其最后的瓶颈,会在你的I/O层面,而不会在你的逻辑层。[/quot]

你这句话说得很好么,可是你怎么又拿密集型计算去比较了?既然你知道高性能系统比较的是io性能,那你可以比较一下java和本地代码的io性能差别到底有多大.....(你不妨比较一下iis和tomcat在区区1000并发下的处理速度,我这里的结果是4倍)。

[quot]用纯JAVA来做,如果精益求精,其整体性能和C/C++实现的是不相上下的。[/quot]
还是用iis的性能说事吧,希望你能找到一个纯java的实现,性能接近iis....
linux下嘛,性能接近lighttp也好啊。
xgyxgy 2007-12-21
timerri 写道
xgyxgy 写道
timerri 写道
一般对java底层不了解的人都会反对jni,不过大可不必敌视它。因为java中对于底层的操作全部是通过jni实现的,包括那些耳熟能详的ServerSocket,FileInputStream等等...

对于超大访问量的系统,用纯java来做应该是糟糕的设计...除非硬件成本能大大低于软件成本.

J2EE的优势在于快速开发,而不在于性能优化。


超大访问量的系统不能用纯JAVA来做这种观点是非常错误的。怎样才算超大访问量?千万级别的算不算?我手头上就有N个N千万级别的系统,有些还是单机上千万级别的,纯JAVA打造。事实上,JDK1.4和JDK5.0与及现在的JDK6.0,性能上的提升是非常惊人的。现在在server端,没什么是纯JAVA做不了的。不止千万,亿级别的系统,同样可以用JAVA来做,而且性能不会比C/C++的要低。


对于超大访问量的系统,用纯java来做应该是糟糕的设计...除非硬件成本能大大低于软件成本.

请仔细看我这句话,我并没有说大型系统不能用java实现。
只不过,在同样的硬件配置下,使用纯java开发的系统将比经过c或c++性能低。要弥补这样的差距,就只能提高硬件配置达到。而在超大型系统中,提高硬件配置的代价是惊人的,一般情况下远远大于通过软件优化的方式的成本。所以,局限于纯java来解决是十分不明智的。

不要盲目相信java的性能,jdk中的实现大部分只是最通用的,而不是最优化的。某些时候,我们必须用自己的本地代码去替换掉jdk中的本地代码的实现以取得更优化的性能。


我明白你的意思,不过我还是觉得你的看法是错误的。对于超大访问量的系统,用纯JAVA来做,硬件成本会比用C/C++做的要高很多?这个是大错特错的!对于一个设计和实现得较好,超大访问量的系统,其最后的瓶颈,会在你的I/O层面,而不会在你的逻辑层。诚然,如果单纯密集型计算,单耗CPU的那种,JAVA会比C/C++的性能会低10% --20%,但是你有几个节点是密集型计算的?在当今的互联网和企业应用,密集型计算的节点,占不到你整个系统的5%。此时,再怎样讨论提升这个10%,已经毫无意义了。对于网络应用来看,单机的情况下,用纯JAVA来做,如果精益求精,其整体性能和C/C++实现的是不相上下的。大型系统最重要是看怎样负载均衡,怎样解决I/O,怎样做cluster。而JAVA在实现这些方面来说,有其天生的优势。在我所看过的团队,有很多花了几个月时间才用C/C++完成一个网络应用(当然都是非常高负载的),但还是较容易coreDump,维护方面较麻烦。而我们的团队可以花不到一个月的时间就完成这个,而且非常稳定,基本上没有出现问题。这两个实现的单机负载能力也不相上下,纯JAVA实现的并不差。我不认为两个团队的水平有很大差别,只是我认为用纯JAVA,会占了很多优势,在开发效率和稳定性上都不错。

windows下的应用我没有经验,也没有发言权了。以上所说的全是在*NIX的。
timerri 2007-12-20
while(1) {  
  nready = select( maxfd + 1, &rset, NULL, NULL, NULL );  
  for( i = 0; i <= maxi; i++ ) {  
    sockfd = client[i];  
    if( FD_ISSET( sockfd, &rset ) ) {  
    //有事件了,做爱做的事
    }  
  }  
}  


 while (1)
 {
  b = GetQueuedCompletionStatus(csdp->m_hPort,
                                &nBytes,
                                &WorkIndex,
                                &pOvl,
                                INFINITE);
  if (b && pOvl)
  {  
    //有事件了,做爱做的事

  };

  };


这两个结构区别很大么?他们只是实现细节不同,后续操作大同小异......

这个问题不用再深究了,我们的分歧在于关注点不同。倒是IOCP用法的多样性,确实是个值得研究一下的方向。
stephen 2007-12-19
timerri 写道
简单说起来,完成端口基于监视线程+消息队列,只不过是内核实现的。
而select就是完全的轮询。这牵扯到二者的具体实现。
不过简化考虑的话,把完成端口当作增强版的select去考虑也是没有什么问题的。在使用上,二者其实很相似....


两者在程序结构上有很大不同。

select 典型的程序结构http://iunknown.javaeye.com/blog/41077
for( ; ; ) {  
  nready = select( maxfd + 1, &rset, NULL, NULL, NULL );  
  for( i = 0; i <= maxi; i++ ) {  
    sockfd = client[i];  
    if( FD_ISSET( sockfd, &rset ) ) {  
      n = read( sockfd, line, MAXLINE );  
      write( sockfd, line, n );  
  
      if( --nready <= 0 ) break;  
    }  
  }  
}  


IOCP 的典型结构http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndllpro/html/msdn_servrapp.asp
CServerDatabaseProtocol::CServerDatabaseProtocol(int iThreadCount) 
{
  bActive=TRUE;
  m_iThreadCount=iThreadCount;
  if (iThreadCount>MAXTHREADCOUNT)
  m_iThreadCount=MAXTHREADCOUNT;
  DWORD id;
  m_hPort=
     ::CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,m_iThreadCount);
  for (int iLoop=0;iLoop<m_iThreadCount;iLoop++)
   {
// Now create the worker threads.
    m_coThreads[iLoop]=::CreateThread(NULL,0,
                                   (LPTHREAD_START_ROUTINE)WorkerThreadFunction,
                                   (void *)this,CREATE_SUSPENDED,&id);
    ::SetThreadPriority(m_coThreads[iLoop],THREAD_PRIORITY_BELOW_NORMAL);
    ::ResumeThread(m_coThreads[iLoop]);
    m_coClients[iLoop]=NULL;
   };
};

int CServerDatabaseProtocol::Associate (HANDLE hComm, int iIndex)
{
CreateIoCompletionPort (hComm,
                        m_hPort,
                        (DWORD)iIndex,
                        m_iThreadCount);

 return 1;
}

long WINAPI WorkerThreadFunction(void *vArg)
{
 CServerDatabaseProtocol *csdp=(CServerDatabaseProtocol *)vArg;
 DWORD nBytes;
 DWORD WorkIndex;
 OVERLAPPED ovl;
 LPOVERLAPPED pOvl=&ovl;
 CClientObject *cpCurrentContext;
 BOOL b;
 while (csdp->bActive)
 {
  b = GetQueuedCompletionStatus(csdp->m_hPort,
                                &nBytes,
                                &WorkIndex,
                                &pOvl,
                                INFINITE);
  if (!b || !pOvl)
  { // Something has gone wrong here...
   GetLastError();
   continue; 
  };
  cpCurrentContext=csdp->m_coClients[WorkIndex];
  if (cpCurrentContext->DispatchFn((STATE_ENUMERATOR)cpCurrentContext->m_se,&ovl)
      == CMD_CLIENT_TERMINATED)
    csdp->DeAssociate(WorkIndex); 
};
 return 0;
};
timerri 2007-12-19
简单说起来,完成端口基于监视线程+消息队列,只不过是内核实现的。
而select就是完全的轮询。这牵扯到二者的具体实现。
不过简化考虑的话,把完成端口当作增强版的select去考虑也是没有什么问题的。在使用上,二者其实很相似....

关于half-sync/half-async,SEDA 和 生产者/消费者
我以前没有研究,大致看起来确实比较符合多流水线架构,多谢提供这样的资料,我研究下看看。
stephen 2007-12-19
timerri 写道

windows平台下比较先进的模型是完成端口+重叠IO,完成端口可以看作增强版的select,它可以支持更大量的句柄,并且可以一次返回多个事件。重叠IO是另一个强大的工具,它允许用户异步的进行通讯操作,而且还能允许用户自己管理传输缓冲。使用这二者的组合,完全可以把所有端口的所有传输数据分成一个个等长的小块,让cpu进行基于定长数据块的处理。看起来像什么?是不是更像流水线?这种方式可以处理大量的连接而不会使性能降低很多。

linux平台下使用的是epoll,功能很像完成端口,也支持一次返回多个事件。只不过没有重叠io的支持,不知道这种模型赶不赶得上windows下的方案?


完成端口并不是增强版的 select ,epoll 反倒可以认为是增强版的 select 。
关于完成端口的描述,可以参考一下这个:http://www.javaeye.com/post/336628

timerri 写道

(补充一点:jdk1.5 update9 之后,linux下的nio已经支持了epoll)

无论是完成端口还是epoll,我们的程序中都不再需要大量的线程了,但是仅仅使用一个线程完成所有请求的处理这种方式也是不可取的,因为很多情况下,系统都可能会对某些资源进行等待,这样就会降低综合的连接处理速度。比较好的方法是按需使用线程,在不大可能出现等待的逻辑中使用单独线程完成,在可能出现等待的逻辑中建立少量线程用多条流水线的处理方式去完成任务。

使用完成端口或者epoll的解决方案,java在大连接处理上的能力将会大大提高,只不过这时候java就不会再成为项目中的主导,看起来会更像一个嵌入式的解释器...


建立少量线程用多条流水线的处理方式去完成任务的想法已经有一些很经典的做法了,可以看看这个
half-sync/half-async,SEDA 和 生产者/消费者
发表评论

提醒: 该博客已发表在公共论坛,博客所有留言会成为论坛回贴,留言请注意遵守论坛发贴规则

您还没有登录,请登录后发表评论

timerri
搜索本博客
最近加入圈子
存档
最新评论