ES的简单学习
基本概念
节点
- 主节点(Master),负责管理集群变更、元数据的更改
- 数据节点(Data Node),搜索、聚合、CURD 等
- 协调节点(Coordinating Node),负责接受客户端的请求,将请求路由到对应的节点进行处理
- 预处理节点(Ingest Node)
分片
- 主分片(primary shard)和副分片(replica shard)
- 分片数量是固定的
索引(Index)
- 一个索引由多个分片组成
- 一个分片由多个 segment 组成
- Mapping 定义了索引里的文档到底有哪些字段及这些字段的类型
- 向 ES 中写入的每一条数据都是一个文档(类似数据库中的一条记录)
- 搜索也是以文档为单位的
- 字段
- 词项(Term)
- 倒排索引与正排索引
查询
Term 是文本经过分词处理后得出来的词项,是 ES 中表达语义的最小单位
- Term Query,返回在指定字段中准确包含了检索内容的文档。
- Terms Query,跟 Term Query 类似,不过可以同时检索多个词项的功能。
- Range Query,范围查询。
- Exist Query,返回在指定字段上有值的文档,一般用于过滤没有值的文档。
- Prefix Query,返回在指定字段中包含指定前缀的文档。
- Wildcard Query,通配符查询。
组合查询
- Bool Query,布尔查询,可以组合多个过滤语句来过滤文档。
- must,查询的内容必须在匹配的文档中出现,并且会进行相关性算分。简单来说就是与 AND 等价。
- filter,查询的内容必须在匹配的文档中出现,但不像 must,filter 的相关性算分是会被忽略的。因为其子句会在 filter context 中执行,所以其相关性算分会被忽略,并且子句将被考虑用于缓存。简单来说就是与 AND 等价。
- should,查询的内容应该在匹配的文档中出现,可以指定最小匹配的数量。简单来说就是与 OR 等价。
- must_not,查询的内容不能在匹配的文档中出现。与 filter 一样其相关性算分也会被忽略。简单来说就是与 NOT 等价。
- Boosting Query,在 positive 块中指定匹配文档的语句,同时降低在 negative 块中也匹配的文档的得分,提供调整相关性算分的能力。
- constant_score Query,包装了一个过滤器查询,不进行算分。
- dis_max Query,返回匹配了一个或者多个查询语句的文档,但只将最佳匹配的评分作为相关性算分返回。
- function_score Query,支持使用函数来修改查询返回的分数。
- script_score:利用自定义脚本完全控制算分逻辑。
- weight:为每一个文档设置一个简单且不会被规范化的权重。
- random_score:为每个用户提供一个不同的随机算分,对结果进行排序。
- field_value_factor:使用文档字段的值来影响算分,例如将好评数量这个字段作为考虑因数。
- decay functions:衰减函数,以某个字段的值为标准,距离指定值越近,算分就越高。例如我想让书本价格越接近 10 元,算分越高排序越靠前。
Suggesters API
- Term Suggester:基于单词的纠错补全。
- Phrase Suggester:基于短语的纠错补全。
- Completion Suggester:自动补全单词,输入词语的前半部分,自动补全单词。
- Context Suggester:基于上下文的补全提示,可以实现上下文感知推荐。
ES 中聚合的类型主要有以下 3 种
- Metric Aggregations: 提供求 sum(求总和)、average(求平均数) 等数学运算,可以对字段进行统计分析。
- Bucket Aggregations:对满足特定条件的文档进行分组,例如将 A 出版社的书本分为一组,将 B 出版社的书本分为一组,类似于 SQL 里的 Group By 功能。
- Pipeline Aggregations:对其他聚合输出的结果进行再次聚合
原理
倒排索引
Lucene的结构
- 一个shared 包含多个 segment,而一个segment中包含了多个 document
- document中包含了多个filed,field 类似数据库表中的字段,filed有name 和类型
- 每个filed都有独立的索引,这个索引就是上面的倒排索引结构
term index
- 这里使用的是前缀的方式,压缩了空间
- 类似与前缀树,但用是 FST 算法来解决
- FST(Finite State Transducers)是一种 FSM(Finite State Machines,有限状态机)
FST
- 通过对 Term Dictionary 数据的前缀复用,压缩了存储空间
- 高效的查询性能,O(len(prefix))的复杂度
- 构建后不可变
- 如下:访问cat为 10 + 0 + 5 + 0 = 15
- 访问 dog:需要加上 g 的 Finalout:1 + 0 + 0 + 1 = 2
- 相当于:占用空间小且高效的 Key-Value 数据结构
- 快速试错,如果在 FST 上不存在,不需要再遍历整个 Term Dictionary;
- 快速定位到 Block 的位置,经过 FST 的输出,可以算出 Block 在文件中的位置
Posting List 的实现
Lucene 把这些数据分成 3 个文件进行存储
- .doc 文件,记录了文档 ID 信息和 Term 的词频,还额外记录了跳跃表的信息,用来加速文档 ID 的查询;并且还记录了 Term 在 pos 和 pay 文件中的位置,有助于进行快速读取
- .pay 文件,记录了 Payload 信息和 Term 在 doc 中的偏移信息
- .pos文件,记录了 Term 在 doc 中的位置信息
Posting List 主要面临着两个问题:
- 如何节省存储?
- 如何快速做交集?
编码格式
- PackedBlock,小于128bit固定长度
- VIntBlock
- 差值编码,delta方式存储
使用 PackedBlock 与 VIntBlock 来解析 .doc 文件
- Lucene 在处理文档的时候,每处理 128 篇文档就会将其对应的文档 ID 数组(docDeltaBuffer)和词频(TermFreq)数组(freqBuffer)
- 处理为两个 Block:PackedDocDeltaBlock 和 PackedFreqBlock
- 使用 PackedInts 类对数据进行压缩存储,生成一个 PackedBlock
- 不足 128 篇文档的数据采用 VIntBlock 来存储
- 在生成 PackedBlock 的时候,会生成跳表(SkipData),使得在读取数据时可以快速跳到指定的 PackedBlock
Roaring Bitmaps
- 用来求两个元素是否都匹配,求交集
- 高 16 位作为一个数值存储到有序数组里
- 低 16 位则存储到 2^16 的位图中去,将对应的位置设置为 1
- 高位使用二分查找,低为使用传统 bitmap 查询
- 如果某个块的 bit很少,直接换成普通数组
还是用了 跳表来 求交集
相关性评分
- TF-IDF (Term Frequency-inverse Document Frequency)
TF-IDF
- 一个词的重要程度跟它在一篇文章中出现的频率成正比
- 跟它在语料库中出现的频率成反比
- TF (Term Frequency On Per Document)表示一个词在一篇文档中出现的频率,值越大越重要
- DF(Document Frequency)表示词项在语料库中出现的频率,值越大就不重要
- IDF(Inverse Document Frequency)表示逆文档频率,值越高越稀有
- tf-idf 本质上是对 TF 进行了加权计算,而这个权重就是 IDF
java in linux” 这个查询,我们可以这样来计算每个匹配文档的得分
|
|
BM25 算分模型
分词器
- Character Filter:主要对原文本进行格式处理,如去除 html 标签等
- Tokenizer:按照指定的规则对文本进行切分,比如按空格来切分单词,同时也负责标记出每个单词的顺序、位置以及单词在原文本中开始和结束的偏移量
- Token Filter:对切分后的单词进行处理,如转换为小写、删除停用词、增加同义词、词干化等
ES 为用户提供了多个内置的分词器
- Standard Analyzer : 这个是默认的分词器,使用 Unicode 文本分割算法,将文本按单词切分并且转为小写
- Simple Analyzer : 按照非字母切分并且进行小写处理
- Stop Analyzer : 与 Simple Analyzer 类似,但增加了停用词过滤
- Whitespace Analyzer : 使用空格对文本进行切分,并不进行小写转换
- Patter n Analyzer : 使用正则表达式切分
- Keyword Analyzer : 不进行分词
- Language Analyzer : 提供了多种常见语言的分词器
- Customer Analyzer:自定义分词器
ES 中的数据大致可以分为两种
- 全文本,例如短信的内容、文章内容等;
- 精确值,如实体 ID、日期等
分析器的处理流程如下
- 先对字符串进行过滤,把一些 HTML、& 等字符处理掉;
- 分词器会将字符串按某些规律(空格、句号等)切分成单词,输出的这些单词为词条(token);
- 词条过滤器对切分后的词条进行过滤,例如过滤停用词(and、is 等),或者同义词转换等;
- 对过滤后的词条作进一步的处理,如小写转换、词根转换。
近实时搜索的原因:Refresh
- 数据先写到 buffer中,等到了阈值或者满了才刷盘
- 避免每次写入创建一个 segment
- 查询的时候最新写入的可能还没落盘,所以找不到
- Transaction Log 用来防止丢失,跟 WAL 类似
ES 提供了 3 种分页方式:
- from + size:最普通、简单的分页方式,但是会产生深分页的问题。
- search after:解决了深分页的问题,但只能一页一页地往下翻,不支持跳转到指定页数。
- scroll API:会创建数据快照,无法检索新写入的数据,适合对结果集进行遍历的时候使用
- Point In Time:PIT 是一个轻量级的数据状态视图,用户可以利用这个视图反复查询某个索引
Terms 聚合的结果不准的问题
- 数据进行了分片存储
- 每个分片的 top n 并不一定是全量数据的 top n
- 每个分片返回足够多的分组
PacificA
- 微软提出的,ES 使用它来做主和从副本之间的同步
- 系统框架主要分为两个部分
- 配置管理器使用单独一个集群来管理,其维护了副本组(Replica Group)信息
- 数据存储集群主要负责数据的读写、复制等操作
PacificA 的数据复制策略
ES 使用本地检查点和全局检查点来标记从副本与主副本的差异
- 两个检查点的值就是上述提到的 Sequence ID
- 全局检查点(GlobalCheckpoint),是所有活跃分片历史都已经对齐、持久化成功的序列号
- 小于全局检查点的操作都已经在所有副本上处理完了
- 本地检查点(LocalCheckpoint),代表着本副本中所有小于这个值的操作都已经处理完毕了
- 有了全局检查点后,系统就可以实现增量数据复制了
更改集群配置(Reconfiguration)
- Primary 故障
- Secondary 故障
- 加入新节点
监控优化
集群管理
- Cluster REST API
- CAT,简化显示的 REST API
集群规划
- 基本架构,在协调节点前加一个 LB 实现请求的负载均衡。
- 读写分离架构,基于基本架构衍生出来的架构,将读写请求分别负载到读写 LB 处理。
- 跨机房部署机构,GTM 分发流量,使客户端实现就近读取数据。多个数据中心的集群互备
索引管理
- 单个索引的创建、删除等
- 别名管理
- 索引设置
- mapping
- reindex
- 索引模版
- 收缩 index
ES 优化
- snaphost,备份,跨集群使用REST API来备份
- 索引生命周期管理,批量写入/更新,批量读取
- shared 分配和rebalance
- 慢查询日志,cpu/内存/磁盘/网络/负载/QPS,JVM 和 GC
- 使用 filter,_search_after而不是from+size
- 存储限流、调整刷新间隔,segment merge策略
- 监控指标:Indexing Rate、Latency、Refresh Rate和Time、Merge Time
- shared数量,大小,segment数量和大小,每个shared的文档count
相关算法和数据结构
算法
- 编辑距离(edit distance),用来做单词纠错、补全的
- 数据编码压缩算法
- TF-IDF、BM25评分模型
- 分词器
- PacificA 主副本同步策略
- raft 选主
数据结构
- 倒排索引
- Roaring Bitmaps
- skip list
- FST 前缀索引,前缀树
- transaction log