前一段时间被人问了个问题: 在使用 ES 的过程中有没有做过什么 JVM 调优措施?
在我搭建 ES 集群过程中, 参照官方文档来的, 并没有对 JVM 参数做过多的调整. 其实谈 JVM 配置参数, 少不了操作系统层面上的一些配置参数, 比如 page cache. 同时 ES 进程需要打开大量文件, 文件描述符的个数 (/etc/security/limits.conf) 也要配置得大一些. 另外 ES jvm.options 配置文件已经针对 JVM 参数做了一些优化, 这里简要介绍一下关于 ES JVM 参数的配置:
将 xms 和 xmx 设置成一样大 避免 JVM 堆的动态调整给应用进程带来 "不稳定"
预留足够的内存空间给 page cache. 后面会重点讨论为什么要这么做?
禁用内存交换, 后面解释为什么要禁用内存交换?
配置 JVM 参数:-XX:+AlwaysPreTouch 减少新生代晋升到老年代时停顿.
启动时就把参数里说好了的内存全部舔一遍, 可能令得启动时慢上一点, 但后面访问时会更流畅, 比如页面会连续分配, 比如不会在晋升新生代到老生代时才去访问页面使得 GC 停顿时间加长
-XX:CMSInitiatingOccupancyFraction 设置成 75%. 主要是因为 CMS 是并发收集, 垃圾回收线程和用户线程同时运行, 老年代使用到 75% 就回收减少 OOM 异常.
-XX:MaxTenuringThreshold 设置成 6. 默认 Survivor 区对象经历 15 次 Young GC 后进入老年代, 设置成 6 就只需 6 次 YGC 就晋升到老年代了. 因为 ES 新生代采用 -XX:+UseParNewGC, 关于这个参数的调优说明, 可参考: 关键业务系统的 JVM 参数推荐(2018 仲夏版)
Young GC 是最大的应用停顿来源, 而新生代里 GC 后存活对象的多少又直接影响停顿的时间, 所以如果清楚 Young GC 的执行频率和应用里大部分临时对象的最长生命周期, 可以把它设的更短一点, 让其实不是临时对象的新生代对象赶紧晋升到年老代
?
-Xss 配置为 1M. 线程占用栈内存, 默认每条线程为 1M. 从这个参数可看出一个进程下可创建的线程数量是有限制的, 可视为创建线程的开销.
page cache 与 应用程序内存
Linux 内存可分成 2 种类型: Page cache 和应用程序内存. 应用程序内存会被 Linux 的 swap 机制交换出去, 而 page cache 则是由 Linux 后台的异步 flush 策略刷盘. 当 OS 内存满了时, 就需要把一部分内存数据写入磁盘, 因此就需要决定是 swap 应用程序内存呢? 还是清理 page cache?OS 有个系统参数 / proc/sys/vm/swappiness 默认值 60, 一般将之设置成 0, 表示优先刷新 page cache 而不是将应用程序的内存交换出去.
那 Linux 的 page cache 的清除策略是什么呢?-- 优化了的 LRU 算法. LRU 存在缺点: 那些新读取的但却只使用一次的数据占满了 LRU 列表, 反而把热点数据给 evict 到磁盘上去了, 因此还需要考虑数据的访问次数(频率)
很多存储系统针对 LRU 做了一些优化, 比如 Redis 的键过期策略是基于 LRU 算法的思想, 用若干个位记录每个 key 的 idle time(空闲时间), 将空闲时间较大的 key 存入 pool, 从 pool 中选择 key evict 出去, 具体可参考: Redis 的 LRU 算法 https://www.cnblogs.com/hapjin/p/10933405.html . 再比如 MySQL innodb 缓冲池管理 page 也是基于 LRU, 当新读入一页数据时不是立即放到 LRU 链表头, 而是设置 mid point(默认 37%), 即放到链表 37% 位置处 . 关于如何理解 page cache 与应用程序内存之间的区别, 可参考这篇文章: 从 Apache Kafka 重温文件高效读写 http://calvin1978.blogcn.com/articles/kafkaio.html
Linux 总会把系统中还没被应用使用的内存挪来给 Page Cache, 在命令行输入 free, 或者 cat /proc/meminfo,"Cached" 的部分就是 Page Cache.
当 Linux 系统内存不足时, 要么就回收 page cache, 要么就内存交换(swap), 而内存交换就有可能把应用程序的数据换出到磁盘上去了, 这就有可能造成应用的长时间停顿了. 参考: 在你的代码之外, 服务时延过长的三个追查方向(上) http://calvin1978.blogcn.com/articles/latency.html
Linux 有个很怪的癖好, 当内存不足时, 有很大机率不是把用作 IO 缓存的 Page Cache 收回, 而是把冷的应用内存 page out 到磁盘上. 当这段内存重新要被访问时, 再把它重新 page in 回内存(所谓的主缺页错误), 这个过程进程是停顿的. 增长缓慢的老生代, 池化的堆外内存, 都可能被认为是冷内存, 用 cat /proc/[pid]/status 看看 VmSwap 的大小, 再 dstat 里看看监控 page in 发生的时间.
一个 JVM 长时间停顿的示例
随着系统的运行, JVM 堆内存会被使用, 引用不可达对象就是垃圾对象, 而操作系统有可能会把这些垃圾对象交换到磁盘上去. 当 JVM 堆使用到一定程度时, 触发 FullGC,FullGC 过程中发现这些未引用的对象都被交换到磁盘上去了, 于是 JVM 垃圾回收进程得把它们重新读回来到内存中, 然而戏剧性的是: 这些未引用的对象本身就是垃圾, 读到内存中的目的是回收被丢弃它们! 而这种来回读取磁盘的操作导致了 FullGC 消耗了大量时间,(有可能)发生长时间的 stop the world. 因此, 需要禁用内存交换以避免这种现象, 这也是为什么在部署 Kafka 和 ES 的机器上都应该要禁用内存交换的原因吧. 具体可参考这篇文章: Just say no to swapping!
总结
本文以 ES 使用的 JVM 配置参数为示例, 记录了一些关于 JVM 调优的参数理解, 以及涉及到的一些关于 page cache, 应用程序内存区别, LRU 算法思想等, 它们在 MySQL,Kafka,Elasticsearch 都有所应用. 文中给出的参考链接都非常好, 对深入理解系统底层运行原理有帮助.
来源: http://www.bubuko.com/infodetail-3122169.html