Elasticsearch的内存调优与解析
Elasticsearch的堆内存默认配置是1GB
。对于几乎所有的部署来说,这个数字通常都太小了。如果使用默认堆配置,则集群可能会出现问题。
有两种方法可以在Elasticsearch中更改堆大小。最简单的方法是设置一个名为ES_HEAP_SIZE
的环境变量。当服务器进程启动时,它将读取这个环境变量并相应地设置堆大小。例如,可以通过命令行设置,如下所示:
|
|
也可以通过es进程启动时的命令行参数传入的形式:
|
|
以上两种方式,环境变量ES_HEAP_SIZE
的优先级通常会高于命令行参数 -Xmx
和 -Xms
。
给Lucene预留大约一半的内存
一个常见的问题是es的堆内存配置太大。
堆对于Elasticsearch绝对是重要的。它被许多内存中的数据结构用来提供快速的操作。
但话虽如此,还有一个主要的内存用户不在堆中,那就是 Lucene
。
Lucene
被设计用来利用底层操作系统的内存缓存(caching in-memory)
中的数据结构。Lucene segments 存储在单独的文件中。因为 segments 是不可变的,所以这些文件永远不会改变。这使得它们对缓存非常友好,并且底层操作系统将乐于将热数据段(hot segments)
驻留在内存中,以便更快地访问。这些 segments 包括 倒排索引(用于全文搜索)
和 doc值(用于聚合)
。
Lucene的性能依赖于与操作系统的交互。但是如果你把所有可用的内存都给了Elasticsearch的堆,那么就没有剩余的内存给Lucene了。这可能会严重影响性能。
标准的建议是将50%的可用内存分配给Elasticsearch堆,而将剩下的50%留给空闲内存。它并不会闲置,Lucene会很高兴地吃掉剩下的任何东西。
如果你没有基于字符串分析的聚合需求(例如,你不需要开启字段的fielddata
),你可以考虑降低堆大小甚至降到更小。堆越小,意味着 Elasticsearch将拥有更快的GCs
和 Lucene将有更多的系统文件内存缓存
,也就意味着性能越好。
堆大小不要超过 32 GB
不要给Elasticsearch分配大量堆内存还有另一个原因:事实证明,当堆内存分配小于32GB,HotSpot JVM 虚拟机
会使用一种技巧来压缩对象指针。
在Java中,所有对象都分配在堆上并由指针引用。 普通对象指针(OOP)指向这些对象,并且大小通常是CPU的运算位数:32位或64位,具体取决于处理器。 而指针引用的是该值的具体字节位置。
对于32位系统,这意味着最大堆大小为4 GB。
对于64位系统,堆大小可以变得更大,但是64位的指针会浪费更多的空间,因为指针很大。另外,比浪费空间更糟糕的是,当在主存和各种缓存(LLC、L1等)之间移动值时,较大的指针会消耗更多带宽。
Hotspot JVM 虚拟机
使用一种叫做 普通对象压缩指针(CompressedOops)
的技巧来解决这个问题。指针不是指向内存中的精确字节位置,而是引用对象偏移量。这意味着32位指针可以引用40亿个对象,而不是40亿个字节。最终,这意味着即便堆可以增长到32 GB左右的物理大小,而仍然使用32位指针。
一旦你越过这个神奇的32GB
边界,指针就会切换回普通对象指针
。每个指针的大小会增长,CPU与内存之间的带宽
会使用得更多,从而损失更多的内存性能。实际上,大约普通对象指针下40~50GB
的堆大小才能获得与压缩对象指针下32GB
的堆大小相同的效果。
即使您有多余的内存,也要尽量避免跨越 32GB
堆边界。
否则它将浪费内存,降低CPU性能,并使GC更难处理。
确切的压缩指针堆大小边界
这取决于不同的JVM虚拟机和平台。一般情况下,将堆大小设置成31GB
就可以安全地小于堆边界。
或者,你也可以结合java的-Xmx
参数和-XX:+PrintFlagsFinal
参数,并查看UseCompressedOops
是否等于true
,这会使你在不同JVM与不同平台
下找到准确的压缩指针堆边界
。
例如,我们在MacOSX
平台中的Java 1.7
下,发现能够使用压缩对象指针
的最大堆大小约为32600MB (~31.83GB)
:
|
|
而在同一台机器上,Java 1.8
的值却是 32766MB (~31.99GB)
:
|
|
确切的
压缩对象指针堆边界,是因不同JVM和不同平台而异的。如果要设置确切的值,要额外地小心。从Elasticsearch v2.2.0开始,启动日志实际上会告诉您JVM是否正在使用压缩OOPs。您将看到一条日志消息,如下所示:
|
|
[true]
代表正在使用压缩对象指针。否则它会显示 [false]
。
如果拥有一台1TB内存的机器
32GB的堆边界相当重要。当您的机器有很多内存时,我们该怎么办呢?目前拥有 512 768 GB RAM 的超级服务器已经变得越来越普遍。
首先,我们建议避免使用这样的大型机器(参见ES硬件部署建议 )。
但如果已经有这种大型服务器,我们有三项建议:
4-32GB
堆内存,让Lucene通过操作系统文件系统缓存使用剩余的内存。所有的内存将缓存segments
,用于快速的全文搜索。doc values
完成! 考虑给Elasticsearch分配4-32GB
堆内存,其余的留给OS用来在内存中缓存doc values
。例如,针对单词标签或SigTerm等,不幸的是,这意味着您需要开启fielddata
,这意味着您需要堆空间。 可以考虑在一台计算机上运行两个或多个实例,而不是部署一个大堆内存的实例。 当然,实例堆大小总数仍然要遵守小于50%的规则。
如果您的计算机具有 128GB 的RAM,请运行两个实例,每个实例的内存不要超过 32GB。 这意味着少于 64GB 用于堆内存,剩余的 64GB 用于Lucene。
另外,需要在ES配置文件中配置cluster.routing.allocation.same_shard.host: true
这将阻止主副本分片被分配到同一台物理机,以提高可用性。
性能杀手:Swap交换
这个大家都懂,但还是要强调一下:将主内存swap交换到硬盘中,会损失很多性能。因为纯内存操作 (in-memory operation)
才能使程序运行地更快。
如果将内存置换到硬盘,0.1ms
的操作将会变成10ms
,如果大量的操作耗时都增加了一个数量级,可想而知 swap交换
对性能的损害有多严重。
一、最好的办法是在您的系统上完全禁用swap:
|
|
二、如果不希望完全禁用swap,您可以尝试降低swappiness:
这个值可以影响操作系统交换内存的积极程度。虽然阻止在正常情况下交换,但无法避免操作系统在紧急内存情况下进行交换。
对于大多数Linux系统,可以使用sysctl配置:
|
|
三、如果以上两种方法都不想采用,则应该启用mlockall:
|
|
mlockall() 是一个C语言的系统调用,它将当前映射的进程内存锁到RAM中,从而避免进行Swap交换。
如果要在JVM中使用它,一种简单方法是通过 JNA (Java Native Access) 来调用。例如:mlockall-agent
慎用 vm.swappiness=0
设置 vm.swappiness=1
比 vm.swappiness=0
更好,因为在最新的 Linux 内核中,设置0可能会触发 OOM-killer
。
此前,vm.swappiness = 0
只是Linux在判断是否交换内存(swap)的一个“倾向”参考值,而并不是说,设置为0以后,Linux就完全不会使用内存交换空间。
但是,在较新的内核中(2.6.32-303.el6及以后),vm.swappiness = 0
的默认行为修改了,当机器的内存严重不足时,根据Linux的默认策略,它会首先把内存占用最大的进程kill掉,从而导致应用故障。
这个修改是在内核3.5-rc1中提交的,并且合并到了2.6.32-303.el6及之后的各个版本。先让我们来看看这个patch:
Sometimes we’d like to avoid swapping out anonymous memory. In particular, avoid swapping out pages of important process or process groups while there is a reasonable amount of pagecache on RAM so that we can satisfy our customers' requirements.
OTOH, we can control how aggressive the kernel will swap memory pages with
/proc/sys/vm/swappiness
for global and
/sys/fs/cgroup/memory/memory.swappiness
for each memcg.
But with current reclaim implementation, the kernel may swap out even if
we set swappiness=0
and there is pagecache in RAM.
This patch changes the behavior with swappiness==0
. If we set
swappiness==0
, the kernel does not swap out completely (for global reclaim
until the amount of free pages and filebacked pages in a zone has been
reduced to something very very small (nr_free + nr_filebacked < high
watermark)).
就像Satoru Moriya所说的那样,在之前的版本中,就算我们设置了swappiness=0
并且RAM中还有pagecache
,内核也可能会交换出部分匿名内存页。
而为了“满足用户的需求”,这个patch修改了swappiness=0
的行为:即如果设置swappiness=0
,那么只有在(nr_free + nr_filebacked < high watermark)
才会交换内存,也就是说空闲内存和文件缓存基本没有了才会触发内存swap。
这样的话,副作用在于:内存如果不够了,Linux有可能触发OOM,从而kill掉耗费内存最多的进程。
在2.6.32-303.el6 RHEL/CentOS及更新版本的内核中,该patch就已经被合并进来:
|
|
RHEL/CentOS 6.3 的内核版本是2.6.32-279,而 RHEL/CentOS 6.4 的内核为2.6.32-358,从这个版本开始,swappiness的行为就已经修改了,使用这个版本及之后版本的同志们需要特别注意一下。
其他分发版本的Linux(比如Debian,Ubuntu)的版本中,可以自己查阅一下,看看是什么时候合并的该patch。
解决的办法其实也很简单:
- 尽量保证Linux操作系统还有足够的内存。
- 最新的内核,建议把vm.swappiness设置1。
- 考虑设置
/proc/(es的pid)/oom_adj
为较小的值来尽量避免ES由于内存不足而被关闭。