On this page
倒排索引(inverted index)也叫反向索引,有反向索引必有正向索引(forward index)。通俗地来讲,正向索引是通过 key 找 value,反向索引则是通过 value 找 key。
倒排索引主要由单词词典(Term Dictionary)和倒排列表(Posting List)及倒排文件(Inverted File)组成。
Term(单词):一段文本经过分析器分析以后就会输出一串单词,这一个一个的就叫做 Term(直译为:单词)
Term Dictionary(单词字典):顾名思义,它里 面维护的是 Term,可以理解为 Term 的集合
Term Index(单词索引):为了更快的找到某个单词,我们为单词建立索引
Posting List(倒排列表):倒排列表记录了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息,每条记录称为一个倒排项(Posting)。根据倒排列表,即可获知哪些文档包含某个单词。(PS:实际的倒排列表中并不只是存了文档ID这么简单,还有一些其它的信息,比如:词频(Term 出现的次数)、偏移量(offset)等)
Inverted File(倒排文件):所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件即被称之为倒排文件,是存储倒排索引的物理文件
在倒排索引中,通过 Term 索引可以找到 Term 在 Term Dictionary 中的位置,进而找到 Posting List,有了倒排列表就可以根据 ID 找到文档了
ES 的底层是基于 Lucene,Lucene 中提出了按段搜索的概念,将一个索引文件拆分为多个子文件,则每个子文件叫作段,每个段都是一个独立的可被搜索的数据集,并且段具有不变性,一旦索引的数据被写入硬盘,就不可再修改
优势
不需要锁,如果从来不更新索引,就不需要担心多进程同时修改数据的问题
一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性,只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘,这提供了很大的性能提升
其缓存(像filter缓存)在索引的生命周期内始终有效,它们不需要在每次数据改变时被重建,因为数据不会变化
写入单个大的倒排索引允许数据被压缩,减少磁盘 I/O 和 需要被缓存到内存的索引的使用量
为了提升写的性能,Lucene 并没有每新增一条数据就增加一个段,而是采用延迟写的策略,每当有新增的数据时,就将其先写入内存中,然后批量写入磁盘中。若有一个段被写到硬盘,就会生成一个提交点,提交点就是一个列出了所有已知段和记录所有提交后的段信息的文件。
索引文件中绝大部分数据都是只写一次,读多次,而只有用于保存文档删除信息的文件才会被多次更改。在某些时刻,当某种条件满足时,多个索引段会被拷贝合并到一个更大的索引段,而那些旧的索引段会被抛弃并从磁盘中删除,这个操作称为段合并(segment merging)。
优点
当多个索引段合并为一个的时候,会减少索引段的数量并提高搜索速度
同时也会减少索引的容量(文档数),因为在段合并时会移除被标记为已删除的那些文档
能合并大小相似的索引段,并考虑每层允许的索引段的最大个数。
在索引期,该合并策略会计算索引中允许出现的索引段个数,该数值称为阈值(budget)。如果正在构建的索引中的段数超过了阈值,该策略将先对索引段按容量降序排序(这里考虑了被标记为已删除的文档),然后再选择一个成本最低的合并。合并成本的计算方法倾向于回收更多删除文档和产生更小的索引段。
如果某次合并产生的索引段的大小大于 index.merge.policy.max_merged_segment 参数值,则该合并策略会选择更少的索引段参与合并,使得生成的索引段的大小小于阈值。这意味着,对于有多个分片的索引,默认的 index.merge.policy.max_merged_segment 则显得过小,会导致大量索引段的创建,从而降低查询速度。用户应该根据自己具体的数据量,观察索引段的状况,不断调整合并策略以满足应用需求。
会不断地以字节数的对数为计算单位,选择多个索引来合并创建新索引。
合并过程中,时不时会出现一些较大的索引段,然后又产生出一些小于合并因子(merge factor)的索引段,如此循环往复。
能够保持较少的索引段数量并且极小化段索引合并的代价。
与 log_byte_size 合并策略类似,不同的是以索引段的文档数为计算单位。
以下两种情况中该合并策略表现良好
文档集中的文档大小类似
期望参与合并的索引段在文档数方面相当
集群(cluster):一组拥有共同的 cluster name 的节点
节点(node):集群中的一个 ElasticSearch 实例
索引(index):ElasticSearch 实例存储数据的地方,相当于关系数据库中的 database 概念
分片(shard):索引可以被拆分为不同的部分进行存储,称为分片,在集群环境下,一个索引的不同分片可以拆分到不同的节点中
主分片(Primary shard):相对于副本分片的定义
副本分片(Replica shard):每个主分片可以有一个或者多个副本,数据和主分片一样
Master
node.master: true 节点可以作为主节点
DataNode
Coordinate node
协调节点,如果仅担任协调节点,将上两个配置设为 false
ES 的选主是 ZenDiscovery 模块负责的,主要包含 Ping 和 Unicast两部分。
对所有可以成为 master 的节点根据 nodeId 字典排序,每次选举每个节点都把自己所知道的节点排一次序,然后选出第一个节点,暂且认为它是 master 节点
如果对某个节点的投票数达到一定的值(discovery.zen.minimum_master_nodes)并且该节点自己也选举自己,那这个节点就是 master,否则重新选举一直到满足上述条件
master 节点的职责主要包括集群、节点和索引的管理,不负责文档级别的管理
一个集群中只有一个 A 主节点,A 主节点因为需要处理的东西太多或者网络过于繁忙,从而导致其他从节点 ping 不通 A 主节点,这样其他从节点就会认为 A 主节点不可用了,就会重新选出一个新的主节点 B。过了一会 A 主节点恢复正常了,这样就出现了两个主节点,导致一部分数据来源于 A 主节点,另外一部分数据来源于 B 主节点,出现数据不一致问题,这就是脑裂。
尽量避免脑裂,需要添加最小数量的主节点配置
discovery.zen.minimum_master_nodes: (N / 2) + 1
这个参数控制的是选举主节点时需要的最少的具有 master 资格的活节点数,N 为具有 master 资格的节点的数量
常用做法(中大规模集群)
角色分离
Master 和 dataNode 角色分开,限制角色,配置奇数个 master
单播发现机制
discovery.zen.ping.multicast.enabled: false 关闭多播发现机制,默认是关闭的
discovery.zen.ping.unicast.hosts: ["master1", "master2", "master3"] 配置单播发现的主节点 ip 地址,从节点要加入进来,就得去询问单播发现机制里面配置的主节点,主节点同意以后才能加入,然后主节点再通知集群中的其他节点有新节点加入
选主触发
discovery.zen.minimum_master_nodes: 2 选举主节点时需要的最少的具有 master 资格的活节点数
减少误判
discovery.zen.ping_timeout: 30(默认值是3秒) 其他节点 ping 主节点多长时间没有响应就认为主节点不可用了
每个分片本质上就是一个 Lucene 索引,因此会消耗相应的文件句柄,内存和 CPU 资源
每个搜索请求会调度到索引的每个分片中,如果分片分散在不同的节点问题不太,但当分片开始竞争相同的硬件资源时, 性能便会逐步下降
ES 使用词频统计来计算相关性,当然这些统计也会分配到各个分片上,如果在大量分片上只维护了很少的数据,则将导致最终的文档相关性较差
ES 推荐的最大 JVM 堆空间是 30 ~ 32G, 所以分片最大容量可限制为 30GB, 然后再对分片数量做合理估算
在开始阶段, 一个好的方案是根据节点数量按照 1.5 ~ 3 倍的原则来创建分片,当性能下降时,增加节点,ES 会平衡分片的放置
对于基于日期的索引需求, 并且对索引数据的搜索场景非常少,也许这些索引量将达到成百上千,但每个索引的数据量只有 1GB 甚至更小,对于这种类似场景, 建议只需要为索引分配 1 个分片