硬件方向

ES 的基础是 Lucene,所有的索引和文档数据都是存储在本地磁盘的。

  1. # path.data: /path/to/data
  2. # path.logs: /path/to/logs

磁盘在现代服务器上通常都是瓶颈。ES重度使用磁盘,你的磁盘处理吞吐量越大,你的节点就越稳定。

  • 使用SSD
  • 使用 RAID 0。条带化 RAID 会提高磁盘IO,代价显然就是当一块硬盘故障时,整个就故障了。不要使用镜像或者奇偶校验 RAID,因为副本已经提供了这个功能。
  • 使用多块硬盘,并允许 ES 通过多个 path data 目录配置,把数据条带化分配到它们上面。
  • 不要使用远程挂载的存储,比如 NFS 或者 SMB/CIFS 。这个引入的延迟对性能来说是完全背道而驰的。

分片策略

分片数需要衡量

  • 一个分片底层就是 Lucene 索引,会消耗一定的文件句柄、内存、以及CPU资源
  • 每一个搜索请求都需要命中索引中的每一个分片,如果每一个分片都处于不同的节点还好, 但如果多个分片都需要在同一个节点上竞争使用相同的资源就有些糟糕了。
  • 用于计算相关度的词项统计信息是基于分片的。如果有许多分片,每一个都只有很少的数据会导致很低的相关度。
  • 主分片,副本和节点最大数之间数量,我们分配的时候可以参考以下关系:
    节点数<=主分片数 *(副本数+1)

路由选择

公式:shard = hash(routing) % number_of_primary_shards
routing 默认是文档id,也可以自定义id。

不带 routing 查询

在查询的时候因为不知道要查询的数据具体在哪个分片上,所以整个过程分为 2 个步骤:

  1. 分发:请求到协调节点后,协调节点将查询请求分发到每个分片上
  2. 聚合:协调节点搜集到每个分片上的查询结果,在将查询的结果进行排序,之后返回结果

带 routing 查询

查询的时候,可以直接根据 routing 信息定位到某个分片然后进行查询,不需要查询所有的分片,经过协调节点排序。

写入速度

ES的默认配置,是综合了数据的可靠性、写入速度、搜索实时性等因素。实际使用时,需要根据业务特性,进行偏向优化。
针对搜索性能要求不高,但写入性能较高的场景,可以有以下优化:

  • 加大 Translog Flush, 目的是降低 Iops、Writeblock
  • 增加 Index Refresh 间隔,目的是减少 Segement Merge 的次数。
  • 调整 Bulk 线程池 和 队列。
  • 优化节点间的任务分布
  • 优化节点间的任务分布
  • 优化 Lucene 层的索引建立,目的是降低 CPU 和 IO

优化存储设备

合理使用合并

Lucene 以段的形式存储数据。当有新的数据写入索引时,Lucene 就会自动创建一个新的段。随着数据量的变化,段的数量会越来越多,消耗的多文件句柄和 CPU 就越多,查询效率就会下降。
由于 Lucene 段合并的计算量庞大,会消耗大量的 I/O,所以 ES 默认采用较保守的策略,让后台定期进行段合并。

减少 Refresh 的次数

Lucene 在新增数据时,采用了延迟写入的策略,默认情况下索引的 refresh_interval 为 1s.
Lucene 将待写入的数据先写入内存中,超过1s(默认)就会触发一次 Refresh,然后 Refresh 就会把内存中的数据刷到 OS的文件缓存系统中。
如果对搜索的实效性要求不高,可以将 Refresh 周期延长,例如 30s。
这样还可以有效减少段刷新次数,但这也同时意味着需要消耗更多的 Heap 内存。

加大 Flush 设置

Flush 的主要目的是把 OS文件缓存系统中的段持久化到硬盘中,当 Translog 的数据量达到 512MB 或者 30分钟,就会触发一次 Flush。
index.translog.flush_threshold_size 参数的默认值是 512MB,我们进行修改。
增加参数值意味着文件缓存系统中可能需要存储更多的数据,所以我们需要为 OS的文件缓存系统留下足够空间。

减少副本的数量

大批量写入,需要所有副本都同步,可以先将索引的副本设置为0,当数据写完后,再开启。

内存设置

ES安装后默认内存是 1G。
jvm.option 文件可设置 ES 的堆大小,Xms 堆初始化大小,Xmx表示可分配的最大内存,都是 1G
确保 Xmx 和 Xms 的大小是相同的,其目的是为了能够在 Java 垃圾回收机制清理完堆后,不需要重新分隔计算堆区的大小而浪费资源,可以减轻伸缩堆大小带来的压力。

ES 堆内存分配原则:

  • 不要超过物理内存的 50%:Lucene 的设计目的是把底层 OS 里的数据缓存到内存中。Lucene 的段是分别存储到单个文件的,这些文件都是不会变化的,所以很利于缓存,同时 OS 也会将这些文件缓存起来,以便更快的访问。如果设置 ES 的堆内存过大,Lucene 可用的内存将会减少,就会严重影响降低 Lucene 的全文本查询性能
  • 堆内存的大小最好不要超过 32GB:在 Java 中,所有对象都分配在堆上,然后有一个 Klass Pointer 指针指向它的类元数据。这个指针在 64 位的操作系统上为 64 位, 64 位的操作系统可以使用更多的内存(2^64)。在 32 位 32 位的操作系统的最大寻址空间为 4GB(2^32)。但是 64 位的指针意味着更大的浪费,因为你的指针本身大了。浪费内存不算,更糟糕的是,更大的指针在主内存和缓存器(例如 LLC, L1 等)之间移动数据的时候,会占用更多的带宽。

    一些参考资料

    ElasticSearch & Solor

    | 特征 | ElasticSearch | Solor | | —- | —- | —- | | 社区和开发者 | 单一商业实体及其员工 | Apache 软件基金会和社区支持 | | 节点发现 | Zen 内置于 ES,需要专用的主节点才能进行分裂脑保护 | Apache Zookeeper,在大量项目中成熟而且经过实战测试 | | 碎片放置 | 动态,可以根据集群状态按需移动分片 | 本质上是静态的,需要手工工作来迁移分片,从Solr 7开始,Autoscaling API 允许一些动态操作 | | 高速缓存 | 每段,更适合动态更改数据 | 全局,每个段更改无效 | | 分析引擎性能 | 结果的准确性取决于数据放置 | 非常适合精确计算的静态数据 | | 全文搜索功能 | 基于 Lucene 的语言分析,单一建议API实现,高亮显示重新计算 | 基于 Lucene 的语言分析,多建议,拼写检查,丰富的高亮显示支持 | | DevOps支持 | 丰富的 API | 尚未完全支持 | | 非平面数据处理 | 嵌套和对象类型的自然支持允许几乎无限的嵌套和父-子支持 | 嵌套文档和父-子支持 | | 查询DSL | JSON | JSON(有限)、XML(有限)或URL参数 | | 索引/收集领导控制 | 不可能 | 领导者安置控制和领导者重新平衡甚至可以节点上的负载 | | 机器学习 | 商业功能,专注于异常和异常值以及时间序列数据 | 内置-在流聚合之上,专注于逻辑回归和学习排名贡献模块 |