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 PositiveFalse Negative
错误False PositiveFalse 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

elasticsearch-analysis-hanlp

Search Template 查询模板化。 https://mustache.github.io/

索引别名
  - 别名是只读的。
  - 多个索引对应一个别名。
  - 一个索引对应多个别名。可以加过滤器。

function score query中,bm25分数一样,但是可以修改分数。可以优化默认算分。算分重排。

分片number_of_shards 副本number_of_replicas

只有一个master节点,多个master eligible节点。

脑裂:限定选举条件。

主分片设置为1,单个分片数据量太大,单机。

ES三节点,3分片1副本,关掉一台机器,集群变黄,过一会,集群会自动重新分配分片,然后变绿。

命令

  1. 查看mapping和settings:GET /la-adap-plan-tpl_v1

  2. GET /la-adap-plan-tpl_v1/_settings

  3. GET /la-adap-plan-tpl_v1/_mapping

  4. GET /la-adap-plan-tpl_v1/_count

  5. GET /_cat/indices

  6. GET /_cat/indices/kibana*?v&s=index

  7. GET /_cat/indices?v&health=green

  8. 查看文档数量:GET /_cat/indices?v&s=docs.count:desc

  9. GET /_cat/indices/kibana*?pri&v&h=health,index,pri,rep,docs,counts,mt

  10. 查看索引所占内存: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