本文主要是总结了Elasticsearch从安装、配置到应用程序使用、运维、性能优化的最佳实践的建议,希望能对于Elasticsearch的开发和运维提供一些帮助。
1、Elasticsearch支持schema_less模式,但是强烈建议index mappings和settings提前通过template进行设置,避免出现数据混乱和一些应用程序逻辑不兼容的问题。
2、集群数量较大时,建议单独规划master节点和ingest节点,避免与数据节点混合部署的情况。这是因为当集群规模比较大以后,master节点与其他节点间的通讯、元数据和状态的管理压力都会比较大,部署独立的节点可以提升性能保障主节点的稳定性。另外,可以避免当master节点离线时,由于master节点和data节点部署在一起,会产生未分配的分片的情况。
3、Elasticsearch底层是lucene,会涉及大量的文件的读写,在数据量比较大的情况下,要保证性能则对底层硬件有比较大的要求,特别是IO,建议物理机SSD部署,如果资源有限可以考虑冷热数据分离。
4、shard的大小对于性能有一定影响,建议shard的大小保持50G以内,另外shard总数量建议不超过50万。
5、应用程序设置调用的超时时间不能过于短,因为Elasticsearch写入的过程是默认先写入主分片,主分片写成功以后转发到副本分片,只有副本分片也写成功了才能返回响应,这是一个同步的过程,如果某个副本分片写入比较慢会影响整体响应的时间。早期版本可以配置async允许异步过程,但2.0.0版本以后取消了这个功能。
6、JVM最大堆内存(-Xmx)和最小堆内存(-Xms)配置成一样的值,避免运行过程中动态调整堆内存大小,这是一个损耗性能的操作。
7、最佳配置:64G物理机,分配50%的内存给elasticsearch,剩下的50%用于操作系统缓存。
Elasticsearch的内存避免高于32G,建议最大31G,这是因为JVM在堆内存小于32G时会采用内存对象指针压缩技术,一旦堆内存超过32G,指针就会退化回普通对象指针,即便内存增加了实际效果可能更差。
8、禁用swapping。swapping会导致性能变慢,一般建议降低swapping发生频率或者直接禁止elasticsearch的swapping。
禁用swapping
在elasticsearch.yml配置文件配置
bootstrap.memory_lock:true打开mlockall开关,锁住JVM内存,禁止被操作系统交换。
注意: 配置了这个选项以后需要在/etc/security/limits.conf配置elasticsearch soft memlock unlimited和elasticsearch hard memlock unlimited
降低swapping发生频率
使用sysctl -q vm.swappiness查看swappiness的值,建议将值修改为1,代表内存不足时才会出现内存交换。
9、配置文件最大描述符
使用ulimit -a查看最大文件描述符数量,可以通过修改/etc/security/limits.conf配置文件进行修改,如下所示,注意,修改完配置以后需要重启系统。
* soft nofile 65536 * hard nofile 65536 * soft nproc 131072 * hard nproc 131072
10、Elasticsearch单个节点能存储的分片数量没有限制,但是每个分片都是一个完成的lucene实例,对于内存和CPU的消耗都是比较大的,所以一个节点上不适宜有太多的分片。可以通过
cluster.routing.allocation.total_shards_per_node配置单个节点能存储的分片数量,通过
index.routing.allocation.total_shards_per_node配置单个节点能存储的单个索引的分片数量。
11、可以动态开启写入和查询慢日志。
/*/_settings {"index.indexing.slowlog.threshold.index.info":"100ms"} /*/_settings {"index.search.slowlog.threshold.query.warn:"100ms"}
12、合理利用routing字段,提升查询效率。
一个document最终被放到那个哪个分片上是有一定的计算规则的,在Elasticsearch上根据公式计算shard_num = hash(_routing) % num_primary_shards,默认情况下_routing使用的是id这个随机的字符串,如果查询的时候有明确的查询字段条件,例如根据证件号等,可以用证件号作为routing,相同的数据hash到同一个分片,这样子查询的时候就可以直接查询对应的分片,而不需要扫描index的所有分片,效率会大大提升。
13、根据业务情况提前规划好索引的大小,比较大的索引建议按照一定的维度进行拆分,例如每个月一个index,查询时利用别名的机制。
14、尽量避免wildcard查询,性能较差。
15、查询时字段按需返回,避免一次性返回整个索引中所有的定义字段。
16、使用Rest API,不要使用Java API等特定客户端语言API,Rest API是趋势,Java API可能未来会被废弃。
17、禁用透明大页Transparent Huge Page。透明大页可能对程序产生一些负面影响,甚至是内存泄漏。
echo server > /sys/kernel/mm/redhat/redhat_transparent_hugepages/defrag
18、大数据量的写入建议采用Bulk批量写入的形式,不建议单条多次写入,效率太低。批量写入可能会有部分失败,可以在收到响应后针对失败的记录进行重试。
19、对于在sort、aggregation场景开启doc_values,提升查询性能,反之,建议将doc_values设置为false,节省磁盘空间,提升索引性能。
20、避免手动提升分片为主分片,而是依赖于集群自身的选举能力,避免操作不当丢失数据。例如被提升为主分片的副本分片数据严重落后其他节点。
21、JVM新生代大小建议为heap大小的1/3,例如-Xmn10g, -XX:NewMaxSize=10g。这个值建议显式指定,因为默认情况下使用CMS算法,理论上新生代和老年代默认比例为1:2,但是实际分配的的新生代和老年代的比例可能不是1:2,可能会导致分配的新生代内存很小,在大批量写入的情况下新生代GC频繁或者oom。
22、在索引或者查询比较慢且占用大量系统CPU的情况下,可以使用/_nodes/hot_threads分析各节点的热点线程CPU消耗情况。
23、Elasticsearch6.8版本以前安全认证和用户权限管理功能是收费的,6.8以后开放了部分功能,能满足一般的应用需要,建议尽量选择6.8以上,如果线上部署了6.8以下的,权限控制可以使用searchguard插件。
24、fielddata内存大小默认没有限制,可能导致频繁的OOM。建议修改配置项
indices.fielddata.cache.size: 10%控制大小,避免堆内存不够GC压力过大。
25、如果字段不需要被搜索,则建议在设置mappings时将index参数设置为false,这样子就不会创建倒排索引,可以节省存储空间。
26、倒排索引记录的内容是可定制化的,通过index_options可以控制索引记录内容,可以为以下这些值,默认情况下被分析器分析的字符串字段使用 positions 作为默认值,其他的字段使用 docs 作为默认值。可以根据需求指定不同的值。
- docs: 记录doc id,能确定某个term是否存在于某个字段,支持搜索。
- freqs: 记录doc id和term frequencies,包含词频,支持评分。
- positions: 记录doc id、term frequencies、term position,支持邻近查询和短语查询
- offsets: 记录doc id、term frequencies、term position、character offsets
27、建议默认将norms设置为false,节省磁盘空间,提升索引性能,只有在需要评分的场景才设置为true。 Norms是一个用来计算文档/字段得分(Score)的"调节因子",TF-IDF、BM25算法计算文档得分时都用到了norms参数,在elasticsearch一般应用于评分。对于 text 类型的字段而言,默认开启了norms,而 keyword 类型的字段则默认关闭了norms。
28、String类型的字段可以设置为keyword和text两种,keyword类型的字段会被当作一个term进行搜索,而text字段会被分词器进行分词,分成多个term提供搜索,如果不希望被分词处理,则设置为keyword。枚举类型的字段,一般都是不分词处理的,设置为keyword。
29、Elasticsearch中null值会被处理为NULL,这个值不会被索引无法进行搜索,可以通过null_value为其设置一个默认的值,以提供索引搜索能力。
30、当需要对节点做下线处理时,先通过排除节点的机制,让集群自动将节点上的分片进行迁移,可以通过以下命令表示移除node-1节点,命令执行以后分片开始迁移,通过_cat/shard API可以查看迁移进度。
curl --location --request PUT 'http://127.0.0.1:9200/_cluster/settings' \ --header 'Content-Type: application/json' \ --data-raw '{ "transient":{ "cluster.routing.allocation.exclude._name":"node-1" } }'
如果迁移完毕,节点重新上线,只需要重新将
cluster.routing.allocation.exclude._name设置为""即可。