一、ES介绍:
- ES是一款基于倒排索引的NoSQL数据库,传统数据库对于模糊查询存在性能瓶颈,ES更擅长于大数据量的模糊查询,搜索场景下
- ES存数据时,会先将数据进行分词,将分词的结果作为索引存入数据库中;当进行查询时也会将查询的参数进行分词,根据分词结果去ES中查询索引,根据索引查找到匹配的文档,从而将文档返回
二、请求方式:
- MySQL:数据存储在表中,表存储在数据库中
- ES:document存储在Type中,type存储在index中
- index:相当于数据库
- type:相当于表
- docment:相当于数据库中的一条数据
- field text/keyword/byte:相遇于列
- 映射Mapping:Schema
- 分片sharding和副本replicas:index都是由sharding组成的,每个sharding都有一个或多个备份
- shard 分片数量在建立索引时设置,设置后不能修改,默认5个;replica 副本数量默认1个,可随时修改数量
三、基本操作
MySQL:数据库 -> 数据表 -> 数据 ES:index -> type -> document
-
索引 (表)
- 创建 – PUT index_name
- 删除 – DELETE index_name
- 查询 – GET index_name
-
文档 (数据):对数据的操作需要先找到Index再找到type再找到文档编号,文档编号类似于MYSQL中的主键
-
添加 – POST index_name/_doc/101
-
删除 – DELETE index_name/_doc/101
不会物理删除,只会将其标记为deleted,当数据越来越多时,在后台自动删除
-
修改 – POST index_name/_doc/101
全量修改、部分更新(脚本修改、文档合并等)
修改数据和添加数据相同,若不存在该数据,则会创建一条数据;存在该数据,则进行全量替换,替换document的json串内容
document是不可变的,如果要修改document的内容,就要进行全量替换,直接对document重新创建索引,替换里面的内容
es会把之前的document标记为deleted,然后新增给定的document,当创建越来越多,es会在后台自动删除标记为deleted的document
-
查询 – GET index_name/_doc/101
-
四、复杂查询
-
精确查询
-
term 不对关键字进行分词,进行全匹配
GET /index1/_search { "query":{ "term":{ "item": "1001" } } }
-
terms 多关键词全匹配
GET /index1/_search { "query":{ "terms":{ "item": ["1001","1002","1003"] } } }
-
-
模糊查询
-
match 对关键词进行拆词匹配
GET /index1/_search { "query":{ "match":{ "itemName": "花瓶" } } }
-
match_all 对所有关键词进行拆词匹配
-
multi_match 多属性匹配
GET /index1/_search { "query":{ "multi_match":{ "query": "类别", "fields": ["itemName","itemPrice"] } } }
-
-
分页查询
GET /index1/_search { "query":{ "match":{ "itemName": "花瓶" } }, "from":0, "size":3 }
-
排序 “order”:“asc/desc”
GET /index1/_search { "query":{ "match":{ "itemName": "花瓶" } }, "from":0, "size":3, "sort": [ { "itemPrice": { "order": "asc" } } ] }
-
范围查询 – gte lte
GET /index1/_search { "query":{ "range": { "itemPrice": { "gte": 5, "lte": 20 } } } }
-
模糊查询 – “fuzzy”:{}
GET /index1/_search { "query":{ "fuzzy": { "itemName": { "value": "瓶" } } } }
-
复合查询 must->and should->or must_not->not
在查询数据时,复合条件的文档会通过_source字段返回数据
GET index1/_search { "query": { "bool":{ "must": [ { "match": { "itemName": "花瓶" } } ] } }, "sort": [ { "itemPrice": { "order": "asc" } } ], "_source": ["itemPrice","itemName"], "from": 0, "size": 5, "highlight": { "fields": { "itemName": {} }, "pre_tags": "aaaa", "post_tags": "ddddd" } }
五、面试相关知识
-
source字段会存储文档的原始信息,在查询复合条件的文档时,会通过_source字段返回原始信息;但有时并不需要source字段,不使用能节省不少的存储空间;以下几种情况必须有source:
- 文档需要使用update或update_by_query更新
- 会用到reindex
- 会用到文档高亮
-
ES:是一个基于Lucene框架的搜索引擎产品,提供Restful风格的操作接口
- Lucene是一个非常高效的全文检索引擎框架
-
倒排索引:索引是从ID到内容,倒排索引是从内容到ID。比较适合做关键字检索,可以控制数据总量,提高查询效率。如:根据输入的某个单词,找到含有这个单词,或与这个单词有关的文章
- 倒排索引中,所有词项对应一个或多个文档
- 倒排索引中的词项,根据字典排序升序排列
-
text和keyword类型的区别:keyword类型是不会分词的,直接根据字符串内容建立倒排索引,所以keyword类型的字段只能通过精确值搜索;text类型在存入es时,回显分词,然后根据分词后的内容建立倒排索引
-
query和filter的区别:query查询操作不仅会进行查询,还会计算分值,用于确定相关度;filter:查询操作仅判断是否满足查询条件,不会计算分值,也不关心返回的排序问题,同时filter查询的结果可以被缓存,提高性能
-
ES写入数据的工作原理:
- 客户端发写数据的请求时,可以发往任意节点,这个节点就会成为协调节点
- 计算文档要写入的分片:计算时采用hash取模的方式进行
- 协调节点会进行路由,将请求转发给对应的primary sharding所有的数据节点
- 数据节点上的primary sharding处理请求,写入数据到索引库,并将数据同步到对应的replica sharding
- primary sharding和replica sharding都保存好文档后,返回客户端响应
-
ES的更新和删除流程:
- 由于ES中的文档是不可变的,因此不能被删除或修改
- 如果是删除,文档其实并没有被删掉,而是在.del文件中被标记为deleted状态,该文档亦然能匹配,但在结果中会被过滤
- 如果是更新,就是将 旧得doc标识为deleted状态,然后创建一个新的doc
- memory buffer每refresh一次,就会产生一个segment文件,所以默认1s生成一个segment,定期执行merge,将多个segment文件合并成一个,同时会将标识为deleted的doc给物理删除掉,不写入新的segment。然后会写一个commit point标识所有新的segment文件供搜索使用,删除旧的
-
ES查询数据的工作原理:
- 客户端发请求给任意一个节点,这个节点就称为协调节点
- 协调节点将查询请求广播到每一个数据节点,这些数据节点的分片就会处理该查询请求
- 每个分片进行数据查询,将符合条件的数据放在一个队列中,并将这些数据的文档ID、节点信息、分片信息都返回给协调节点
- 由协调节点将所有结果进行汇总并排序
- 协调节点向包含这个文档ID的分片发送get请求,对应的分片将文档数据返回给协调节点,最后协调节点将数据整合返回给客户端
总结:客户端将请求发动到协调节点,协调节点将请求广播到数据节点,数据节点的分片进行数据查询,返回结果进行汇总并排序,协调节点向包含文档ID的分片发送get请求,得到文档数据后,整合返回给客户端
-
ES的搜索流程:
- Query – 协调节点将搜索请求广播到所有的primary shared或replica,每个分片在本地执行搜索并构建一个匹配文档大小的优先队列。接着每个分片返回各自优先队列中 所有docId和打分值给协调节点,由协调节点进行数据合并、排序、分页等,产生最终结果
- Fetch – 协调节点根据Query 阶段产生的结果,在各个节点上查询docId实际的document内容,最后由协调节点返回结果给客户端
- summary:协调节点根据分片执行搜索返回的结果进行数据合并、排序、分页等并产生Query 结果;根据Query 结果查询docId实际的document内容返回给客户端
-
ES的深度分页与滚动搜索scroll:
- 深度分页:其实就是搜索的深浅度,如1-10页是比较浅的,第10000/20000页就是很深的了。搜索太深就会造成性能问题,es为了性能,不支持超过一万条数据以上的分页查询,因此应该避免深度分页操作(限制分页页数),比如最多只提供100页展示
- 滚动搜索:一次查询1万+条数据,往往会造成性能相应。此时可使用滚动搜索scroll,滚动搜索可以先查询出一些数据,再接着依次往下查询。在第一次查询时会有一个滚动id,相当于一个锚标记,随后再次滚动搜索会需要根据上一次搜索滚动id,进行下一次的搜索请求。
-
ES在高并发下如何保证读写一致:
- 对于更新操作 – 可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖
- 每个文档都有一个_version,在文档被修改时,该值加1,当一个旧版本出现在新版本之后,会被简单忽略。_version确保数据不会因为修改冲突而丢失
- 比如指定文档version进行修改,如果版本号不对,请求失败
- 对于写操作 – 一致性级别支持quorum/one/all,默认为quorum 但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样的副本被认为故障,副本会在不同节点上重建
- quorum – 要求ES中大部分的shard是活跃可用的,才可以执行写操作
- one – 写操作只要有一个primary shard是active活跃可用的,就可以执行
- all – 写操作必须所有的primary shard和replica shard都是活跃可用的,才可以执行
- 对于读操作 – 可以设置 replication 为 sync(默认),使得操作在主分片和副分片都完成后才会返回;如果设置replication 为 async 时,也可以通过设置搜索请求参数 _preference 为 primary 来查询主分片,确保文档是最新版本
- 对于更新操作 – 可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖
-
建立索引阶段性能提升方法:
- 大批量导入时,设置 index.number_of_replicas: 0 关闭副本,等数据导入完成后再开启
- 使用批量请求并调整大小:每批数据5-15MB
- 如果搜索结果不需要近实时性,可以把每个索引 index.refresh_interval 改到30s
- 增加index.translog.flush_threshold_size 设置,从默认的512MB到更大的值
- 使用 SSD 存储介质
- 端和合并:Elasticsearch 默认值是 20 MB/s,如果是SSD,可以考虑提高到100-200MB/s。如果在做批量导入,可以彻底关掉合并限流