【Elasticsearch】Elastic Stack


一、Elasticsearch 入门

1-1 术语介绍

Elasticsearch——Mysql结构对照表:

  • 文档 Document

用户存储在ES中的数据文档

个人理解:相当于数据库中的一行数据,有不同的字段。

  • 索引 Index

具有相同字段的文档列表组成

个人理解:由相同字段的文档组成

  • 类型 Type 注:ES6.x已经取消其概念,现在默认为doc

  • 节点 Node

一个Elasticsearch的运行实例,是集群的构成单元

个人理解:集群是由多个Node构成,每一个Node是Elasticsearch的运行实例

  • 集群 Cluster

由一个或多个节点组成,对外提供服务

1-2 Document介绍

Document在ES中实际就是一个Json Object

  • Json Object,由字段(Field)组成,常见数据类型如下

字符串:text、keyword

? - 区别 text是分词的,keyword是不分词的

? 使用:结构化搜索,全文文本搜索、聚合、排序等

数值型:long、integer、short、byte、double、float、half_float、scaled_float

? - ES提供了节省空间的数据类型,如上述:short、byte、half_float、scaled_float

? 整数类型:inyeger,long,short,byte 字段的长度越短,索引和搜索的效率越高

布尔:boolean

二进制:binary

范围类型:integer_range,float_range,long_range,double_range,date_range

  • 每个文档都有唯一的id标示

自行指定:我们可以指定字段中的主键为document_id

自动生成:不自动指定,es会自动生成唯一的document_id

  • document in es
{
  "name":"唐三",
  "sex":"男"
}

Document 在ES中由field name 和 field value组成

  • 在ES中每个Document都有一条MetaData

_index : 文档所在的索引名

_type : 文档所在的类型名ES6.x以后默认为doc

_id : 文档唯一id

_uid : 组合id,由 _type 和 _id 组成6.x _type不再起作用,现在 _uid 同 _id 数据是一样了

_source : 文档的原始Json数据,可以从这里获取每个字短的内容

_all : 整合所有字短内容到该字段,默认禁用现在已经不推荐使用了,针对所有字段进行分词,占磁盘空间且查询效率也不是很好

1-3 Index介绍

  • 索引中存储具有相同结构的文档(Document)

每个索引都有自己的 mapping 定义,用于定义字段名称和类型

  • 一个集群可以有多个索引,比如:

nginx 日志存储的时候可以按照每天生成一个索引来存储

nginx-log-2021-11-22

nginx-log-2021-11-23

nginx-log-2021-11-24

1-4 Rest API介绍

  • Elasticsearch 集群对外提供Restful API

Rest - 全称 :Representational State Transfer

? 翻译:表现层状态转移,表现层其实就是资源,简单理解就是你对这个资源操作[增加、删除等]状态会发生改变

URI : 指定资源,如Index、Document等

Http Method :指明资源操作类型,如 GET获取、POST修改或更新、PUT新建、DELETE 删除 资源等

  • Rest API 两种交互方式

Curl 命令行

curl -XPUT 'http://localhost:9200/employee/doc/1' -i -H "Content-Type:application/json" -d '
{
"usename":"rockybean",
"job":"software engineer"
}'

Kibana DevTools 【kibana 插件】,也可以用Postman,我比较喜欢使用postman

1-5 Index API

  • ES有专门的Index API,用于创建[PUT]、更新[POST]、删除[DELETE]索引配置等

request

创建索引

PUT   http://127.0.0.1:9200/test_index

#执行成功后会有 response
{
"acknowledged":"true",
"shards_acknowledged":"true",
"index":"test_index"
}

查看现有索引

GET   http://127.0.0.1:9200/_cat/indices?v

1-6 Document API

  • ES 有专门的 Document API

    • 创建文档 PUT
    PUT http://127.0.0.1:9200/test_idex/doc/1
    {
    "usename":"alfred",
    "age":1
    }
    

    这个主要是在创建索引后,添加一条一条数据,可以指定也可以不指定id

    • 查询文档 GET
    GET http://127.0.0.1:9200/test_index/doc/_search
    
    {
      "query": {
        "term": {
          "_id": "1"
        }
      }
    }
    

    根据id查询,也可以根据字段查询

    查询语句,json格式,放在http body 中发送到es

    • 更新文档 POST

    根据id更新

    • 删除文档 DELETE

补充:批量创建文档 _bulk

  • es允许一次创建多个文档,从而减少网络传输开销,提升写入速率
    • endpoint 为 _bulk
#批量插入 格式如下
{"index":{"_index":"test_index","_type:"doc","_id":"3"}}{"usename":"alfred","age":"10"}
或
{"create":{"_index":"test_index","_type:"doc","_id":"4"}}{"usename":"小明","age":"11"}
#批量删除 
{"delete":{"_index":"test_index","_type:"doc","_id":"3"}}
{"delete":{"_index":"test_index","_type:"doc","_id":"4"}}
#批量更新
{"update":{"_id":"2","_index":"test_index","_type":"doc"}}{"doc":{"age":"20"}}
{"update":{"_id":"3","_index":"test_index","_type":"doc"}}{"doc":{"age":"30"}}

二、Elasticsearch 倒排索引与分词

2-1 书的目录与索引

  • 书本的目录 用于查看章节查找

指定章节名称 指明页数

第一章 Mysql 架构与历史 1

现在有个需求,如何快速查找 “ACID” 关键词所在的页面?

  • 书的索引 用于关键词查找

指明关键词 指明页数

ab tool 51,89

ACID 6,551

这样就可以快速定位到要找的页码,然后定位到需要的内容

  • 书与搜索引擎

目录页对应正排索引

索引页对应倒排索引

在搜索引擎中

  • 正排索引
    • 文档id到文档内容,单词的关联关系
      • 这里的单词就是把文档内容做了分词后,每个单词的结果
  • 倒排索引
    • 单词到文档id的关联关系

2-2 正排与倒排索引简介

  • 正排索引
    • 文档 id 到文档内容、单词的关联关系

文档ID 文档内容

? 1 elasticsearch是最流行的搜索引擎

? 2 php是世界上最好的语言

? 3 搜索引擎是如何诞生的

正排索引做了分词生成倒排索引

  • 倒排索引
    • 单词到文档id的关联关系

单词 文档ID列表

elasticsearch 1

流行 1

搜索引擎 1,3

php 2

世界 2

最好 2

语言 2

如何 3

诞生 3

倒排索引-查询流程:正排索引和倒排索引结合完成一个查询的全过程

  • 查询包含 “搜索引擎”的文档
    • 通过倒排索引获得 “搜索引擎”对应的文档id有 1 和3
    • 通过正排索引查询1 和 3的完整内容
    • 返回用户最终结果

2-3 倒排索引详解

  • 倒排索引是搜索引擎的核心,主要包含两部分

    • 单词词典 (Term Dictionary)
    • 倒排列表 (Posting List)
  • 单词词典(Term Dictionary)是倒排索引的重要组成

记录所有文档的单词,一般都比较大

记录单词到倒排列表的关联信息

  • 单词字典的实现一半是用 B+Tree

    • 下图排序采用拼音实现

      倒排索引-倒排列表
  • 倒排列表(Posting List)

记录了单词对应的文档集合,由倒排索引项(Posting)组成

  • 倒排索引项(Posting)主要包含如下信息

文档id :用于获取原始信息

单词频率(TF,Term Frequency):记录guidance在文档中的出现次数,用于后续相关性算分

位置(Position):记录单词在文档中的分词位置(多个),用于做词语搜索(Phrase Query)

偏移(Offset):记录单词在文档的开始和结束位置,用于做高亮显示

  • 以搜索引擎为例

文档ID 文档内容

? 1 elasticsearch是最流行的搜索引擎

? 2 php是世界上最好的语言

? 3 搜索引擎是如何诞生的

Posting List

DocId TF Position Offset

1 1 2 <18,22>

3 1 0 <0,4>


例:查找 “搜索引擎” 相关类型

查询流程:首先根据搜索的词 搜索引擎 在 Term Dictionary 找到Posting List对应的信息【Docld、TF、Position、Offset】,根据倒排索引项去正排索引拿到原始的信息,再计算相关性算分,然后进行排序返回给用户

  • es存储的是一个json格式的文档,其中包含多个字段,每个字段会有自己的倒排索引
{
"usename":"rockybean",
"job":"software engineer"
}

username 倒排索引

job 倒排索引

2-4 分词介绍

  • 分词

分词是指将文本转换成一系列单词(term or token )的过程,也可以叫做文本分析,在es里面称为Analysis

例子:

文本

easticsearch是最流行的搜索引擎

分词结果

elasticsearch 流行 搜索引擎

  • 分词器是es中专门处理分词的组件,英文为Analyzer ,它的组成如下 分词器-调用顺序:先后如下

    • Character Filters

    针对原始文本进行处理,比如去除 html 特殊标记符,去除标签

    • Tokenizer

    将原始文本按照一定规则切分为单词

    • Token Filters

    针对tokenizer处理的单词就行再加工,比如转小写、删除或新增等处理

2-5 Analyze API

需要重视这个 API 如果查询的结果和你想的不一样,就可以使用这个api去排查这个结果

  • es提供了一个测试分词的api接口,方便验证分词效果,endpoint 是 _analyze

可以直接指定 analyzer进行测试

可以直接指定索引中的字段进行测试

可以自定义分词器进行测试

测试

POST _analyze
{
  "analyzer": "standard"
  , "text": "hello world!"
}

结果

{
  "tokens" : [
    {
      "token" : "hello",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "",
      "position" : 0
    },
    {
      "token" : "world",
      "start_offset" : 6,
      "end_offset" : 11,
      "type" : "",
      "position" : 1
    }
  ]
}
  • 自定义分词器analyzer
POST  _analyze
{
    "tokenizer":"standard",//tokenizer 指明使用哪个分词器
    "filter":["lowercase"],//转换成小写
    "text":"Hello World!"
}

结果

{
  "tokens" : [
    {
      "token" : "hello",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "",
      "position" : 0
    },
    {
      "token" : "world",
      "start_offset" : 6,
      "end_offset" : 11,
      "type" : "",
      "position" : 1
    }
  ]
}

2-6 自带分词器

预定义的分词器

  • Standard Analyzer

    • 默认分词器
      • 按照非字母切分
      • 小写处理
  • Simple Analyzer 【单引号都会被干掉】

    • 按照非字母切分
    • 小写处理
  • Whitespace Analyzer

    • 按照空格切分
  • Stop Analyzer

    • Stop Word 指语气助词等修饰性的词语,比如 the、an、的、这等等
    • 相比Simple Analyzer 多了Stop Word处理
  • Keyword Analyzer

    • 不分词,直接将输入作为一个单词输出
  • Pattern Analyzer

    • 通过正则表达式自定义分割符
    • 默认是 \W+ 即非字词的符号作为分割符
  • Language Analyzer

    • 提供了30+常见语言的分词

2-7 中文分词

  • 难点

中文分词指的是将一个汉字序列切分成一个一个单独的词。在英文中,单词之间是以空格作为自然分界符,汉语中词没有一个形式上的分界符

上下文不同,分词结果迥异,比如交叉歧义的问题,例:

  • 乒乓球拍/卖/完了
  • 乒乓球/拍卖/完了

常见分词系统

  • IK

    • 实现中英文单词切分,支持ik_smart、ik_maxword等模式
    • 可自定义词库,支持热更新分词词典
  • jieba

    • python中最流行的分词系统,支持分词和词性标注
    • 支持繁体分词、自定义词典、并行分词等
  • 基于自然语言处理的分词系统

    • Hanlp
      • 由一系列模型与算法组成的Java工具包,目标是普及自然语言处理在生产环境中的应用
    • THULAC
      • 由清华大学自然语言处理与社会人文计算实验室研制推出的yitao中文词法分析工具包,具有中文分词和词性标注功能

2-8 自定义分词器之 CharacterFilter

  • 当自带的分词无法满足需求时,可以自定义分词

    • 通过自定义Character Filters 、Tokenizer 和 Token Filter实现
  • Character Filters

    • 在Tokenizer 之前对原始文本进行处理,比如增加、删除、替换字符等
    • 自带如下
      • HTML Strip 去除 html 标签和转换 html实体
      • Mapping 进行字符替换操作
      • Pattern Repalce 进行正则匹配转换
    • 会影响后续tokenizer解析的postion 和 offset信息

例:

POST _analyze
{
  "tokenizer":"keyword",
  "char_filter":["html_strip"],
  "text":"

I'm so happy!

" }

输出结果:

{
  "tokens" : [
    {
      "token" : """
I'm so happy!
""",
      "start_offset" : 0,
      "end_offset" : 32,
      "type" : "word",
      "position" : 0
    }
  ]
}

2-9 自定义分词之 Tokenizer

  • Tokenizer
    • 将原始文本按照一定规则切分为单词(term or token)
    • 自带如下
      • Standard 按照单词进行分割
      • letter 按照非字符类进行分割
      • Whitespace 按照空格进行分割
      • UAX URL Email 按照 standard 分割,但不会分割邮箱和url
      • NGram 和 Edge NGram 连词分割
      • Path Hierarchy 按照文件路径进行分割

例:

POST _analyze
{
  "tokenizer":"path_hierarchy",
  "text":"/one/two/three"
}

输出

{
  "tokens" : [
    {
      "token" : "/one",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "/one/two",
      "start_offset" : 0,
      "end_offset" : 8,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "/one/two/three",
      "start_offset" : 0,
      "end_offset" : 14,
      "type" : "word",
      "position" : 0
    }
  ]
}

2-10 自定义分词之 TokenFilter

  • Token Filters
    • 对于tokenizer输出的单词(term)进行增加、删除、修改等操作
    • 自带的如下
      • lowercase 将所有term 转换为小写
      • stop 删除stop words
      • NGram 和Edge Ngram 连词分割
      • Synonym 添加近义词的term

POST _analyze
{
  "text":"a Hello,world!",
  "tokenizer":"standard",
  "filter":[
    "stop",
    "lowercase",
    {
      "type":"ngram",
      "min_gram":4,
      "max_gram":4
    }
    ]
}

输出

{
  "tokens" : [
    {
      "token" : "hell",
      "start_offset" : 2,
      "end_offset" : 7,
      "type" : "",
      "position" : 1
    },
    {
      "token" : "ello",
      "start_offset" : 2,
      "end_offset" : 7,
      "type" : "",
      "position" : 1
    },
    {
      "token" : "worl",
      "start_offset" : 8,
      "end_offset" : 13,
      "type" : "",
      "position" : 2
    },
    {
      "token" : "orld",
      "start_offset" : 8,
      "end_offset" : 13,
      "type" : "",
      "position" : 2
    }
  ]
}

2-11 自定义分词

  • 自定义分词的 api
    • 自定义分词需要在索引的配置中设定

分词配置,可以自定义 char_filter、tokenizer、filter、analyzer

PUT test_index  #索引名
{
  "settings":{
    "analysis":{
      "char_filter":{},
      "tokenizer":{},
      "filter":{},
      "analuzer":{}
    }
  }
}
  • 自定义分词器 Custom Analyzer
  • Character Filters

    • HTML Strip
  • Tokenizer

    • Standard
  • Token Filters

    • Lower case
    • ASSCII Folding

自定义分词器一

PUT test_index_1
{
  "settings":{
    "analysis":{
      "analyzer": {
        "my_custom_analyzer":{
        "type":"custom",
        "tokenizer":"standard",
        "char_filter":[
          "html_strip"
          ],
          "filter":[
            "lowercase",
            "asciifolding"
            ]
      }
      }
      
    }
    
  }
}

结果

{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "test_index_1"
}

自定义分词验证

POST test_index_1/_analyze
{
  "analyzer": "my_custom_analyzer",
  "text": "Is this a box ?"
}

结果

{
  "tokens" : [
    {
      "token" : "is",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "",
      "position" : 0
    },
    {
      "token" : "this",
      "start_offset" : 3,
      "end_offset" : 7,
      "type" : "",
      "position" : 1
    },
    {
      "token" : "a",
      "start_offset" : 11,
      "end_offset" : 12,
      "type" : "",
      "position" : 2
    },
    {
      "token" : "box",
      "start_offset" : 13,
      "end_offset" : 16,
      "type" : "",
      "position" : 3
    }
  ]
}

2-12 分词使用说明

  • 分词会在如下两个时机使用

    • 创建或更新文档时(Index Time),会对相应的文档进行分词处理
    • 查询时(Search Time),会对查询语句进行分词
  • 索引时分词是通过配置Index Mapping 中每个字段的analyzer 属性实现的

    • 不指定分词时,使用默认standard
PUT test_index
{
  "mappings":{
    "doc":{
      "properties":{
        "title":{
          "type":"text",
          "analyzer":"whitespace"
        }
      }
    }
  }
}