得益于 Elasticsearch 对 Lucene 恰到好处的功能增强和丰富的文档,业务搜索/检索场景使用 Elasticsearch(ES) 越来越普遍。本文主要对自己在业务搜索场景中对 ES 的使用实践做一些总结,ES 一些基础如倒排索引、index 基础概念和 kibana dev tools 工具等不在本文讨论范围内。

index alias 使用优化

ES 原生提供了 index 这种和 MySQL 中 table 对等的概念,随着业务和需求的变化,对原 index 部分字段类型的增改在所难免。得益于 ES 的列式存储特性,新增字段是非常容易的,但是要修改则不可以,除非新建索引。那么问题来了,由于需求等多方因素影响,确实需要新建索引达到修改原索引字段类型的目的,这时业务就需要将读写流量切换到新索引了。

我们顺藤摸瓜再进一步分析可以知道,在这个流量切换过程中,如何在保证数据一致性的前提下避免线上业务故障呢?为了解决这类问题,ES 官方提供了最佳实践之 零宕机修改 mapping, 核心是 ES 提供了一种和视图类似的「alias(别名)」机制,并且在切换 alias 指向实际的索引时可以保证原子性。indices aliases 阐述了如何具体操作。这里需要注意的一个细节是如果使用 ES 提供的 reindex 方法,在超大索引重建时,新数据仍然在继续写入,那么这个时候盲目切换索引是会丢数据的,比较精确的方式是在写入 ES 时双写,全部重建后再切换 alias, 这种双写场景需要良好的工具支持,后续我再专文说明。

  1. 新建索引时,同时建立对应的 alias, 索引名可以在 alias 后面加入 -v1 标记。对业务使用 index 时可以对外仅暴露不变的 alias, 避免业务代码改动,屏蔽底层索引优化切换细节。
  2. 日志类数据考虑按天建立索引,使用 索引模板/indices templates 可以让日志类数据按天自动创建索引,减轻人工维护负担。
  3. 多个物理索引并且需要通过 alias 写入的场景,可以考虑使用 Write Alias 在多个索引中指定一个为 write_alias, alias type 的需求细节可以参考 Alias types issue

分片数

对于小的业务,一个主分片一个副本分片就够了,然后根据节点数提高副本分片从而提升读性能。对于大的业务量,一般来说总的分片数和集群所有节点数保持一致或者整数倍为佳,压力均摊。关于分片数设置,ES 官方博客的 分片数设置 也有说明,根据你节点的磁盘特点,机械/SSD, 业务特点,业务检索/日志类 略有不同,业务检索类可以适当控制分片体积,过大过小都不好,最佳的方案是自己做业务压测,满足要求为止。

分片自动均衡

对于变动不大的业务场景,可以考虑关闭分片自动均衡,防止在节点增删时触发分片自适应调整,占用集群资源进而影响线上业务。可以参考 shards allocation 了解更多细节。

mapping schema 优化

设置 mapping 时,选择合适的类型是十分重要的,好马配好鞍,好的算法配合适当的数据结构才能绽放出最耀眼的光芒。

  1. 线上业务明确类型,mapping 字段类型自己维护,设置 "dynamic": "false". 不要图方便让 ES 自动建类型,后期性能啥的是个大坑。
  2. 不要再自作聪明搞 type 了,属于滥用,已经明确被抛弃了
  3. 对于明确不需要倒排索引的就禁用好了,"enabled": false, 细节参考 ES enabled
  4. 枚举类型字段使用 keyword, 哪怕本身是 integer, 量大的时候对 term query 能有好几倍的延时优化. 最近的 ES 采用了 Lucene 新版本中对 Number 的底层优化,使用了更适合排序而不是原始的倒排索引。更极致的优化是根据不同的枚举值建立不同的索引,这个要看业务场景。想了解 keyword 优化细节的同学可以参考 ES keyword 优化
  5. 需要同时支持好几种不同应用场景的,如排序和倒排场景,考虑使用 subfields

nested query 优化

能避免就避免,是性能杀手。如果一定要用,其实可以考虑对部分场景如某个字段枚举值不多,那么直接以值作为字段名的一部分,进而化解原本 nested query 才能满足的查询场景。

其他

  1. 慢查询监控,对部分延时较大的语句针对性优化
  2. 上线预热,部分索引切换场景时考虑先预热再进行线上服务,减少线上超时
  3. 能用 filter 就用 filter, 可以充分利用缓存
  4. 尽量少用 script 这些
  5. 预处理生成索引代替实时计算
  6. 遇到性能问题时可以到 profile 看看每一个过程的耗时

其他的想到再补吧,index, mapping 常用的差不多就这些了