ElasticSearch-Note-2
- 核心概念:
- NRT 近实时,从写入数据到可以被搜索到有一个小延迟,基于es执行搜索和分析可以达到秒级
1. 集群健康检查
GET /_cat?health?v
-
status
- green 每个索引的primaty shard 和replica shard 都是active状态
- yellow 每个索引的primary shard 是active状态,replica shard不是active状态,处于不可用状态
-
默认配置是给每个index分配5个primary shard和5个replica shard
-
快速查看集群中有哪些索引
GET /_CAT?indices?v
2. 多种搜索方式
-
query string search
GET /index/type/_search
GET /index/type/_search?q=name:value&sort=price:desc
- took 耗费毫秒
- _shards:数据拆成了五个分片,搜索请求会达到所有的primary Shard 或replica Shard
- max_score:
- query string search无法完成复杂条件的搜索
- query DSL(Domain Specified Languages)
- query filter
{
"query":{
"bool":{
"must":{
"match":{
"field":"value"
}
},
"filter":{
"range":{
"price":{
"gt":25
}
}
}
}
}
}
-
full-text search
- 全文检索
{
"query":{
"match":{
"producer":"yagao producer"
}
}
}
- 对某个字段进行拆解,建立倒排索引
//将存储的数据进行拆解
special 4
yagao 4
producer 1,2,3,4
gaolujie 1
zhonghua 3
yagao producer ====》 yagao 和 producer
//再去匹配,根据分数排序
-
phrase search
- 全文检索会将输入的搜索串解开,去倒排索引中一一匹配,只要匹配任一个拆解后的单词,就可以作为结果返回
- phrase search要求输入的搜索串必须在指定的字段文本中包含一模一样的才可以作为结果返回
-
highlight search
3. 嵌套聚合、下钻分析、聚合分析
- 聚合
#terms是否有其他字段
{
#不返回数据
"size":0,
"query":{
"group_by_tags":{
"terms":{
"field":"tags"
}
}
}
}
{
"aggregations":{
"group_by_tags":{
"buckets":[
{
"key":"防蛀牙",
"doc_count":2
#每个分组的数量
}
]
}
}
}
- 先分组,再算每组平均值,并降序
{
"size":0,
"aggs":{
"group_by_tags":{
"terms":{
"field":"tags",
"order":{
"avg_price":"desc"
}
},
"aggs":{
"avg_price":{
"avg":{
"field":"price"
}
}
}
}
}
}
- 按照指定的价格范围区间进行分组,在组内再按照tag分组,在计算每组的平均价格
{
"size":0,
"aggs":{
"group_by_price":{
"range":{
"field":"price",
"ranges":[
{"from":0,"to":20},
{"from":20,"to":40}
]
},
"aggs":{
"group_by_tags":{
"terms":{
"field":"tags"
},
"aggs":{
"average_price":{
"avg":{
"field":"price"
}
}
}
}
}
}
}
}
4. ElasticSearch基础分布式架构
4.1 分布式机制透明隐藏特性
- 分片机制
- 集群发现机制
- shard负载均衡
4.2 扩容方案
- 垂直扩容
- 水平扩容
4.3 rebalance
4.4 master节点
- 管理es集群的元数据,负责索引、创建删除。一般master不承载所有请求
4.5 节点对等分布式架构
- 每个节点都能接收请求
- 自动请求路由【如果找不到会帮忙转发请求,找到可以处理的节点】
- 节点接收到请求会自动去其他服务器收集数据
4.6 Shard知识点
- shard都是一个最小工作单元
- 增减节点时,shard会自动在node负载均衡
- 每个document肯定只存在于某一个primary shard
- replica shard负责容错,以及承担读请求负载
- primary shard在创建索引的时候就固定了,replica shard可以随时修改
- 默认数量:primary:5,replica:1,默认一共10个shard
{
"settings":{
"number_of_shards":3,
"number_of_replicas":1
}
}
4.7 单node环境中创建index
4.8 两个node中replica shard是如何分配的
4.9 横向扩容过程,如何超出扩容极限以及如何提升容错性
4.9.1 超出扩容极限
- 六个shard 想用九台服务器提高性能
- 通过增加replica shard
4.9.1 提升容错性
- 6个shard,三台服务器,可以容忍一台宕机
- 9个shard,三台服务器,可以挂两台
4.10 master选举、replica容错、数据恢复
- 只要有一个primary shard挂了,则集群状态为red
- 启动master选举,选择一个replica shard 作为primary
- 宕机服务器重启后作为replica shard ,同步数据
5. 分布式文档系统
5.1 document核心元数据
-
_index
-
_type
-
_id
4. 手动生成id:路径后面带id
5. 自动生成id:- 长度20个字符
- URL安全,base64编码
- 采用GUID算法方式进行生成,保证分布式系统并行生成时不会发生冲突
-
_source
- GET请求返回的文档数据存放在source中
- 可以自定义返回指定字段
-
_document
- document的全量替换
- 若id存在,则全量替换文档内容
- 原文档不会立即删除
- 重新建立索引
- document的强制创建
put /index/type/id?op_type=create
put /index/type/id/_create
- document删除
- delete /index/type/id
- 不会立即物理删除,只标记为deleted,当数据越来越多时,在后台自动删除
- document的全量替换
5.2 并发冲突问题
- es默认使用乐观锁
- 悲观锁
- 方便、直接加锁,对应用程序来说透明,不需要额外的操作。
- 缺点:并发能力低,同一时间都只能有一条线程操作数据
- 乐观锁
- 并发能力高,不给数据加锁,大量线程并发操作。
- 缺点:麻烦,每次数据更新时,都要先比对版本号,然后可能需要重新加载数据,再次修改再写
- ES使用乐观锁核心原理:
- 每次修改或删除都会对_version进行加一
- 由于replica同步的过程是采用多线程异步操作,会出现先修改的数据后到,后修改的数据先到的情况
- 于是在同步的时候会先对比一下版本号,再进行修改
- 用外部的版本号进行并发控制
PUT /test_index/test_type/8?version=3&version_type=external
5.3 数据修改partial update内部原理
POST /INDEX/type/id?_update
{
"doc":{
"field":"value"
}
}
- partial update相较于全量替换的优点
- 所有的查询、修改、写回操作都发生在es的一个shard内部,比u面所有的网络数据传输的开销,相对于全量替换减少2次网络请求,大大提升的了性能
- 减少了查询和修改中的时间间隔,可以有效减少并发冲突的情况
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MjWCZ8CP-1571677387066)(F:\工作文档\个人技术框架笔记\ElasticSearch\images\partialUpdate.png)]
- 原始全量替换的方式:查询 - 展示 - 用户修改 - 再写回去,并发冲突的情况就会发生的比较多
5.3.1 使用groovy实现partial update
- 可以基于groovy脚本实现各种各样的复杂操作
- 内置脚本
POST /test_index/test_type/11/_update
{
"script":"ctx._source.num+=1"
}
- 外部脚本
#在es的config的script中创建groovy文件 ctx._source.tags+=new_tags
POST /index/type/11/_update
{
"script":{
"lang":"groovy",
"file":"test-add-grooy",
"params":{
"new_tag":"tag1"
}
}
}
- 通过脚本删除
#test-delete-groovy: ctx.op = ctx._source.num ==count? 'delete':'none'
#POST /index/type/11/_update
{
"script":{
"lang":"groovy",
"file":"test-delete-grooy",
"params":{
"count":1
}
}
}
-
upsert操作
- 在已经删除的情况下执行update会出现404错误
- 通过upsert来先初始化,再更新数据
#POST /index/type/11/_update
{
"script":"ctx._source.num+=1",
"upsert":{
"num":0,
"tags":[]
}
}
5.3.2 partial update内置乐观锁并发控制
-
当读到版本号为1,准备写入过程中被其他线程提前修改了,此时partial update会失败
-
可通过retry_on_confilct
-
retry_on_confilct
post /index/type/id/_update?retry_on_conflict=5&version=6
- 再次获取数据和版本号
- 基于更新的数据再次写入
- 可设置重复获取次数
5.4 批量查询
- 减少网络请求的性能开销
#GET /_mget
{
"docs":[
{
"_index":"index",
"_type":"type",
"_id":2
}
]
}
5.5 批量增删改
#POST /_bulk
{"create":{"_index":"index","_type":"type","_id":"id"}}
#附带新增的数据
{"field":"greate"}
#附带修改的数据
{"doc":{"field":"greate"}}
- delete
- create 强制创建
- update
- index 普通的put
5.6 深度剖析document数据路由原理
-
路由算法: shard = hash(routing) % number_of_primary_shards
- routing值是document id,传入hash函数中,产生一个数字
- 求余数
-
可以手动指定routing的值
put /index/type/id?routing=user_id
- 将document放在某一个shard中
- 优点:
- 有助于应用级别的负载均衡
- 提升批量读取的性能
-
primary shard不可变的谜底
- 如果变了,会找不到其他数据
5.7 深度剖析document增删改查内部原理
- 增删改只能在primary shard上进行操作
- 请求到到 协调节点,由协调节点负责转发
- primary shard插入数据,并同步数据后才返回给客户端
5.8 在进行写操作的时候,加一个参数 consistency
-
在进行写操作的时候,加一个参数 consistency
put /index/type/id?consistency=quorum
-
可选的值
- one: 要求写操作,只要有一个primary shard 是active活跃可用的,就可执行
- all:要求写操作,必须所有的primary shard和replica shard都是活跃的,才可执行
- quirum:默认值,要求所有的shard中,必须是大部分的shard都是活跃的,可用的
-
quorum的大部分计算公式
- int((primaty+number_of_replicas)/2) +1
- 3个primary shard,number_of_replicas =1 3+3*1=6
- quorum = int((3+1)/2)+1 = 3
- 只有当number_of_replicas的数量大于1的时候才会生效
- 当quorum不齐全时,会等待,默认一分钟。或者手动指定等待时间 timeout=30
5.9 随机轮询算法:round-robin
- 随机轮询算法:round-robin
- 将访问同个文档的请求均匀分发的primary或replica
- 当分发给一个replica shard上时,而数据还在primary shard中建立索引中,则会找不到该文档
6. 初识搜索引擎
6.1 search结果&timeout机制
6.2 multi_index & multi_type
6.3 分页 & deepPaging
6.3.1 情景描述:
总共有60000条数据,每个shard上有20000条数据,每页10条数据。
6.3.2 搜索过程:
- 将请求发送到一个协调节点上
- 由协调节点将请求转发到index的三个shard所在的node上
- 此时若要搜索10001~10010条数据
- 三个shard每个shard需要都返回10010条数据给协调节点,而不是返回第10001~10010条数据
- 意味着coordinate节点将收到30030条数据
- 再对这些数据进行排序,_score,相关分数,然后取最高的10条数据
6.3.3 缺点
- 深度搜索时,就需要再协调节点中保存大量数据,还要进行大量数据的排序
- 这个过程耗费网络带宽、耗费内存,耗费cpu
6.4 mapping
- 可指定字段类型
- 每个字段使用不同的分词器
- 指定是否可聚合
6.5 精确搜索&全文搜索
- 全文搜索
- 缩写、全称
- 格式转化,单词形式
- 大小写
- 同义词
6.6 如何定位不合法搜索及原因
6.6.1 示例
#GET /INDEX/TYPE/_validate/query?explain
{
"query":{
"match":{
}
}
}
6.7 定制搜索结果的排序规则
- sort 关键字
6.7.1 将一个field索引两次来解决字符串排序问题
- 排序的时候会对string类型进行分词后再排序
- 关键:
- 建立二次索引
- 使用fielddata
- 结果:
- 分词使用整个字符串进行排序
6.7.1.1建立二次索引映射
#PUT /index
{
"mapping":{
"article":{
"properties":{
"title":{
"type":"text",
"fields":{
"raw":{
"type":"string",
"index":"not_analyzed"
}
},
"fielddata":true
},
"content":{
"type":"text"
}
}
}
}
}
6.7.1.2查询时候使用field.raw
#GET /index/type/_search
{
"query":{
"match_all":{
}
},
"sort":{
"title.raw":{
"order":"desc"
}
}
}
6.8 相关度评分TF&IDF算法
6.8.1 TF/IDF评分算法
-
对文档进行相关度评分计算
-
分数是如何被影响的?
-
TF&IDF:Term Frequency/inverse document frequency
- Term frequency:搜索文本中的各个词条再field文本中出现的次数,次数越多,越相关
- Inverse document frequency:搜索文本的各个词条在整个索引的所有文档中出现了多少次,出现次数越多,越不相关
搜索请求: hello world
doc1: hello,today is very good
doc2: hi world,how are you
- 一万条document中,hello这个单词在所有document中出现了1000次,world这个单词在所有document中出现了100次
- 则doc2 更相关
- Field-length norm:field长度,field越长,相关度越弱
搜索请求: hello world
doc1:{“title":"hello article","content":"babbababa"}
doc2:{“title":"my article","content":"babbababa,hi,world"}
- 假设前提:hello、world在整个index出现的次数一样多
- doc1更相关,因为关键词所在的域的文本内容更短
6.8.2 _score是如何被计算出来的
get /index/type/_search/explain
6.8.3 分析一个document是如何被匹配上的
get /index/type/1/_explain
6.9 doc value探究
6.9.1 正排索引
- 搜索的时候,依靠倒排索引
- 排序的时候依靠正排索引,即doc values
- 建立索引时:一方面建立倒排索引,一方面建立正排索引供排序、聚合、过滤等操作使用
- doc values是被保存在磁盘上的
6.9.2 正排与倒排
- 倒排
doc1: hello world you and me
doc2: hi, world, how are you
word doc1 doc2
hello *
world * *
you * *
and *
me *
hi *
how *
hello you --> hello,you
hello--> doc1
you --> doc1,doc2
- 正排
doc1: { "name": "jack", "age": 27 }
doc2: { "name": "tom", "age": 30 }
document name age
doc1 jack 27
doc2 tom 30
6.10 Query Pharse
6.10.1 搜索请求处理过程
- 请求发送到某一个coordinate
- 在协调节点上建立priority queue队列,大小为from+size长度的队列
- 将请求进行转发
- 每个shard上也会建立priority queue,并返回给协调节点
- 协调节点将所有的priority queue进行merge,全局排序后的queue放到自己的queue中
- 此时协调节点可以将自己的priority queue中的数据取出当前那一页的数据
6.10.2 replica shard如何提升搜索吞吐量
6.11 fetch Phrase
6.11.1 fetch Phrase工作流程
- 协调节点构建完priority queue之后,就发送mget请求取所有shard上获取对应的document
- 各个shard将document返回给协调节点
- 协调节点将合并后的document返回给客户端
- 一般搜索不加from,size默认搜索前10条,按照score排序
6.12 搜索相关参数梳理及bouncing results问题
6.12.1 参数梳理
- timeout: 在指定时间内返回结果,避免查询耗时过长
- routing:document文档路由,_id,routing=user_id 可以让同一个user对应的数据到一个shard上去
- search_type:
- 默认: query_then_fetch
- dfs_query_then_fetch 可以提升revelance sort精准度
6.12.2 bouncing results问题
- 两个document排序,field值相同,在不同的shard可能排序不同;每次看到的搜索结果的排序都不一样
6.12.2.1 解决方法
- 将preference设置为一个制度穿,比如user_id,让每个user每次搜索的时候都是用同一个replica shard去执行
6.13 scoll技术滚动搜索
- 第一次请求
#GET /index/type/_search?scroll=1m
{
"query":{
"match_all":{}
},
"sort":{"_doc"},
"size":3
}
- 滚动请求
#GET /_search?scroll
{
"scroll":"1m",
"scroll_id":""
}
7. 索引管理
7.1 索引增删改
7.1.1 创建
#PUT /INDEX
{
"settings":{},
"mappings":{}
}
7.1.2 修改
PUT /index/_settings
{
"number_of_replicas":1
}
7.2 修改分词器及定制分词器
7.2.1 默认分词器
- standard
- standard tokenizer:以单词边界进行切分
- standard token filter:什么都不做
- lowercase token filter:将所有字母转换为小写
- stop token filer(默认被禁用):移除停用词,比如a the it等等
7.2.2 修改分词器的设置
#PUT /my_index
{
"settings": {
"analysis": {
"analyzer": {
"es_std": {
"type": "standard",
"stopwords": "_english_"
}
}
}
}
}
#GET /my_index/_analyze
{
"analyzer": "standard",
"text": "a dog is in the house"
}
#GET /my_index/_analyze
{
"analyzer": "es_std",
"text":"a dog is in the house"
}
7.2.3 定制自己的分词器
#PUT /my_index
{
"settings": {
"analysis": {
"char_filter": {
"&_to_and": {
"type": "mapping",
"mappings": ["&=> and"]
}
},
"filter": {
"my_stopwords": {
"type": "stop",
"stopwords": ["the", "a"]
}
},
"analyzer": {
"my_analyzer": {
"type": "custom",
"char_filter": ["html_strip", "&_to_and"],
"tokenizer": "standard",
"filter": ["lowercase", "my_stopwords"]
}
}
}
}
}
#GET /my_index/_analyze
{
"text": "tom&jerry are a friend in the house, <a>, HAHA!!",
"analyzer": "my_analyzer"
}
#PUT /my_index/_mapping/my_type
{
"properties": {
"content": {
"type": "text",
"analyzer": "my_analyzer"
}
}
}
7.3 type底层数据结构
7.4 root Object解析
7.4.1 _source
- 查询的时候可以拿到完整的document,不需要线拿document id,再发送一次请求
- partial update基于_source实现
- reindex时,直接基于_source实现,不需要从数据库,(或者其他外部存储)查询数据再修改
- debug query更容易,可以直接看到_source
7.4.1.1 可禁用source:
PUT /index
{
"mappings":{
"my_types":{
"_source":{
"enabled":false
}
}
}
}
7.4.1.2 设置source的字段
#PUT /source_index
{
"mappings": {
"source_type":{
"_source":{
"includes":["content"]
},
"properties": {
"title":{
"type": "text"
},
"content":{
"type": "text"
}
}
}
}
}
7.4.2 _all
-
将所有field打包在一起,作为一个_all field,建立索引。
-
没有指定任何field进行搜索时,就是使用_all field在搜索
-
也可以在field级别设置,对每个字段的映射设置include_in_all参数 ,设置是否要将field值包含在_all field中
-
\ _all字段默认是关闭的,如果要开启_all字段,索引增大是不言而喻的。_all字段开启适用于不指定搜索某一个字段,根据关键词,搜索整个文档内容
7.4.3 index,store,all
7.4.3.1 index
- index 设置为false,则不索引,也不能检索
- 其他值
- no: 不对该字段进行索引(无法搜索)
- analyzied: 分词后索引
- not_analyzied: 以单个关键词进行索引
7.4.3.2 store
- 属性store默认false,当某个数据字段很大,我们可以指定其它字段store为true,这样就不用从_source中取数据。 store 的意思是,是否在 _source 之外在独立存储一份
- 哪些情形下需要显式的指定store属性呢?大多数情况并不是必须的。从_source中获取值是快速而且高效的。如果你的文档长度很长,存储 _source或者从_source中获取field的代价很大,你可以显式的将某些field的store属性设置为yes。缺点如上边所说:假设你存 储了10个field,而如果想获取这10个field的值,则需要多次的io,如果从_source中获取则只需要一次,而且_source是被压缩过 的。
- 还有一种情形:reindex from some field,对某些字段重建索引的时候。从source中读取数据然后reindex,和从某些field中读取数据相比,显然后者代价更低一些。这些字段store设置为yes比较合适
- 总结:
- 如果对某个field做了索引,则可以查询。如果store:yes,则可以展示该field的值
- 但是如果你存储了这个doc的数据(source enable),即使store为no,仍然可以得到field的值(client去解析)。
- 所以一个store设置为no 的field,如果_source被disable,则只能检索不能展示
7.4.3.3 all
7.5 Dynamic Mapping策略
7.5.1 策略规则
- true:遇到陌生字段酒精性dynamic mapping
- false:遇到陌生字段就忽略
- strict:遇到陌生字段就报错
7.5.2 手动关闭日期映射
#PUT /index/_mapping/type
{
"date_detection":false
}
7.5.3自定义映射模板
PUT /my_index
{
"mappings": {
"my_type": {
"dynamic_templates": [
{ "en": {
"match": "*_en",
"match_mapping_type":"string",
"mapping": {
"type": "string",
"analyzer": "english"
}
}
}
]
}}}
PUT /my_index/my_type/1
{
"title": "this is my first article"
}
PUT /my_index/my_type/2
{
"title_en": "this is my first article"
}
- title没有匹配到任何dynamic模板,默认standard分词器,不会过滤停用词,is可以搜索到结果
- title_en匹配到dynamic模板,使用english分词,会过滤停用词,无法搜索到
7.6 基于scoll+bulk+索引别名实现零停机重建索引
7.6.1 操作步骤
- 初始索引: 误建了一个date类型的字段,存放在alias_index索引中
- 此时想将字段变更为字符串类型
- 重新建立新索引 alias_index_new
- 利用scoll技术,将数据批量查询出来
- 利用bulk,将数据导入到新索引中
- 让java应用指向旧索引的别名,good_index,实际指向alias_index
- 最后进行嫁接
POST /_aliases
{
"actions": [
{ "remove": { "index": "my_index", "alias": "goods_index" }},
{ "add": { "index": "my_index_new", "alias": "goods_index" }}
]
}
- 基于alias对client透明切换index
开发技巧
聚合排序
-
TermsAggregationBuilder可根据聚合的情况根据不同条件进行排序
-
_count
按文档数排序。对
terms
、histogram
、date_histogram
有效。 -
_term
按词项的字符串值的字母顺序排序。只在 terms
内使用。
_key
按每个桶的键值数值排序(理论上与 _term
类似)。 只在 histogram
和 date_histogram
内使用。