选型
因为数据库的like等操作会导致索引失效,进而导致查询性能低,功能弱,从而引入ElasticSearch通过倒排索引解决全文检索性能和功能的问题。
数据结构
倒排索引
一般包含两部分:单词词典(Term Dictionary)+倒排列表(Post list)
单词词典
一般用B+树,hashmap,字典树等实现,存储的是单词词条的集合和对应词条在列表位置地址的指针
倒排列表
存储的是包含该词条的对应文档id和词频等(用于相关度的评分)
索引更新
如果每新增删除修改一条索引数据便对生成的倒排索引进行更新的话会导致大量的索引重建,性能不高。所以ES使用了定时更新的策略,而平时的对数据的增删改操作会通过将数据id将入删除列表或者新建临时索引来解决。
原始索引:对原始文档数据进行解析生成的倒排索引,单词词典存在内存中,倒排列表存在磁盘上
临时索引(新建索引):添加新的文档数据之后生成的新的倒排索引,单词词典和倒排列表都在内存中
索引更新策略
因为ES不是实时对索引进行删除,所以原始索引和新建的临时索引都会存在失效的删除数据,所以需要在某个时间对其进行更新和重建。
完全重建策略
新文档和老文档合并,然后重新建一份新的倒排索引,再新索引建立完成之前,内存依然需要维护着老索引,满足用户的请求,比较适合文档数据小的索引集合,缺点是需要较长的时间且需另外开辟一块内存进行索引重建。
再合并策略
因为单词词典和倒排列表都是已经排好序的集合,则可通过归并算法(原始索引和新索引分别从低到高取出索引进行重新排序,被删除的数据索引直接丢弃即可)生成新的倒排索引。优点是速度快,因为是顺序读取,磁盘也是顺序读写,减少了磁盘寻道时间,缺点是需要开辟一个新的内存空间存储新的索引。
原地更新策略
直接将新建的临时索引插入到原始索引里面,优点是节省内存,缺点是因为原始索引的倒排列表是存在磁盘上的,且存放地址是连续的,则临时索引插入的话需要预留足够的空间,但是无法预知需要预留的具体空间是多少,预留过多造成空间浪费,少了导致无法插入。
混合策略
一般都是根据具体情况使用具体策略,当倒排索引的倒排列表较短的时候使用再合并策略,倒排列表过长的话使用原地更新策略。
集群
分片+副本
按照id的字典序选主,投票超过半数的其中一个候选主节点会被选举为master节点,负责分配分片和副本在各节点的情况。
注意:分片数确定之后不可修改,除非Reindex(一般用于数据迁移或者版本升级)。要增加吞吐量只能增加副本数。
路由计算
hash(主键)%分片数
写请求流程
请求访问的节点被称为协调节点,通过路由计算确定数据存储的位置之后协调节点将数据转发到对应节点,节点上主分片写入数据到OS Cache后才算完成写入,之后会向副本节点发送写入请求,副本写入完成(默认超过半数写入,可设置)则会向主分片节点发送确认信息,主分片节点再向协调节点发送确认信息。
索引数据先写入索引再写入translog日志,和mysql数据库相反(先写入日志再写入索引),应该是因为写入index逻辑复杂,可能会导致失败,所以如果先写入日志可能会导致日志有很多脏数据。
注意:只有索引数据写入到操作系统缓存(OS Cache)之后才可以查询到该数据,所以ES是近实时的(1s误差,可设置),OS Cache刷写到磁盘上的时间默认为30min,可设置。刷写到磁盘之后translog会被清除并申请一个新的内存空间作为translog,当然,translog满了(默认512M)也会触发刷写到磁盘的操作,默认是每5秒flush一次日志到磁盘,可设置。
读请求流程
单词词典->倒排列表->文档数据
近实时
默认1秒刷一次数据到OS Cache,只有到达OS Cache之后才能被搜索到。
安全性和可靠性
translog保证可靠性,默认每5秒刷盘一次,或者写入操作触发一次刷盘,只有刷盘成功之后才会返回确认请求,也可设置成异步模式。改为异步可能丢失5秒数据。
脑裂问题
设置节点数需要超过半数才能对外提供服务即可,7以后默认解决了该问题。
还可以设置心跳超时时间,防止误判。
主节点和数据节点分离,减轻主节点压力,防止其假死导致触发选举新主节点。
调整配置参数
- 给每个文档指定有序的具有压缩良好的序列模式ID,避免随机的UUID-4 这样的 ID,这样的ID压缩比很低,会明显拖慢 Lucene。
- 对于那些不需要聚合和排序的索引字段禁用Doc values。Doc Values是有序的基于document => field value的映射列表;
- 不需要做模糊检索的字段使用 keyword类型代替 text 类型,这样可以避免在建立索引前对这些文本进行分词。
- 如果你的搜索结果不需要近实时的准确度,考虑把每个索引的 index.refresh_interval 改到 30s 。如果你是在做大批量导入,导入期间你可以通过设置这个值为 -1 关掉刷新,还可以通过设置 index.number_of_replicas: 0关闭副本。别忘记在完工的时候重新开启它。
- 避免深度分页查询建议使用Scroll进行分页查询。普通分页查询时,会创建一个from + size的空优先队列,每个分片会返回from + size 条数据,默认只包含文档id和得分score给协调节点,如果有n个分片,则协调节点再对(from + size)× n 条数据进行二次排序,然后选择需要被取回的文档。当from很大时,排序过程会变得很沉重占用CPU资源严重。
- 减少映射字段,只提供需要检索,聚合或排序的字段。其他字段可存在其他存储设备上,例如Hbase,在ES中得到结果后再去Hbase查询这些字段。
- 创建索引和查询时指定路由routing值,这样可以精确到具体的分片查询,提升查询效率。路由的选择需要注意数据的分布均衡。
JVM调优
- 确保堆内存最小值( Xms )与最大值( Xmx )的大小是相同的,防止程序在运行时改变堆内存大小。
Elasticsearch 默认安装后设置的堆内存是 1 GB。可通过../config/jvm.option
文件进行配置,但是最好不要超过物理内存的50%和超过32GB。 - GC 默认采用CMS的方式,并发但是有STW的问题,可以考虑使用G1收集器。
- ES非常依赖文件系统缓存(Filesystem Cache),快速搜索。一般来说,应该至少确保物理上有一半的可用内存分配到文件系统缓存。