2012年10月8日星期一

Cassandra运维问题小记

一眨眼cassandra从0.7到1.0.8用了快半年多快一年了, 总体来说, 这真是一个非常好的开源项目, 但是运维过程中也遇到过一些小问题, 总结一下, 省得自己忘记

1. ParNew GC Pause
有三台服务器经常会产生SocketTimeout, 看了日志发现每次compaction之后就会产生一次ParNew GC, 最长有6秒左右, 有时候没有compaction平均每5-10分钟也会ParNew GC一次, 不过这种情况相对比较短, 在300-500ms左右. 观察了其他节点, 一到两个月才会出现一次200ms以上的ParNew GC.

ParNew是一个stop-of-the-world GC, 当young的对象promote到old generation的时候, 这个GC会暂停整个VM一会. 最长6秒的GC会导致前端Hector客户端SocketTimeout.

jmap看了一下heap分布, 又看了一下启动脚本, 发现总共4GB的heap, 其中young generation是200MB * cores = 800MB, survivor ratio=8, 这样eden=720MB, suvivor=160MB. cassandra的启动脚本这样计算young gen是为了减少promotion, 避免更严重的old generation的频繁GC.

用jmanage连上jmx接口看了一下gc动作, 每5分钟从1.3G到2.6G之间抖动, , 这是比较大的young generation gc, 所以会经常有200ms以上的ParNew GC. 但是另外三个节点抖动没这样么剧烈.

查找资料后来发现jdk6_u06在young到old的promotion的时候一次只能复制16MB, 要u22之后才能去掉这个限制. 再次检查产品环境发现init.d里的启动脚本有问题, 有问题的三个节点用的是jdk6_u06启动的, 这么大的young generation做promotion一次只能copy 16MB肯定会有问题.于是在三个节点上改启动脚本用jdk7启动, 重启后发现三个节点有两个正常, 一个仍然频繁GC.

JDK7在处理小于32G的heap的时候会自动压缩64位的指针为32位, 但是jdk6某些版本需要额外加参数才能打开, 这会大幅度减小heap的大小, 这样的好处有
1. 虽然内存(DRAM)不贵, 但是缓存(SRAM)贵, 压缩后的指针虽然需要encode/decode但是可以让L1/L2 cache保存更多的指针, 如果按照管官方文档说的可以减小1倍, 那么等同于也就提高一倍L1/L2 cache了.
2. 减小内存总线的压力. NUMA架构下尽管已经被SMP的全局数据总线效果好很多, 但是处理器交叉访问内存的时候仍然会对总线宽度产生压力. 压缩后的指针对总线的压力更小.

最后有问题的那个节点是服务器硬件本身就比较老, 2007年的E5405处理器, 在Centos5下面跑连NUMA都不支持. 没办法, 只能通过VM调优, 降低内存使用, 继而减少GC行为. 为此再次加入两个String相关的VM参数优化内存,
JVM_OPTS="$JVM_OPTS -XX:+UseStringCache"
JVM_OPTS="$JVM_OPTS -XX:+OptimizeStringConcat"
加入这两个参数重启后, 再也没有出现200ms以上的ParNew GC.


2. Dead node appears in the ring
有一个节点因为主板硬件故障, 被我们decomission & remove token了. 当时没问题, 但是后来发现重启其他节点会看到. 估计这是cassandra的一个小bug, 解决方法很暴力, 直接通过JMX改Gossiper, See http://mail-archives.apache.org/mod_mbox/cassandra-user/201206.mbox/%3C02CB6332-9EF7-434F-96EE-80F93CC5EB8D@hibnet.org%3E

下次再实验一下
1. G1 GC的运行效率如何
2. 使用JNA之后heap使用能下降多少