ES是什么
- 搜索引擎 search engine
- 近实时 (Near) Real Time Search
- RESTful API
- 分布式、高可用
- 面向文档存储,json格式
- 基于Apache Lucene
核心概念
- Cluster 集群
- Node 构成集群的单机节点
- Index 索引
- Shard 分片
- Replica 副本
- Segment 分段
- Document 文档
- Field 字段
- Inverted Index 倒排索引
- Text / Keyword 类型
使用全流程
schema(mapping)
es不需要前置的schema定义,在索引doc时确定schema
因为es数据的交互形式是json,所以doc可以开箱即用,在写入doc时如果没有预先定义的mapping,doc的每一个field会根据传过来的json数据确定类型,默认规则(dynamic field mapping)如下:
json类型 |
es类型 |
null |
不会增加field |
boolean |
boolean |
string |
date(通过Date detection)double/long(通过Numeric detection)text(带keyword的sub field) |
number |
float/long |
object |
Object |
array |
array(array的item类型取决于第一个非null元素的类型) |
同时es还支持定义模板 dynamic_template,来对默认的规则进行扩充、修改,例如下例就是修改了默认的string映射:
{
"mappings": {
"dynamic_templates": [
{
"strings_as_keywords": {
"match_mapping_type": "string",
"mapping": {
"type": "text"
}
}
}
]
}
不过如果没有动态字段的需求,个人不建议使用es的dynamic mapping,使用不当的话会污染mapping,所以可以指定 dynamic为false来关闭动态mapping。
当然可以使用put mapping api来预定义index的mapping结构,包括字段类型、使用的分析器(text类型)、是否索引等等。
es官方也非常推荐将相同的字段以不同的方式索引到es中,例如一个字符串类型的值可以使用索引成text类型来进行全文检索,也可以索引成keyword类型进行排序、聚合。
建议使用别名(alias),es对mapping的拓展是开放的,但对mapping的修改是禁止的。例如,可以为mapping增加一个字段,但是不能删除/修改字段。所以使用alias指向真正的index,这样,在有field需要修改的场景可以使用reindex api重建索引、再使用alias api更改指向,可以实现无缝的切换。
数据写入
分布式写入流程
可以看到,es写入的总延时等于写入主节点的时间+max(写入从节点的时间)。
Shard 写入流程
比较重要的三个概念
- refresh
- flush
- fsync todo:是否会丢数据
写入优化
- 使用bulk api批量操作
- 调整refresh_interval的间隔,es在每一次refresh时都会创建lucene的segment,并尝试进行segment的合并,开销较大,若对搜索的实时性要求不高,可以适当的调大refresh_interval的大小
- 不需要索引的字段指定index属性为not_analyzed
- SSD(经典性能不行,硬件来凑)
读取
Search
使用search api可以很方便的实现数据的检索。es提供了很多search api,例如match_query,term_query等等,可以很方便的组装query DSL(Domain Specific Language),且开发人员不需要考虑DSL中query的顺序,dsl中query的顺序不会影响最后的执行效率,真正的执行顺序会在CBO(Cost Based Optimizer)后进行重排。
- term index 使用FST(Finite State Machines) -> 定位倒排链
- SkipList -> 合并倒排链
Aggregation
- Metrics 将query命中的数据集进行count,max等操作,是一个单一的数值
- Bucket 将query命中的数据集再按条件分为更小的数据集合,再在这些小集合上执行Metrics,可以类比于sql中的group by
Sort
不要使用text字段作为排序字段,text字段一般用分析器进行分词,对text字段做排序往往得不到预期的结果
ES返回的doc默认会按照_score(文档相关性)降序排列,即算分后的分数值,如果指定了其他的Sort字段,就会按照指定的字段排序。同样支持script,来构造比较复杂的排序规则。
_score,评分的计算依赖于不同的query方式,例如对于fuzzy查询会计算与检索词拼写的相关程度,term查询会计算内容和关键词间的百分比等等。但是一般意义上我们说的全文搜索是指计算内容与关键词的相关程度,ElasticSearch的相似度算法使用 TF/IDF,即词频/逆文档频率,包括以下内容:
- 词频:检索词在该字段出现的频率。出现频率越高,相关性也越高。 字段中出现过5次要比只出现过1次的相关性高。
- 逆文档频率:每个检索词在索引中出现的频率。频率越高,相关性越低。 检索词出现在多数文档中会比出现在少数文档中的权重更低, 即检验一个检索词在文档中的普遍重要性。
- 字段长度准则 : 字段的长度是多少?长度越长,相关性越低。 检索词出现在一个短的 title 要比同样的词出现在一个长的 content 字段相关性更高。
Page
1.基于offset的分页
- from + size,from指定偏移量,size指定要取的数据条数
- 执行原理:
- client发起请求,收到请求的shard成为协调节点,负责后续请求数据的合并
- 执行query,取到from+size大小的结果集,协调节点本地构建构建大小为from+size的priority queue ,协调节点将请求分发到其他shard
- 其他shard 同样执行query,取到from+size大小的结果集,将集合返给协调节点
- 协调节点合并结果集,最后得到from+size大小的priority queue,将后size个数据返给client
- 特点: 支持随机分页访问 不适合用在feed无限下拉场景,在分页的边界可能存在数据重复的可能性 随着from的增大,开销也逐渐增大,不适合深度分页的场景
2.基于cursor的分页 scroll
- 针对这次请求将符合条件的所有结果汇集到协调节点缓存起来,后续直接从协调节点的缓存中取出
- 由于缓存的存在,文档后续的变更并不会同步到缓存中,并不适合实时请求
- 同样因为缓存的数据量为query命中的所有doc,所以scroll的堆内存开销是非常大的
- 适用于索引的重建/数据的迁移等非实时、低频的场景
3.search_after
- 使用上一页的检索结果来帮助检索下一页,排序字段里要包括doc的唯一标识,以保证search_after的一致性
- 每一个shard请求的数据集大小为size
- 不支持随机分页访问
- 实时处理,若doc有变更且影响到了排序因子,有可能出现重复数据
读取优化
- 没有范围查找需求的number类型字段,类型定义为keyword
- 慎用wildcard query,尽量使用分词后的结果使用match query,有使用wildcard query的需求,注意字符转义
- 搜索词的长度要做限制
- feed流场景使用search_after
- 不需要得分的字段用filter context替换query content,query和filter区别