设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 IT综合资讯 查看内容

虚拟研讨会:在低延迟环境中使用Java

2014-7-23 11:52| 发布者: joejoe0332| 查看: 3683| 评论: 0|原作者: InfoQ|来自: InfoQ

摘要: 正文:  以前,C和C++是低延迟环境事实上的选择,但现在Java使用的越来越多了。  InfoQ有幸邀请到了这个领域的四位专家,跟他们一起讨论是什么推动了这一趋势,在这种情况下使用Java有哪些最佳实践。与会者名单 ...


问题4.InfoQ:如果先不谈垃圾回收,那么Java还有哪些其他特有的技术有助于编写低延迟代码的技术(如果你用C++就不会用到这些技术)?我能想到的有JVM预热,把所有类加载到permgen中以避免IO,用于避免缓存未命中的Java专有技术等等。

Lawrey:Java能让你编写、测试和剖析应用程序,使应用在有限的资源内更加有效。这使你能有更多的时间确保完成所有最重要的事。我见过很多C或C++项目花了很多的时间去深度探讨底层,最终两个终端之间仍会很长时间的延迟。

Montgomery:这个问题可有些难度。有一点比较明显,JVM的预热可以做适当的优化。然而,目前的C++无法在运行期做类层次分析时优化一些类和方法的调用。在C++中可以使用很多其他技术,或者在某些情况下并不需要这些技术。不论哪种语言的低延迟技术通常都有一些建议,告诉你最好不要去做哪些事,它们会带来很大的影响。在Java中需要避免的对低延迟应用有不良影响的做法不算很多。其中之一是不要使用特定的API,比如Reflection(反射)API。非常幸运的是,我们通常可以用更好的方案达成相同的结果。

Thompson:答案就在你的问题里,:-)。从本质上说,Java必须先预热才能使运行期达到稳定状态。一旦稳定下来Java就可以像本机语言一样快了,甚至在某些情况下会更快。Java最大的弱点是缺少对内存层的控制。在主流处理器中一次缓存未命中就会丢失500条已经执行过的指令。为了避免缓存不能命中,我们需要控制内存层,以一种可预测的方式访问内存。为了达到这种程度的控制,并减轻垃圾回收的压力,我们通常要用DirectByteBuffers去创建数据结构,或者放弃堆而使用Unsafe。这就可以做到精确的数据结构规划。如果Java引入对结构体数组的支持,就不必再这么做了。这并不需要切换语言,只是引入一些新的特性。

Piper:这个问题似乎是一个伪命题。综合所有情况来看,编写低延迟程序还是编写其他特别关注性能的程序是非常类似的(无论C++还是Java),无非是让开发人员编写的代码在某些层进行间接处理后(比如,通过JVM或者通过C++的类库、编译优化等)在硬件平台上运行,事实上这些细节的不同并不会造成多大的差异。优化本质上是一种演练,优化的规则一直都是像下面这样的描述:

  1. 不要。
  2. 也不要(只适用于专家)。

如果没达到你想要优化到的程度:

  1. 看看你是否真的需要提速。
  2. 剖析代码看看实际上把时间都花在了哪里。
  3. 重点关注具有高回报值的区域,先不考虑其他部分。

当然现在你用工具做这些事的时候,Java和C++可能有不同的潜在热点,那只是因为它们本来就不同嘛。诚然,你需要比普通的JAVA程序员多了解一些细节(使用C++也是如此);相比而言使用Java时某些内容并不需要做过多深入地了解,因为在运行期已经对它们进行了充分地处理。你可能需要优化以下几个方面:可疑的代码路径、数据结构和锁。我们在Diffusion中采用了基准驱动法,我们不断地剖析我们的应用程序,寻找优化的可能性。


问题5.InfoQ:管理垃圾回收行为对大家用Java编写低延迟代码的方式有怎样的影响?

Lawrey在不同的情况下有不同的解决方案。我首选的解决方案是把垃圾限制到最小,那么它就不会造成多大危害了。你可以把垃圾回收的次数降低到每天一次以内。

个人认为,这时候减少垃圾回收真正的理由是擦除尚未填满CPU缓存的垃圾。减少这些垃圾能为你提升2到5倍的代码性能。

Montgomery:我发现大多数Java低延迟系统为了最小化甚至试图消除垃圾的产生已经做出了最大的努力。比方说,避免使用字符串都不算是什么稀罕事了。Informatica Ultra Messaging (UM)自己已经提供了特定的Java方法以迎合大量用户复用对象的需求,并避免了一些使用模式。如果让我来猜的话,最常见的隐含式已经成为对象复用的流行用法。这一模式也受到了其他许多非低延迟类库的影响,比如Hadoop。它目前已经成为社区内的一项常用技术,它为API或框架用户提供了选项和方法,使用户可以在低垃圾或者零垃圾的方式下使用它们。

除了代码实践方面的影响,运维也会对低延迟系统产生影响。正像我们说过的,许多系统将采取一些垃圾回收的创新。把垃圾回收的执行限制在每天特定的时间已经不再是一种罕见的方法。这意味着应用设计和运维需求是控制异常值和获得更多确定性的主要因素。

Thompson:如果使用对象池(像前面的回答中所说的)就需要在ByteBuffers或空闲的堆中管理大部分数据结构,这使Java程序有了C一样的风格。如果我们拥有真正的并发垃圾回收器就可以避免这种结果。

Piperjava.lang.String到底会有多大?不好意思,开个玩笑,实话实说,与其让每个程序员去修改他们的代码,还不如改进垃圾回收的行为。以HotSpot为例,从早期垃圾回收停顿以分钟计,到现在也走过了一段艰难漫长之路。许多改进都是由市场竞争驱动的,从延迟的角度来看,BEA JRockit在过去的表现一向都比HotSpot要好很多,抖动要低得多。然而最近Oracle正在合并JRockit和HotSpot的代码库,原因恰恰是它们之间几乎没有多大差距了。在其他很流行的JVM(比如Azul的Zing)上也可以看到很多类似的改进,许多情况下开发人员试图“改进”垃圾回收的行为,但没有取得真正的效果,反而有的时候适得其反。

但是,这并不是说开发人员不能去管理垃圾回收,举例来说,合并和使用空闲的堆存储可以降低对象的分配,从而限制内存抖动。同时也应该想到,JVM的开发人员也非常关心这些问题,所以你基本没必要自己去处理这些事,或者只需要购买一个商业的JVM。最糟糕的是,你在这一方面优化应用的时候根本就不确定它是不是真的有问题,但从此以后由于这类技术绕过了Java垃圾回收那些非常实用的特性,从而增加了应用的复杂度,使之后的维护更加困难。


问题6.InfoQ:在分析低延迟应用时,你有没有在性能的“峰值”和极端值背后发现任何常见原因或模式?

Lawrey:IO等待之类的。CPU指令或数据缓存干扰。上下文切换。

Montgomery:在Java里,大家开始更加充分地理解垃圾回收停顿了,我们很幸运能够用到更好的垃圾回收器,它更为实用。然而,系统影响对于所有语言都是共有的。在峰值背后的众多原因之中,躲藏着这么一个原因,那就是操作系统调度延迟。有时它是直接的延迟,而有时它是由于延迟引起的连锁反应,相比而言这更要命。在重负载之下某些操作系统的调度要比其他操作系统更好。出人意料的是,对于许多开发人员来说,糟糕的应用选择会经常使调度成为意外状况,而且往往难以充分调试。你需要注意来自于I/O的内在延迟和在某些系统上会发生的I/O竞争。一个好的假设是,任何I/O调用都可能会在某些点和将在某些点上阻塞。往往关键是思考其内在的影响。请铭记,网络调用即I/O。

还有许多网络方面的特定原因同样会造成糟糕的性能。我列举一下几条比较关键的原因。

  • 网络通行要花时间。在广域网环境里,跨网间传播数据需要花费大量的时间。
  • 以太网是非可靠的,它是提供可靠性之上的协议。
  • 网内丢包引起延迟,这些延迟是由于中继和恢复以及类似于TCP队头阻塞的次级效应。
  • 使用UDP时,由于资源匮乏造成在接收端发生各种方式的网内丢包。
  • 由于交换机和路由器拥塞发生网内丢包。路由器和交换机是很自然的竞争点,它们之间产生竞争时丢包是权益之计。
  • 可靠的网络介质(比如InfiniBand)针对网络层的延迟在权衡之下会选择丢包。不过,丢包的最终结果同样还是会造成延迟。

在很大程度上,大量使用网络的低延迟应用往往不得不考虑大量的延迟原因以及附加的网内抖动的来源。在很多低延迟应用中,抖动的最常见原因除了网络延迟外,有最大嫌疑的可能就是丢包了。

Thompson:我清楚很多延迟峰值的原因。许多人都知道垃圾回收,除此之外我还清楚许多锁竞争、TCP相关的问题以及许多Linux内核由于配置不当导致的相关问题。许多应用的算法设计得比较差,它们在突发情况下无法分摊那些开销很大的操作(比如IO和缓存未命中),从而形成排队效应。我发现算法设计常常是应用性能问题和延迟峰值的最主要原因。

处理延迟峰值时,到达安全点的时间(TTS)是一个主要考虑因素。许多JVM操作需要通过使所有用户线程达到安全点才能中止这些线程。安全点检查通常是在方法返回上执行的。这需要安全点能够成为来自于撤销的倾向锁、某些JNI交互、未优化的代码直到许多垃圾回收阶段的任何事物。通常把所有线程发到安全点所花的时间比完成作业本身还要多得多。随后的工作是要花费巨大的成本去唤醒那些所有的线程让它们再次执行。让一个线程快速、可预见地到达安全点通常不是许多JVM考虑或优化的部分,比如对象克隆和数组复制。

Piper:峰值最常见的原因是垃圾回收停顿,改善垃圾回收停顿最常用的方法是垃圾回收调优,这要优于实际去变更代码。例如,JDK6和JDK7默认使用的是并行收集器parallel collector),我们只是简单地把它换为并发标记清除收集器(concurrent mark sweep collector)就能使“全世界停止运行”的垃圾回收停顿产生巨大的变化,通常正是它导致的峰值。除此之外,你还要考虑用到的堆的大小。太大的堆会给垃圾回收带来更大的压力,会造成更长的停顿时间,一般情况下只需简单地消除内存泄漏和降低内存使用率就会使一个低延迟应用有截然不同的整体行为表现。

除了垃圾回收之外,延迟峰值的另一主要原因是锁竞争,但由于它通常难以确定,所以使它更加难以识别和处理。另外一定要牢记的是,任何时候应用都没有能力去处理它,它将产生延迟峰值。很多情况下都会产生锁竞争,有一些甚至在JVM控制之外,比如访问内核或操作系统资源。如果可以识别出这些限制,那么完全可以修改应用使它不使用这些资源,或者改变使用这些资源的时间。


问题7.InfoQ:Java 7已经开始支持基于InfiniBand设备的套接字直接协议(Sockets Direct Protocol,SDP)了。你是否已经看到过有生产系统使用过它了?如果这项技术还没有被应用过,那么你看到有什么其他的解决方案吗?

Lawrey:因为它会产生相当多的垃圾,所以我没有把它用于以太网。在低延迟系统中,你希望把网络跳数降到最低,通常唯一不能移除的就是外部连接。这些通常都是以太网。

Montgomery我们见过的不多。在前面我提到过它,但我们还没有看到它被认真考虑过。Ultra Messaging是用来在SDP和开发人员之间使用消息传递的接口。SDP非常适合用于(R)DMA访问模式,而不是基于推送的应用模式。虽然可以把DMA模式转变为推送模式,但很遗憾,这不适合用SDP来做。

Thompson我还没有见过它在实际环境中的应用。多数人使用类似于OpenOnload和那些来自于Solarflare或Mellanox之类的网络适配器。在极端情况下,我看到在InfiniBand上的RDMA使用预定义的锁无关算法直接从Java中访问共享的内存。

PiperOracle的Exalogic和Coherence这两个产品已经用过Java和SDP有一段时间了,所以从这个意义上来说,我们已经见过这一特性在产品系统中应用过一段时间了。按照开发人员实际使用Java SDP代替某些第三方产品去支持目录的情况看,也没有太多,但是如果他能增加商业利益,那我们预计这一点会有所改变。我们自己已经使用了针对延迟优化过的硬件(例如Solarflare10GbE适配器),从核心驱动安装包中获得的好处要优于具体的Java调优。



酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部