高性能硬件上的程序部署策略(一)

这些案例也是来自于<<深入理解java虚拟机,JVM高级特性与最佳实践>>第二版,来了解实际项目中的具有代表性的问题

高性能硬件上的程序部署策略

前景介绍: 一个15万PV/天左右的在线文档类型网站最近更换了硬件系统,新的硬件为4个cpu,16GB物理内存,操作系统为64位CentOS 5.4s,Resin作为Web服务器,整个服务器暂时没有部署别的应用,所有的硬件资源都可以提供给这个访问量并不算太大的网站使用,管理员为了尽量利用硬件资源选用了64位的JDK1.5,并通过-Xmx和-Xms参数将java堆固定为12G,使用后效果不理想,经常不定期出现长时间失去响应的情况,原因发现时由于GC停顿导致的,虚拟机运行在Server模式,默认使用吞吐量优先收集器,回收12GB的堆,一次Full GC的停顿时间长达14秒,而且由于程序设计的关系,访问文档时要把文档从磁盘提取到内存中(可以理解为从MySQL提取出出来序列化,存进内存,不是缓存),导致内存中出现很多由文档序列化产生的大对象,这些大对象很多都进入了老年代,没有再Minor GC 中清理掉,这种情况下即使有12GB的堆,内存也会很快消耗殆尽,由此每隔十几分钟出现十几秒的停顿

出现的这种情况的主要问题:
过大的堆内存进行回收时带来的长时间的停顿

在高性能硬件上部署程序,目前主要是两种方式

  1. 通过64位的JDK来使用大内存
  2. 使用若干个32位虚拟机建立逻辑集群来利用硬件资源

1、通过64位JDK来使用大内存

这个案例也是采用的这种方式,

对于使用大内存的前提的有把握把应用程序的Full GC频率控制的足够低,至少要低到不会影响用户使用,譬如十几个小时乃至一天才出现一次Full GC ,这样可以通过在深夜执行定时任务的方式触发Full GC甚至自动重启应用服务来保持内存可用空间在一个稳定的水平

控制Full GC的关键是看应用中绝大多数对象是否符合"朝生夕灭"的原则,即大多数对象的生存时间不应太长,尤其是不能有成批量的,长生存时间的大对象产生,这样才能保障老年代空间的稳定(保障老年代空间的稳定就像保障MySQL数据库不能崩溃一样重要)

大多数网站形式的应用中,主要对象的生命周期都应该是请求级或者页面级,而会话级全局级的长生命对象相对较少,

上面啰嗦了这儿多,其实就一点,代码要写的合理,只要写的合理,应当都能实现在超大堆中正常使用而没有Full GC

但是对于如果用JDK 64位来管理大内存,还是有一定的问题:

  1. 内存回收导致的长时间停顿
  2. 现阶段,64位JDK的性能测试结果普遍低于32位JDK
  3. 需要保证程序足够稳定,因为这种应用要是产生堆溢出几乎就无法产生堆转储快照(因为太大,要产生十几GB乃至更大的Dump文件),哪怕产生了快照也几乎无法进行分析
  4. 相同程序在64位JDK消耗的内存一般比32位的JDK大,这是由于指针膨胀,以及数据类型对齐补白的等因素造成的

上面有这么多问题,所以现阶段很多管理员使用的是第二种部署方案

2、利用若干个32位虚拟机建立逻辑集群来利用硬件资源

在一台物理机上启动多个应用服务进程,每个服务进程分配不同的端口,然后在前端搭建一个负载均衡器(像Nginx这样的),以反向代理的方式分配访问请求,而且不用太过在意均衡器转发所消耗的性能,即使64位的JDK,许多应用也不止有一台服务器,因此许多应用迭代前端的均衡器总是要存在的,

考虑到一台物理机器上建立逻辑集群的目的仅仅是为了尽可能利用硬件资源,并不需要关系状态保留热转移之类的高可用性需求,也不需要保证每个虚拟机进程有绝对准确的均衡负载,因此使用无Session复制亲和式集群时一个不错的选择(当然session和redis缓存数据共享也可以),我们仅仅需要保障集群具有亲和性,也就是均衡器按照一定的规则算法(一般根据sessionIDNginx用一致性哈希)将一个固定的用户请求永远分配到固定的一个集群节点进行处理即可,这样在程序开发阶段就基本不用为集群环境做什么特别的考虑了

但是还是有一定的问题,但是肯定没有第一个部署方案有那么恐怖的问题

  1. 尽量避免节点竞争全局的资源,最典型的就是磁盘竞争,各个节点如果同时访问某个磁盘文件的话(尤其是并发写操作容易出现问题,因为我们都知道读和写的区别,尤其在高并发的情况下),很容易出现IO异常,对于关系型数据库,现在都是读写分离,主从备份,再加个mycat中间件,
  2. 很难最高效率的利用某些资源池,譬如连接池,一般都是在各个节点建立自己的独立的连接池,这样有可能导致一些节点池满了,而有一些节点仍有空余的,尽管可以使用集中式的JNDI,但这个有一定的复杂性并且可能带来额外的性能开销,
  3. 各个节点仍然不可避免的受到32位的内存限制,根据操作系统的不同内存的限制也不同,32位的windows平台每个进程最大用到2G,考虑到堆外内存,也就1.5G,Linux和UNIX可以提升到3GB甚至接近4GB的内存,但32位的最大肯定不能超过4GB,因为232次方
  4. 大量的使用本地缓存,在逻辑集群中会造成较大的内存浪费,因为每个逻辑节点都有一份缓存,这时候可以考虑把本地缓存集中式管理(比如用redis集群来管理所有的缓存)

说完上面的你是否感觉现在很多中间件都是在解决第二个部署方案中的问题的,所以为什么很多还是用第二种部署方案

上面两种的的区别分析完,再回到这个案例上,因为我们不能光分析就行了,还是需要解决实际的情况。

最后案例的部署方案
1、调整为建立5个32位的JDK的逻辑集群,每个进程按2GB计算(堆固定为1.5G),占用了10GB内存,还有6个物理内存,
2、另外建立一个Apache服务(可以理解为Nginx)作为前端均衡代理访问门户,
3、考虑用户对响应速度比较关心,而且这又是一个文档类的网站,主要的压力在磁盘和内存的读取和写入CPU资源敏感度低,因此改用CMS收集器进行垃圾回收。

发布了213 篇原创文章 · 获赞 22 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/weixin_43113679/article/details/100733993
今日推荐