62-ElasticSearch核心技术与实战-阮一鸣
https://github.com/geektime-geekbang/geektime-ELK
关键字
近实时,分布式存储/搜索/分析引擎
es节点类型:master,coordinate,data,hot,warm,ml
结构化,全文,地理位置,自动补全
同一文档并发更新
索引是文档的容器
每个节点都保存了集群的状态,只有master才能修改集群的状态。任意节点修改会导致数据不一致。 集群状态包括:1 所有节点信息 2 所有索引和mapping setting信息 3 分片的路由信息。
每个节点默认起到了协调节点的作用,接受Client请求,并请求发送到合适的节点,最终将结果汇聚。
主分片在索引创建时指定,后面不可修改,除非reindex。
一个分片是一个Lucene实例。
通过分片实现水平扩展和数据的可用性。
429:集群过于繁忙。
每个文档有一个版本号,用于并发控制避免冲突。
倒排索引实现:单词词典(Term Dictionary- B+ tree或哈希实现,记录单词到倒排列表关系)+倒排列表(Posting list- 记录单词到对应文档)
通过Analyzer进行分词:文本转成单词过程叫分词。Character Filter -> tokenizer -> token filter
ICU Analyzer
分页:from size
排序:sort
修改字段类型,必须reindex,重建索引。
多字段类型:一个字段既可以是keyword,也可以是text。
精确值:keyword,数字,日期,布尔 全文本:text(非结构化文本)
精确值不需要被分词,但是会为每个字段建立索引。精确值在索引时,不需要特殊分词处理。
Index Template,Dynamic Template
聚合类型:bucket(划分文档可以term或range),metric(min,max,sum,avg,cardinality),pipeline,matrix
term查询,对输入不分词,在倒排索引中查询准确的词项,并且使用相关度算分公式为词项的文档进行相关度算分。通过constant score将查询转换成giltering,避免算分,并利用缓存,提高性能。
term查询:term,range,exist,prefix,wildcard
将query转换成filter,忽略tf-idf计算,避免相关性算分开销。
全文查询:索引和搜索都进行分词。match,match_phrase,query string,必须要进行相关算分
结构化检索:精确匹配或者部分匹配,不需要打分判断相似性 。 结果:有或无。根据场景需要,来看结构化搜索是否需要打分。
相关性打分:文档和查询语句匹配度。打分本质是排序。settings.similarity
boosting:控制相关度的方法 boosting query
打分es5.0从TF-IDF升级为BM25
Query Context:相关性算分 Filter Context:不需要算分,利用缓存性能更好
bool查询:复合查询 must 必须匹配,贡献算分 should 选择性匹配,贡献算分 must_not filter context,查询字句,必须不能匹配 filter:必须匹配,不贡献算分。4种子句,2个影响算分,2个不影响。
单字符串多字段查询:bool,disjunction max query,multi match
多语言检索技巧:归一化词元,单词词根抽取,同义词,拼写错误
search template
index alias 实现零停机运维,创建不同的查询视图
查询后重新算分:function score query,用来控制算分
搜索建议 suggest as you type
term/phrase suggester
自动补全:completion suggester(基于FST和索引,而不是倒排索引)
基于上下文提示:context suggester
精准度:completion>phrase>term 召回率:term>phrase>competion 性能:completion>phrase>term
处理请求节点-协调节点:如果是创建/删除索引请求,需要路由到master。请求可以发送到任何节点。
master node决定把分片分发到data node上。
master node:创建,删除索引请求,决定分片分发到哪一个数据节点,维护并且更新集群状态,然后同步给其它节点。
如何避免脑裂:限制选举条件,设置仲裁quorum。quorum=master节点/2+1,从而避免脑裂问题。
副本分片出现导致每个节点都有完备的数据。不设置副本分片数,一旦节点关闭,就会导致数据丢失。
文档到分片的路由算法:shard=hash(_routing) % number_of_primary_shard _routing默认是文档ID。
近实时 1s后被搜到。倒排索引的不变性。不变性带来挑战:如果让一个新的文档可以被搜索,那么需要重建整个索引。
refresh:index buffer写入segment过程叫refresh。不执行fsync操作。每秒执行一次,查询后查找所有的segment,对结果汇总。refresh之后,文档可以被搜索了。清空index buffer,不会清空trans log。
index buffer,tran log。
flush:30分钟一次,调用refresh,index buffer清空,调用fsync缓存种segment写入磁盘,清空tran log,
tran log:segment写入磁盘耗时。refresh时候,segment先写入缓存,为了保证数据不丢失,index文档时候,既写index buffer,同时写trans log,默认落盘。
分布式搜索机制:两步 1.Query 2.Fetch 协调节点将请求发到数据节点,数据节点查询后将数据返回到协调节点,协调节点会把每个分片文档重新排序。
Query then fetch存在问题:
一 性能问题:1 每个分片查询文档from+size 协调节点需要处理number_of_shard * (from + size),存在深度分页问题。
二 相关性算分不准:主分片越多,相关性算分越不准。相关性算分在分片之间是独立的。
可以使用DFS Query then fetch:
到每个文档把各个分片词频和文档频率搜集,完整的进行一次相关性得分,耗费更多CPU和内存,性能较差。
排序是根据字段原始内容进行,倒排索引无法发挥作用,需要正排索引。通过文档ID和字段快速得到字段原始内容。
分词和相关性没有关系。结构化查询有分数,但是没有分词。
对Text类型进行排序:FieldData,搜索运行时创建。filed data只能对text进行排序。
排序实现:doc values(列式存储,不支持text)和 filed data(支持text排序)
关闭doc values:明确不需要排序和聚合分析。
from:990 size:10 协调节点会在每个分片获取1000个文档,协调节点聚合所有结果,最后排序选前1000个。页数越深,占用内存越多。es避免深度分页,设置了10000个文档。from+size < 10000
search after避免深度分页,不支持指定页数from,只能往下翻。实时获取下一页文档信息。类似于书签。如果size=10,查询990-1000,实现通过唯一排序值定位,将每次处理文档控制在10以内。每次使用上一个文档的sort值进行查询。
scroll 获取全部数据快照。
三种搜索类型:实时获取顶部部分文档 search after 需要全部文档,导出数据 scroll 分页 from size,深度分页,选择search after
Query(结构化) VS Search(非结构化)
Restful API VS Transport API
近实时分析,近实时搜索
Logstash Persistent queue
ELKB(Beats)
Instant Aggregation
Logstash ArcSight
基于词项和基于全文搜索
相关度算分公式
多字段mapping。多字段属性。
复合查询:Constant Score转为Filter
结构化搜索。
Boosting Relevance:Boosting Query
Context:Query Filter
解决结构化查询:包含而不是相等问题。
如何实现should not逻辑?
单字符串多字段查询:Disjunction Max Query:Tie Breaker
单字符串多字段查询:Multi Match
统计的机器学习算法:CRF算法,HMM算法,svm算法
HighLight
Search Template
Index Alias
Function Score Query
Suggester:4种 有拼写检查,自动补全(Finite-state transducer),上下文提示。
https://github.com/seperman/fast-autocomplete
相似性算分:Levenshtein Edit Distance
Precision 和 Recall
Coordination Node:处理请求的节点。 Data Node:保存数据的节点。
ES如何避免脑裂问题?
https://github.com/lmenezes/cerebro
Refresh和Flush
ES搜索过程。
Github代码搜索实现用ES。
近实时搜索和分析能力。
集群滚动升级。
查询评分,过滤不评分。评分计算相关度。
使用查询(query)语句来进行 全文搜索或者其它任何需要影响 相关性得分 的搜索。除此以外的情况都使用过滤(filters)。
boolean查询:must,must_not,should,filter。
constant_score查询取代只有filter的bool查询。
多字段:一个字段用于搜索(index=analyzed),一个字段用于排序(index=not_analyzed)。
TF/IDF (词频次数,文档频次数)
倒排索引用来检索(关键字找结果集),DocsValue(结果集找关键值的集合)用来字段排序,聚合。
分布式检索的过程:query then fetch。或者 dfs_query_then_fetch
“深分页” 很少符合人的行为。一般是爬虫或者机器人的行为。
bouncing results问题:两次查询,结果不一样。
ES字段可以自动推算。all字段7.0废除。版本用来解决文档冲突功能。_score 相关性分数。
索引设置Mapping和Settings。
ES:相关性,高性能全文索引, RDMS:事务,join。
节点类型:master-eligible, master, data, coordinating,hot/warm,machine learning node,tribe node。每个节点单一类型,master才能对集群数据,状态修复。
主分片不能修改,除非reindex,副本分片可以动态调整。一个索引三个主分片,一个副本分片。
修改POST,索引PUT,POST create会自动创建id,PUT必须指定。POST是修改,PUT的新增。
BULK一条失败,不会影响其他。
批量命令:BULK,MGET,MSEARCH。
倒排索引:索引页。关键字在书本的位置。正排索引:目录页。
倒排索引结构:term dictionary和posting list(倒排列表),记录位置,因为需要语句搜索。
phrase:语句
mapping中某些字段不做索引,缺点无法被搜索,优点节省空间。
Analysis:文本分析把全文转换成一系列term/token。通过Analyzer实现。分词器由三部分组成。Analyser原理是什么?Character Filter -> Tokenizer -> Token Filter
输入不用分词,用keyword analyzer。
中文分词器:ICU,IK,THULAC 清华
计算机学科:Information Retrieval
Precision=True Positive / (True Positive + False Positive),
Recall= True Positive / (True Positive + False Negative)
Ranking。
查询评估:
- 查到该查到 True Positive 真阳性
- 查到不该查到 False Positive 假阳性
- 没查到不该查到 True Negative 真阴性
- 没查到应该查到 False Negative 假阴性
表头 | 查到 | 没查到 |
---|---|---|
正确 | True Positive | False Negative |
错误 | False Positive | False Negative |
Dynamic Mapping 自动推断(自动识别)类型,索引不存在,自动创建。
Dynamic Mapping的Text字段会自动加keyword子字段。但是strict不会自动加keyword子字段。
多字段类型:特定字段特定分析器,指定不同的analyzer。
精确值keyword VS 全文text,精确值不需要被分词。text是文本类型,keyword关键字类型。
ES默认创建文档,“name”:"Alibaba",name会text并且keyword子字段,term查询”Alibaba“会查不到。term查询不会分词。要想查询到,需要用keyword查询。term查询也会算分,跳过算分,使用constant_score不算分了。
TF/IDF,BM25
条件组合,bool查询。对字段查询,bool查询两个算分,两个不算分。
- 单字符串多字段查询(Google,百度的查询模式)
- bool query(字段算分加和)
- disjunction(分离) max query。
- multi match query
分词,nlp,搜索引擎关系,nlp是自然语言处理技术,其中可以用来中文分词。
百度analysis-baidu-nlp:百度NLP中文分词插件。
HanLP作者的书《自然语言处理入门》 https://www.ituring.com.cn/book/2706
Search Template 查询模板化。 https://mustache.github.io/
索引别名
- 别名是只读的。
- 多个索引对应一个别名。
- 一个索引对应多个别名。可以加过滤器。
function score query中,bm25分数一样,但是可以修改分数。可以优化默认算分。算分重排。
分片number_of_shards 副本number_of_replicas
只有一个master节点,多个master eligible节点。
脑裂:限定选举条件。
主分片设置为1,单个分片数据量太大,单机。
ES三节点,3分片1副本,关掉一台机器,集群变黄,过一会,集群会自动重新分配分片,然后变绿。
命令
查看mapping和settings:GET /la-adap-plan-tpl_v1
GET /la-adap-plan-tpl_v1/_settings
GET /la-adap-plan-tpl_v1/_mapping
GET /la-adap-plan-tpl_v1/_count
GET /_cat/indices
GET /_cat/indices/kibana*?v&s=index
GET /_cat/indices?v&health=green
查看文档数量:GET /_cat/indices?v&s=docs.count:desc
GET /_cat/indices/kibana*?pri&v&h=health,index,pri,rep,docs,counts,mt
查看索引所占内存:GET /_cat/indices?v&h=i,tm&s=tm:desc
URI Search(Query String)
GET /la-adap-plan-tpl/_search?q=9&df=id
{
"profile": "true"
}
泛查询
GET /la-adap-plan-tpl/_search?q=9
{
"profile": "true"
}
GET /la-adap-plan-tpl/_search?q=id:9
{
"profile": "true"
}
Analyzer
GET /_analyze
{
"analyzer": "standard",
"text":"lili 111 222 你好北京"
}
POST /users/_analyze
{
"field": "name",
"text": [111]
}
POST /_analyze
{
"tokenizer": "standard",
"filter": ["lowercase"],
"text": ["Mastering Elasticsearch"]
}
自定义分词器
CharacterFilter 加工文本 Tokenizer:文本到词 Token Filters:词再次加工
爬虫过滤了html标签
POST _analyze
{
"char_filter": ["html_strip"],
"tokenizer": "keyword",
"text": ["<html>lili</html>"]
}
输入字符串替换
POST _analyze
{
"char_filter": [
{
"type": "mapping",
"mappings": [
"- => _"
]
}
],
"tokenizer": "standard",
"text": "123-456, I-test!"
}
搜索到任意一级目录
POST _analyze
{
"tokenizer": "path_hierarchy",
"text":"/user/a/b/c/d"
}
POST _analyze
{
"tokenizer": "whitespace",
"filter": ["stop"],
"text": ["The rain"]
}
POST _analyze
{
"tokenizer": "whitespace",
"filter": ["lowercase","stop"],
"text": ["The rain"]
}
Request Body查询
高阶方法只能在RequestBody里面做。
GET /la-adap-plan-tpl/_search
{
"_source": ["id","learningPlans"],
"query": {
"match_all": {}
}
}
match,match_phrase
GET /users/_search
{
"query": {
"match": {
"title": {
"query": "111",
"operator": "and"
}
}
}
}
GET /users/_search
{
"query": {
"match_phrase": {
"title": {
"query": "111",
"slop": 1
}
}
}
}
Query String Query
POST /users/_search
{
"query": {
"query_string": {
"default_field": "user",
"query": "mike OR lili2"
}
}
}
POST /users/_search
{
"query": {
"simple_query_string": {
"query": "lili OR mike",
"default_operator": "OR"
}
}
}
Dynamic Mapping
PUT mapping_test/_mapping
{
"dynamic":true
}
PUT mapping_test/_mapping
{
"dynamic":false
}
PUT mapping_test/_mapping
{
"dynamic":"strict"
}
自定义Mapping
NULL_VALUE
论文
Karen Sparck Jones: A statistical interpretation of term specificity and its application in retrieval
Stephen Robertson: The Probabilistic Relevance Framework: BM25 and Beyond