一个15万pv、天左右的在线文档类型网站最近更换了硬件系统,新的硬件为4个cpu,16GB物理内存,操作系统为 64位的 CentOS 5.4 ,Resin作为服务器。
管理员选用了 64位的 jdk 1.5,通过-Xmx,-Xms 将堆固定在12GB,使用一段时间后,出现了卡B现象,长时间无响应。
监控发现卡B是由于GC停顿导致的。虚拟机运行在Server模式,默认使用吞吐量优先收集器,一次回收12GB的堆,一次 Full Gc的时间高达 14秒,再加上程序设计的关系,访问文档中需要一次性把文档从磁盘中读取到内存,导致内存中出现很多由文档序列化产生的大对象,这些大对象很多都进入了老年代,没有在Minor GC中清理掉。在这种情况下,即使有12GB的堆,内存也很快会被消耗殆尽,有次导致每隔十几分钟出现十几秒的卡B,令网站开发人员和管理人员感到郁闷。
这里先不谈眼神讨论程序代码问题,在程序部署上的主要问题显然是过大的堆内存进行回收时带来的城市间的停顿。硬件升级前使用32为系统1.5GB的堆,用户只感到访问网站比较缓慢,但不会发生十分明显的停顿,因此才考虑升级硬件提升程序效能。如果重新缩小给Java堆分配的内存,那么硬件上的投资就浪费了。
在高性能硬件上部署程序,目前主要有两种方式:
1 通过 64位 JDK 来使用大内存。
2 使用若干个32位虚拟机简历逻辑集群来利用硬件资源。
此案例中的管理员才用了第一种部署方式。对于用户交互性强、对停顿时间敏感的系统,可以给Java虚拟机非陪超大堆的前提是有把握把应用程序的Full GC频率控制的足够地,至少要低到不会影响到用户使用,譬如十几个小时乃至一天才出现一次GC,这样可以通过在深夜执行定时任务的方式触发Full GC 甚至自动重启应用服务器来将内存可用空间保持在一个稳定的水平。
控制Full GC频率的关键是看应用中绝大多数对象是否符合“朝生夕灭”的原则,即大多数对象的生存时间不应当太长,尤其是不能产生成批量的、长生存时间的大对象,这样才能保障老年代空间的稳定。
在大多数网站形式的应用里,主要对象的生存周期都应该是请求级或页面级的,会话级和全局级的长生命对象相对很少。只要代码写的合理,应当都能实现在超大堆中正常使用而没有Full GC,这样的话,使用超大堆内存时,网站相应的速度才比较有保证。除此之外,如果读者计划使用64位的JDK来管理大内存,还需要考虑下面可能面临的问题:
1 内存回收导致的长时间停顿。
2 现阶段,64位JDK的性能测试普遍地域32位JDK。
3 需要保证程序足够稳定,因为这种应用要是产生堆溢出几乎就无法产生堆转储快照(因为要产生十几GB乃至更大的dump文件),即使产生了快照也几乎无法进行分析。
4 相同的程序在64位JDK中消耗的内存一般比32位的JDK大,这是由指针膨胀以及数据类型对其补白等因素导致的。
考虑到以上几点因素,你可能会选择使用若干个虚拟机集群来利用硬件资源,具体做法是子啊一台物理机器上启动多个应用服务器进程,给每个服务器进程分配不同的端口,然后在前端大减一个负载均衡器,以反向代理的方式来分配访问请求。
这种部署方式可能会遇到下面的问题:
1 尽量避免节点竞争全局的资源,最典型的就是磁盘竞争,各个节点如果同事访问某个磁盘文件的话,很容易导致IO异常。
2 很难高效率的利用某些资源池,譬如连接池,一般都是在各个节点上简历自己的独立的连接池,这样有可能导致一些节点池满了而另外一些节点仍有较多空余,尽管可以使用集中式的JNDI,但这有一定的复杂性并且可能带来额外的性能代价。
3 各个节点仍然不可避免的收到32位的内存限制,在32位window平台中每个进程只能使用2GB的内存,考虑到堆以外的内存开销,堆一般最多只能开到1.5GB。在某些linux unix 系统(Solaris)可以提升到3GB乃至接近4Gb的内存,但32位中仍然接受最高4GB内存的限制。
4 大量使用本地缓存的应用,在逻辑集群中会造成大量的内存浪费,因为每个逻辑节点上都有一份缓存,这时可以考虑把本地缓存改为集中式缓存。
重新回到本案例。最后的部署方案调整为简历5个32位JDK的逻辑集群,每个进程按照2GB内存计算(其中堆固定为1.5GB),占用了10GB的内存。另外建立一个Apache服务作为前端均衡代理访问门户。考虑到用户对响应速度比较关心,并且文档服务的主要压力集中在磁盘和内存访问上,CPU 资源敏感度脚底,因此采用CMS 收集器进行垃圾回收。在部署方式调整后,服务在没有出现长时间停顿,速度也有比较大提升。
感谢周志明启发。
高性能硬件上的程序部署策略
最新推荐文章于 2024-08-30 10:54:11 发布