ElasticSearch基础

Elasticsearch是用Java开发的当前最流行开源的企业级搜索引擎,能够达到实时搜索稳定可靠快速,安装使用方便。Elasticsearch是基于Lucene的,Lucene可被认为是迄今为止最先进性能最好功能最全搜索引擎框架

Lucene是一个全文检索框架,通过程序扫描文本中每个单词,针对单词建立索引,并保存该单词在文本中的位置、以及出现次数。用户查询时通过之前建立好的索引来查询,将索引中单词对应文本位置、出现次数返给用户,有了具体文本位置,则可将具体内容读取出来。

Lucene基于倒排索引,对于使用的数据库主键索引通过主键定位到某条数据,而倒排索引刚好相反,是通过数据对应到主键

但想要使用Lucene,必须使用Java来作为开发语言并将其直接集成到应用中,且Lucene配置及使用非常复杂,需要深入了解检索相关知识来理解其工作原理。

  • 只能在Java项目中使用,且要以jar包方式直接集成项目
  • 使用非常复杂,创建索引搜索索引代码繁杂
  • 不支持集群环境,索引数据不同步,不支持大型项目
  • 索引数据不能太多,索引库和应用所在同一个服务器,共同占用硬盘,共用空间少

ES与Solr对比

单纯对已有数据进行搜索时Solr更快,当实时建立索引时Solr会产生IO阻塞,查询性能较差,该情况下Elasticsearch具有明显优势。

  • Solr利用Zookeeper进行分布式管理,而Elasticsearch自带分布式协调管理功能
  • Solr支持更多格式数据,如JSON、XML、CSV,而Elasticsearch仅支持JSON文件格式
  • Solr在传统搜索应用中表现好于Elasticsearch,但在处理实时搜索应用时效率明显低于Elasticsearch
  • Solr是传统搜索应用的有力解决方案,但Elasticsearch更适用于新兴实时搜索应用。

ES与关系型数据库

关系型数据库 Database数据库 Table表 ROW行 Column列
Elasticsearch Index索引库 Type类型 Document文档 Field字段

ES核心概念

索引index

一个索引就是一个拥有几分相似特征的文档集合,相当于关系型数据库中的database,一个索引由一个名字来标识,必须全部是小写字母,且当要对对应于该索引中的文档进行索引搜索更新删除时,都要使用该名字。

Mapping映射

ElasticSearch中的Mapping映射用来定义一个文档,Mapping是处理数据的方式规则方面做一些限制,如某个字段数据类型默认值分词器是否被索引等,这都是映射里面可设置的。

Field字段

相当于是数据表的字段或列

Type字段类型

每个字段都应该有一个对应的类型,如TextKeywordByte

Document文档

一个文档是一个可被索引的基础信息单元,类似一条记录,文档以JSON格式来表示

Cluster集群

一个集群由一个多个节点组织在一起,共同持有整个数据,并一起提供索引和搜索功能

Node节点

一个节点即集群中一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能,一个节点可通过配置集群名称的方式来加入一个指定的集群。默认每个节点都会被安排加入到一个叫做elasticsearch的集群中。

一个集群中可拥有任意多个节点,且若当前网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做elasticsearch的集群。

分片

一个索引可存储超出单个结点硬件限制的大量数据,如一个具有10亿文档的索引占据1TB磁盘空间,而任一节点都没有这样大的磁盘空间,或者单个节点处理搜索请求,响应太慢,为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,每一份就是一个分片。

创建索引时可指定分片数量每个分片本身也是一个功能完善且独立的索引,该分片可被放置到集群中任何节点上,分片允许水平分割扩展内容容量,允许在分片之上进行分布式并行操作,进而提高性能吞吐量,每个分片怎样分布文档怎样聚合回搜索请求,完全Elasticsearch管理,对于用户透明。

副本

在一个网络环境中,失败随时都可能发生,在某个分片或节点处于离线状态,或由于任何原因消失,该情况下有一个故障转移机制是非常有用且强烈推荐。为此Elasticsearch允许创建分片的一份或多份拷贝,这些拷贝叫做副本分片或直接叫副本

扩展搜索量和吞吐量,搜索可在所有的副本上并行运行,每个索引可被分成多个分片一个索引有零个或者多个副本, 一旦设置了副本,每个索引就有了主分片副本分片分片和副本数量可在索引创建时指定,在索引创建后,可在任何时候动态地改变副本数量,但不能改变分片数量

ES安装

ES不能使用root用户来启动,必须使用普通用户来安装启动

1
2
3
4
5
6
7
8
9
10
groupadd elasticsearch # 创建elasticsearch用户组
useradd eleven # 创建eleven用户
passwd eleven # 给eleven用户设置密码为eleven
usermod -G elasticsearch eleven # 将用户eleven添加到elasticsearch用户组

mkdir -p /usr/local/es # 创建es文件夹
chown -R eleven /usr/local/es/elasticsearch-7.6.1 # 修改owner为eleven用户

visudo # 使用root用户执行visudo命令然后为es用户添加权限
eleven ALL=(ALL) ALL # 在root ALL=(ALL) ALL 一行下面添加eleven用户

修改elasticsearch.yml,可通过修改jvm.options配置文件调整JVM参数。

1
2
3
4
5
6
7
8
9
10
11
12
cluster.name: eleven-es	# 集群名称
node.name: node1 # 节点名称
path.data: /usr/local/es/elasticsearch-7.6.1/data # 数据目录
path.logs: /usr/local/es/elasticsearch-7.6.1/log # 日志目录
network.host: 0.0.0.0
http.port: 9200
discovery.seed_hosts: ["IP1", "IP2", "IP3"]
cluster.initial_master_nodes: ["节点1名称", "节点2名称", "节点3名称"]
bootstrap.system_call_filter: false
bootstrap.memory_lock: false
http.cors.enabled: true
http.cors.allow-origin: "*"

ES需要大量创建索引文件,需要大量打开系统文件,所以需要解除linux系统当中打开文件最大数目限制,不然ES启动会抛错:max file descriptors [4096] for elasticsearch process likely too low, increase to at least [65536]

1
2
3
4
5
sudo vim /etc/security/limits.conf
* soft nofile 65536
* hard nofile 131072
* soft nproc 2048
* hard nproc 4096

若出现max number of threads [1024] for user [es] likely too low, increase to at least [4096]错误信息,是由于普通用户启动线程数限制最大可创建线程数太小,无法创建本地线程问题。

安装IK分词器

使用Elasticsearch来进行中文分词,需要单独给Elasticsearch安装IK分词器插件

1
2
3
mkdir -p /usr/local/es/elasticsearch-7.6.1/plugins/ik
cd /usr/local/es/elasticsearch-7.6.1/plugins/ik
unzip elasticsearch-analysis-ik-7.6.1.zip

ES的默认分词设置是standard单字拆分,可使用IK分词器ik_smartik_max_word分词方式,ik_smart会做最粗粒度拆分ik_max_word会将文本做最细粒度拆分。修改默认分词方法,修改eleven_index索引的默认分词为ik_max_word

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PUT /school_index
{
"settings": {
"index": {
"analysis.analyzer.default.type": "ik_max_word"
}
}
}
POST _analyze
{
"analyzer": "standard",
"text": "中华人民共和国"
}
POST _analyze
{
"analyzer": "ik_smart",
"text": "中华人民共和国"
}
POST _analyze
{
"analyzer": "ik_max_word",
"text": "中华人民共和国"
}

ES基础

ES是面向文档Document的,使用JSON作为文档序列化格式,这其可存储整个对象或文档Document,不仅仅是存储,还会索引index每个文档内容使之可被搜索。ES中可对文档而非成行成列的数据进行索引搜索排序过滤

条件查询GET /索引名称/类型/_search?q=字段1:字段值,字段2:字段值,条件之间是通过逗号分隔多个条件,如分页排序输出指定字段等通过&符号分隔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
GET _cat/nodes?v	// 查看集群节点状态
GET _cat/health?v // 查看集群健康状态

GET /es_db // 查询索引:GET /索引名称
PUT /es_db // 创建索引:PUT /索引名称
DELETE /es_db // 删除索引:DELETE /索引名称
PUT /es_db/_doc/1 // 添加文档:PUT /索引名称/类型/id
{
"name": "张三",
"sex": 1,
"age": 25,
"address": "广州天河公园",
"remark": "java developer"
}
GET /es_db/_doc/1 // 查询文档:GET /索引名称/类型/id
DELETE /es_db/_doc/1 // 删除文档:DELETE /索引名称/类型/id
GET /es_db/_doc/_search // 查询当前类型中的所有文档:GET /索引名称/类型/_search
GET /es_db/_doc/_search?q=age:28 // 条件查询:GET /索引名称/类型/_search?q=*:***
GET /es_db/_doc/_search?q=age[25 TO 26] // 范围查询:GET /索引名称/类型/_search?q=***[** TO **]
GET /es_db/_doc/_mget // 根据多个ID进行批量查询:GET /索引名称/类型/_mget
{"ids":["1","2"]}
GET /es_db/_doc/_search?q=age:<=28 // 查询小于等于:GET /索引名称/类型/_search?q=age:<=**
GET /es_db/_doc/_search?q=age:>=28 // 查询大于等于:GET /索引名称/类型/_search?q=age:>=**
GET /es_db/_doc/_search?q=age[25 TO 26]&from=0&size=1 // 分页查询:from=*&size=*
GET /es_db/_doc/_search?_source=name,age // 对查询结果只输出某些字段:_search?_source=字段,字段
GET /es_db/_doc/_search?q=age[25 TO 26],sex:0 // 多条件查询
GET /es_db/_doc/_search?sort=age:desc // 对查询结果排序sort=字段:desc/asc

ES基于Restful API和所有客户端交互都是使用JSON格式数据,其他所有程序语言都可使用RESTful API,通过9200端口的与ES进行通信,GET查询、PUT添加、POST修改、DELETE删除,POSTPUT都能起到创建/更新的作用:

  • PUT需要对一个具体的资源进行操作,也就是要确定id才能进行更新/创建,而POST可针对整个资源集合进行操作,若不写id则由ES生成一个唯一id进行创建新文档,过填了id则针对该id文档进行创建/更新
  • PUT会将JSON数据都进行替换,POST只会更新相同字段的值
  • PUTDELETE都是幂等性操作,不论操作多少次结果都一样

文档批量操作

通过_mget的API来实现批量操作多个文档,可通过_id批量获取不同indextype的数据,若查询的是同一个文档可indextype放到URL上。且可通过_source指定查询字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
GET _mget
{
"docs": [
{
"_index": "es_db_1",
"_type": "_doc",
"_id": 1,
},
{
"_index": "es_db",
"_type": "_doc",
"_id": 2
}
]
}
GET /es_db/_doc/_mget?_source=age,name
{
"docs": [
{
"_id": 1
},
{
"_id": 2
}
]
}

批量对文档进行写操作是通过_bulk的API来实现的,通过_bulk写操作文档,一般至少有两行参数,第一行参数为指定操作的类型操作的对象如index、type、id,第二行参数为操作的数据actionName表示操作类型,主要有createindexdeleteupdate

1
2
3
4
5
6
7
8
9
10
11
{
"actionName": {
"_index": "indexName",
"_type": "typeName",
"_id": "id"
}
}
{
"field1": "value1",
"field2": "value2"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST _bulk	// 批量创建文档
{"create":{"_index":"article","_type":"_doc","_id":3}}
{"id":3,"title":"eleven 1","content":"eleven 666","tags":["java","面向对象"],"create_time":1554015482530}
{"create":{"_index":"article","_type":"_doc","_id":4}}
{"id":4,"title":"eleven 2","content":"eleven NB","tags":["java","面向对象"],"create_time":1554015482530}

POST _bulk // 普通创建或全量替换index,若原文档不存在则创建,若存在则替换
{"index":{"_index":"article","_type":"_doc","_id":3}}
{"id":3,"title":"eleven 3","content":"eleven3 666","tags":["java","面向对象"],"create_time":1554015482530}
{"index":{"_index":"article","_type":"_doc","_id":4}}
{"id":4,"title":"eleven 4","content":"eleven4 NB","tags":["java","面向对象"],"create_time":1554015482530}

POST _bulk // 批量修改update
{"update":{"_index":"article","_type":"_doc","_id":3}}
{"doc":{"title":"ES大法必修内功"}}
{"update":{"_index":"article","_type":"_doc","_id":4}}
{"doc":{"create_time":1554018421008}}

POST _bulk // 批量删除delete
{"delete":{"_index":"article","_type":"_doc","_id":3}}
{"delete":{"_index":"article","_type":"_doc","_id":4}}

乐观并发控制

在数据库领域中,有悲观并发控制乐观并发控制两种方法来确保并发更新不丢失数据,悲观并发控制关系型数据库广泛使用,阻塞访问资源以防止冲突;ES使用乐观并发控制,若源数据在读写当中被修改,更新将会失败。

1
2
3
4
5
6
7
8
PUT /db_index/_doc/1?if_seq_no=1&if_primary_term=1
{
"name": "Jack",
"sex": 1,
"age": 25,
"book": "Spring Boot 入门到精通2",
"remark": "hello world2"
}

ES老版本是使用version字段来乐观并发控制,新版本7.x使用if_seq_no=文档版本号&if_primary_term=文档位置来乐观并发控制。每当Primary Shard发生重新分配时如重启Primary选举等,_primary_term会递增1_primary_term主要是用来恢复数据时处理当多个文档的_seq_no一样时的冲突。如当一个shard宕机了,raplica需要用到最新的数据,就会根据_primary_term_seq_no两个值来拿到最新的document。

文档映射

ES中映射可以分为动态映射静态映射,在关系数据库中,需要事先在数据库下创建数据表,并创建表字段、类型、长度、主键等,最后才能基于表插入数据。而Elasticsearch中不需要定义Mapping映射,在文档写入ES时,会根据文档字段自动识别类型,该机制为动态映射;也可事先定义好映射,包含文档的各字段类型分词器等,该方式为静态映射

  • 字符串string类型包含textkeyword
  • text:该类型被用来索引长文本,创建索引前会将文本进行分词,转化为词的组合,建立索引;允许es来检索这些词,不能用来排序和聚合
  • keyword:该类型不能分词,可被用来检索过滤排序聚合不可用text进行分词模糊检索
  • 数值型longintegershortbytedoublefloat
  • 日期型date
  • 布尔型boolean

动态映射规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
GET /es_db/_mapping // 获取文档映射

PUT /es_db2 // 创建索引且设置文档映射
{
"mappings": {
"properties": {
"name": {
"type": "keyword",
"index": true,
"store": true
},
"sex": {
"type": "integer",
"index": true,
"store": true
},
"age": {
"type": "integer",
"index": true,
"store": true
},
"book": {
"type": "text",
"index": true,
"store": true,
"analyzer": "ik_smart", // 指定text类型的ik分词器
"search_analyzer": "ik_smart" // 指定text类型的ik分词器
},
"address": {
"type": "text",
"index": true,
"store": true
}
}
}
}

若要推倒现有的映射,得重新建立一个静态索引,然后把之前索引里的数据导入到新的索引里,删除原创建的索引为新索引起个别名,为原索引名。

1
2
3
4
5
6
7
8
9
10
11
POST _reindex // 把之前索引里的数据导入到新的索引里
{
"source": {
"index": "db_index"
},
"dest": {
"index": "db_index_2"
}
}
DELETE /db_index // 删除原创建的索引
PUT /db_index_2/_alias/db_index // 为新索引起个别名, 为原索引名

3)删除原创建的索引

4)为新索引起个别名, 为原索引名

DSL高级查询

Domain Specific Language领域专用语言,由叶子查询子句复合查询子句两种子句组成。DSL查询语言又分为查询DSL过滤DSL。ES中索引的数据都会存储一个_score分值,分值越高就代表越匹配查询上下文中不仅要判断查询条件与文档是否匹配,且还要关心相关度_score分值,需要根据分值排序;过滤器上下文中值关心查询条件与文档是否匹配,不计算_score分值,不关心排序问题,经常使用过滤器,ES会自动缓存过滤器内容

1
2
GET /es_db/_doc/_search // 无查询条件是查询所有,默认查询所有,或使用match_all表示所有
{"query":{"match_all":{}}}

叶子查询

模糊匹配

模糊匹配主要是针对文本类型的字段,文本类型的字段会对内容进行分词查询时也会对搜索条件进行分词,然后通过倒排索引查找到匹配数据,模糊匹配主要通过match等参数来实现

  • match:通过match关键词模糊匹配条件内容,需指定字段名会进行分词
    • query指定匹配的值
    • operator匹配条件类型
      • and:条件分词后要匹配
      • or:条件分词后有一个匹配即可,默认为or
    • minmum_should_match:指定最小匹配数量
  • query_string:和match类似,可不指定字段所有字段中搜索,范围更广泛
  • match_phase:会对输入做分词,但结果中也包含所有分词,且顺序一样
  • prefix前缀匹配
  • regexp:通过正则表达式来匹配数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
POST /es_db/_doc/_search
{
"from": 0,
"size": 2,
"query": {
"match": { // match会根据该字段的分词器,进行分词查询
"address": "广州"
}
}
}
POST /es_db/_doc/_search
{
"query": {
"multi_match": { // 多字段模糊匹配查询
"query": "长沙",
"fields": ["address", "name"] // address或name字段中匹配到“长沙”
}
}
}
POST /es_db/_doc/_search
{
"query": {
"query_string": { // 未指定字段条件查询query_string, 含AND与OR条件
"query": "广州 OR 长沙" // 所有的字段中只要包含“广州”或“长沙”
}
}
}
POST /es_db/_doc/_search
{
"query": {
"query_string": { // 指定字段条件查询query_string
"query": "admin AND 长沙",
"fields": ["name", "address"] // name或address匹配admin和长沙
}
}
}
GET /es_db/_search
{
"query": { // ES执行搜索时,默认operator为or
"match": { // remark字段包含java或developer词组,则符合搜索条件。
"remark": "java developer"
}
}
}
GET /es_db/_search
{
"query": {
"match": {
"remark": { // remark字段包含java和developer词组
"query": "java developer",
"operator": "and"
}
}
}
}
GET /es_db/_search
{
"query": {
"match": {
"remark": { // 需要remark字段中包含多个搜索词条中的一定比例
"query": "java architect assistant",
"minimum_should_match": "50%" // minimum_should_match可使用百分比或固定数字
}
}
}
}

match_phrase短语搜索,使用短语搜索时和match类似,首先对搜索条件进行分词,ES在做分词时除了将数据切分外,还会保留一个词在整个数据中的下标position。当ES执行match phrase短语搜索时,首先将搜索条件分词,然后在倒排索引中检索数据,若搜索条件分词数据在某个document某个field出现时,则检查匹配到的单词的position是否连续,若不连续则匹配失败

ES对match phrase短语搜索提供了slop参数,可实现数据在所有匹配结果中,多个单词距离越近相关度评分越高排序越靠前,若slop移动次数使用完毕还没有匹配成功则无搜索结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET _search
{
"query": {
"match_phrase": { // 短语搜索,搜索条件不分词
"remark": "java assistant"
}
}
}
GET /es_db/_search
{
"query": {
"match_phrase": {
"remark": {
"query": "java assistant",
"slop": 1
}
}
}
}
前缀搜索

通常针对keyword类型字段即不分词字段keyword类型字段数据大小写敏感,前缀搜索效率比较低,且不计算相关度分数前缀越短效率越低。若使用前缀搜索,建议使用长前缀,因为前缀搜索需要扫描完整索引内容,所以前缀越长相对效率越高。

1
2
3
4
5
6
7
8
GET /test_a/_search
{
"query": {
"prefix": {
"f.keyword": {"value": "Jav"}
}
}
}
通配符搜索

通配符可在倒排索引中使用,也可在keyword类型字段中使用。?问号匹配一个任意字符*星号匹配0n个任意字符性能也很低,也需要扫描完整索引

1
2
3
4
5
6
7
8
GET /test_a/_search
{
"query": {
"wildcard": {
"f.keyword": { "value": "?e*o*" }
}
}
}
正则搜索

可在倒排索引keyword类型字段中使用,性能很低需要扫描完整索引

1
2
3
4
5
6
GET /test_a/_search
{
"query": {
"regexp": {"f.keyword": "[A-z].+"}
}
}
搜索推荐

其原理和match phrase类似,先使match匹配term数据即示例中的java,然后在指定slop移动次数范围内前缀匹配示例数据spmax_expansions是用于指定prefix最多匹配多少个term,超过该数量就不再匹配了。该语法限制只有最后一个term会执行前缀搜索。执行性能很差最后一个term需要扫描所有符合slop要求的倒排索引的term。若必须使用一定要使用参数max_expansions

1
2
3
4
5
6
7
8
GET /test_a/_search
{
"query": {
"match_phrase_prefix": {
"f": {"query": "java sp","slop": 10,"max_expansions": 10}
}
}
}
模糊搜索

搜索时可能搜索条件文本输入错误,fuzzy技术就是用于解决错误拼写的,英文中很有效但中文中几乎无效,其中fuzziness代表valueword可修改多少个字母来进行拼写错误纠正,修改字母数量包含字母变更,增加或减少字母。

1
2
3
4
5
6
7
8
GET /test_a/_search
{
"query": {
"fuzzy": {
"f": {"value": "word","fuzziness": 2}
}
}
}
精确匹配
  • term单个条件相等,查询字段映射类型属于为keyword不会被分词
  • terms:单个字段属于某个值数组内的值
  • range:字段属于某个范围内的值
    • gte:大于等于
    • lte:小于等于
    • gt:大于
    • lt:小于
    • now:当前时间
  • exists:某个字段的值是否存在
  • ids通过ID批量查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
POST /es_db/_doc/_search
{
"query": {
"term": { // term查询不会对字段进行分词查询,会采用精确匹配
"name": "admin"
}
}
}
POST /es_db/_doc/_search
{
"query": {
"range": { // 范围查询
"age": {"gte": 25,"lte": 28}
}
}
}
POST /es_db/_doc/_search // 范围、分页、输出字段、综合查询
{
"query": {
"range": { // 范围查询
"age": {"gte": 25,"lte": 28}
}
},
"from": 0, // 分页
"size": 2,
"_source": ["name", "age", "book"], // 指定输出字段
"sort": {"age": "desc"}// 排序
}

组合查询

组合条件查询是将叶子条件查询语句进行组合而形成的一个完整的查询条件,mustfiltershoudmust_not子条件是通过termtermsrangeidsexistsmatch叶子条件为参数,当只有一个搜索条件时,must等对应的是一个对象,当多个条件时,对应的是一个数组

  • bool:各条件之间有andornot关系
    • must:各个条件都必须满足,即各条件是and关系
    • should:各个条件有一个满足即可,即各条件是or关系
    • must_not不满足所有条件,即各条件是not关系
    • filter不计算相关度评分,即不计算_score不对结果排序,效率更高,查询结果可被缓存
  • constant_score不计算相关度评分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST /es_db/_doc/_search
{
"query": {
"bool": {
"filter": { // 对数据进行过滤
"term": {"age": 25}
}
}
}
}
GET /es_db/_search
{
"query": { // 使用should+bool搜索,控制搜索条件的匹配度
"bool": {
"should": [ // 必须匹配java、developer、assistant三个词条中的至少2个
{"match": {"remark": "java"}},
{"match": {"remark": "developer"}},
{"match": {"remark": "assistant"}}
],
"minimum_should_match": 2 // 控制搜索条件的匹配度
}
}
}

ES中执行match搜索时,ES底层通常会对搜索条件进行底层转换,来实现最终的搜索结果,若不怕麻烦,尽量使用转换后的语法执行搜索效率更高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /es_db/_search	// 转换前
{"query":{"match":{"remark":"java developer"}}}
GET /es_db/_search // 转换后
{"query":{"bool":{"should":[{"term":{"remark":"java"}},{"term":{"remark":{"value":"developer"}}}]}}}

GET /es_db/_search // 转换前
{"query":{"match":{"remark":{"query":"java developer","operator":"and"}}}}
GET /es_db/_search // 转换后
{"query":{"bool":{"must":[{"term":{"remark":"java"}},{"term":{"remark":{"value":"developer"}}}]}}}

GET /es_db/_search // 转换前
{"query":{"match":{"remark":{"query":"java architect assistant","minimum_should_match":"68%"}}}}
GET /es_db/_search // 转换后
{"query":{"bool":{"should":[{"term":{"remark":"java"}},{"term":{"remark":"architect"}},{"term":{"remark":"assistant"}}],"minimum_should_match":2}}}
boost权重控制

boost权重控制一般用于搜索时相关度排序使用,将某字段数据匹配时相关度分数增加

1
2
3
4
5
6
7
8
9
10
11
12
GET /es_db/_search
{
"query": {
"bool": {
"must": [{"match": {"remark": "java"}}],
"should": [
{"match": {"remark": {"query": "developer","boost": 3}}},
{"match": {"remark": {"query": "architect","boost": 1}}}
]
}
}
}
dis_max

dis_max语法是直接获取搜索多条件单条件query相关度分数最高的数据,以该数据做相关度排序基于dis_max实现best fields策略进行多字段搜索best fields策略是搜索document中某个field尽可能多的匹配搜索条件。与之相反的是most fields策略尽可能多的字段匹配到搜索条件

best fields策略优点是精确匹配的数据可尽可能排列在最前端,且可通过minimum_should_match去除长尾数据,避免长尾数据字段对排序结果的影响。缺点相对排序不均匀

1
2
3
4
5
6
7
8
9
10
11
GET /es_db/_search
{
"query": {
"dis_max": { // 找name字段中rod匹配相关度分数或remark字段中java developer匹配相关度分数,哪个高就使用哪个相关度分数进行结果排序
"queries": [
{"match": {"name": "rod"}},
{"match": {"remark": "java developer"}}
]
}
}
}

dis_max是将多个搜索query条件中相关度分数最高的用于结果排序,忽略其他query分数,在某些情况下需要其他query条件中相关度介入最终结果排序,此时可使用tie_breaker参数来优化dis_max搜索tie_breaker参数表示将其他query搜索条件相关度分数乘以参数值再参与结果排序。若不定义tie_breaker参数相当于参数值为0,故其他query条件的相关度分数被忽略。

1
2
3
4
5
6
7
8
9
10
11
12
GET /es_db/_search
{
"query": {
"dis_max": { // 找name字段中rod匹配相关度分数或remark字段中java developer匹配相关度分数,哪个高就使用哪个相关度分数进行结果排序
"queries": [
{"match": {"name": "rod"}},
{"match": {"remark": "java developer"}}
],
"tie_breaker": 0.5
}
}
}

使用multi_match简化dis_max+tie_breaker,ES中相同结果搜索也可使用不同语法语句来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
GET /es_db/_search
{
"query": {
"dis_max": {
"queries": [
{"match": {"name": "rod"}},
{"match": {"remark": {"query": "java assistant","boost": 2}}}
],
"tie_breaker": 0.5
}
}
}
GET /es_db/_search
{
"query": {
"multi_match": {
"query": "rod java developer",
"fields": [
"name",
"remark^2" // ^n代表权重,相当于"boost":n
],
"type": "best_fields", // 其中type常用的有best_fields和most_fields
"tie_breaker": 0.5,
"minimum_should_match": "50%"
}
}
}

cross fields是一个唯一标识,且分部在多个fields中,使用该唯一标识搜索数据即cross fields搜索。如人名可分为姓和名,地址可分为省、市、区县、街道等。使用人名或地址来搜索document,就称为cross fields搜索。

实现这种搜索,一般都是使用most fields搜索策略,因为这就是多个field问题。Cross fields搜索策略是从多个字段中搜索条件数据默认和most fields搜索逻辑一致计算相关度分数和best fields策略一致。一般若使用cross fields搜索策略,都会携带operator额外参数,用来标记搜索条件如何在多个字段中匹配。

1
2
3
4
5
6
7
8
9
10
11
GET /es_db/_search
{
"query": {
"multi_match": { // 搜索条件中java必须在name或remark字段中匹配,developer也必须在name或remark字段中匹配
"query": "java developer",
"fields": ["name", "remark"],
"type": "cross_fields",
"operator": "and"
}
}
}

most fields策略是尽可能匹配更多字段,会导致精确搜索结果排序问题,又因为cross fields搜索,不能使用minimum_should_match来去除长尾数据。故在使用most fieldscross fields策略搜索数据时,都有不同缺陷,商业项目开发中都推荐使用best fields策略实现搜索。

可通过copy_to解决cross fields搜索问题copy_to就是将多个字段复制到一个字段中实现一个多字段组合,在商业项目中,也用于解决搜索条件默认字段问题。若需要使用copy_to语法,则需要在定义index时手工指定mapping映射策略

1
2
3
4
5
6
7
8
9
PUT /es_db/_mapping
{
"properties": {
"provice": {"type": "text","analyzer": "standard","copy_to": "address"},
"city": {"type": "text","copy_to": "address"},
"street": {"type": "text","analyzer": "standard","copy_to": "address"},
"address": {"type": "text","analyzer": "standard"}
}
}

在mapping定义中新增provice、city、street、address等字段,其中provice、city、street三个字段值会自动复制到address字段中,实现一个字段组合。在搜索地址时可在address字段中做条件匹配,从而避免most fields策略导致的问题。在维护数据时不需对address字段特殊维护,ES会自动维护组合字段。在存储时物理上不一定存在但逻辑上存在,因为address由3个物理存在属性province、city、street组成。

使用matchproximity search实现召回率精准度平衡,若搜索时只使用match phrase语法,会导致召回率低下,若只使用match语法,会导致精准度低下,因为搜索结果排序是根据相关度分数算法计算得到。若需要在结果中兼顾召回率精准度,就需要将matchproximity search混合使用。

  • 召回率搜索结果比率,如索引A中有100个document,搜索时返回多少个document
  • 精准度搜索结果准确率,如搜索条件为hello java,搜索结果中尽可能让短语匹配和hello java离的近的结果排序靠前
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /test_a/_doc/3
{"f":"hello, java is very good, spark is also very good"}
POST /test_a/_doc/4
{"f":"java and spark, development language "}
POST /test_a/_doc/5
{"f":"Java Spark is a fast and general-purpose cluster computing system. It provides high-level APIs in Java, Scala, Python and R, and an optimized engine that supports general execution graphs."}
POST /test_a/_doc/6
{"f":"java spark and, development language "}

GET /test_a/_search
{"query":{"match":{"f":"java spark"}}}
GET /test_a/_search
{
"query": {
"bool": {
"must": [{"match": {"f": "java spark"}}],
"should": [{"match_phrase": {"f": {"query": "java spark","slop": 50}}}]
}
}
}

连接查询

  • 父子文档查询:parent/child
  • 嵌套文档查询:nested

ES架构原理

在ES中主要分成MasterDataNode两类节点,ES启动时会选举出一个Master节点,当某个节点启动后,使用Zen Discovery机制找到集群中的其他节点并建立连接,并从候选主节点中选举出一个主节点。一个ES集群中只有一个Master节点,但会有NDataNode节点,在生产环境中内存可相对小一点但机器要稳定。

  • Master管理索引即创建、删除索引,分配分片维护元数据管理集群节点状态不负责数据写入和查询,比较轻量级
  • DataNode数据写入数据检索,大部分ES压力都在DataNode节点上
分片Shard

ES是一个分布式搜索引擎,索引数据也分成若干部分,分布在不同服务器节点中,分布在不同服务器节点中的索引数据,就是Shard分片。Elasticsearch会自动管理分片,若发现分片分布不均衡,会自动迁移一个索引index由多个shard分片组成,分片是分布在不同的服务器上

副本

为了对ES分片进行容错,假设某个节点不可用,会导致整个索引库都将不可用。故需要对分片进行副本容错,每个分片都会有对应的副本默认创建索引为1个分片、每个分片有1个主分片1个副本分片

每个分片都会有一个Primary Shard主分片,也会有若干个Replica Shard副本分片Primary ShardReplica Shard不在同一个节点上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PUT /job_idx_shard_temp // 创建指定分片数量、副本数量的索引
{
"mappings": {
"properties": {
"id": {"type": "long","store": true},
"area": {"type": "keyword","store": true},
"edu": {"type": "keyword","store": true}
}
},
"settings": {
"number_of_shards": 3, // 指定分片数量
"number_of_replicas": 2 // 指定副本数量
}
}

GET /_cat/indices?v // 查看分片、主分片、副本分片

文档写入原理

选择任意一个DataNode发送请求如node2,此时node2就成为一个coordinating node协调节点,通过协调节点计算得到文档要写入的分片shard = hash(routing) % number_of_primary_shards,其中routing是一个可变值默认为文档_id,然后协调节点会进行路由,将请求转发给对应primary shard主分片所在的DataNode,假设primary shard主分片在node1、replica shard副分片在node2,node1节点上的Primary Shard处理请求,写入数据到索引库中,并将数据同步到Replica shard副分片,Primary Shard和Replica Shard都保存好了文档则返回Client。

检索原理

Client发起查询请求某个DataNode接收到请求后,该DataNode就成为Coordinating Node协调节点协调节点将查询请求广播到每一个数据节点,这些数据节点分片会处理该查询请求,每个分片进行数据查询,将符合条件的数据放在一个优先队列中,并将这些数据的文档ID节点信息分片信息返回给协调节点协调节点将所有结果进行汇总并全局排序,协调节点向包含这些文档ID分片发送get请求,对应的分片将文档数据返回给协调节点,最后协调节点将数据返回给客户端。

准实时索引

当数据写入到ES分片时会首先写入到内存中,然后通过内存buffer生成一个Segment,并刷到文件系统缓存中而不是直接刷到磁盘,数据可被检索,ES中默认1refresh一次。数据在写入内存的同时,也会记录Translog日志,若refresh期间出现异常,会根据Translog来进行数据恢复,等到文件系统缓存中的Segment数据都刷到磁盘中,则清空Translog文件,ES默认每隔30分钟会将文件系统缓存的数据刷入到磁盘Segment太多时ES定期会将多个Segment合并成为大的Segment,减少索引查询时IO开销,此阶段ES会真正的物理删除之前执行过delete的数据