ElasticSearch 集群基本概念及常用操作汇总(建议收藏)


内容来源于本人的印象笔记,简单汇总后发布到博客上,供大家需要时参考使用。

原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94

目录:

  • ElasticSearch集群特性
  • es部署安装,要踩的坑少不了
  • ElasticSearch CRUD操作
  • ElasticSearch Search操作
  • ElasticSearch 分词器analyzer

ElasticSearch,集群特性

ES的集群是基于Master Slave架构的
集群中一个节点被选举为主节点后,
 它将负责管理集群范围内的所有变更,例如增加、删除索引,或者增加、删除节点等;
 而主节点并不需要涉及到文档级别的变更和搜索等操作,所以当集群只拥有一个主节点的情况下,即使流量的增加它也不会成为瓶颈。 任何节点都可以成为主节点;

作为用户,我们可以将请求发送到 集群中的任何节点 ,包括主节点。 每个节点都知道任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。 无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。

分片

https://www.elastic.co/guide/cn/elasticsearch/guide/current/_add-an-index.html

https://www.elastic.co/guide/cn/elasticsearch/guide/current/routing-value.html#routing-value

Elasticsearch 是利用分片将数据分发到集群内各处的。分片是数据的容器,文档保存在分片内,分片又被分配到集群内的各个节点里。 当你的集群规模扩大或者缩小时, Elasticsearch 会自动的在各节点中迁移分片,使得数据仍然均匀分布在集群里;

一个分片可以是 主 分片或者 副本 分片。 索引内任意一个文档都归属于一个主分片,所以主分片的数目决定着索引能够保存的最大数据量。

技术上来说,一个主分片最大能够存储 Integer.MAX_VALUE - 128 个文档,但是实际最大值还需要参考你的使用场景:包括你使用的硬件, 文档的大小和复杂程度,索引和查询文档的方式以及你期望的响应时长。

一个副本分片只是一个主分片的拷贝。副本分片作为硬件故障时保护数据不丢失的冗余备份,并为搜索和返回文档等读操作提供服务。

在索引建立的时候就已经确定了主分片数,但是副本分片数可以随时修改。

注:索引创建的时候就需要确定好主分片的数量,因为索引一旦创建完成后,主分片的数量将不可以再变更,只能动态的修改副本分片数,而副本分片数只能提供搜索和返回文档等读操作时提供相关的服务,所以通过增加副本数可以增加集群读操作的可用性;但是对于索引中数据的存储,还是只能保存在主分片上,所以,合理的确定主分片的值,对于后续的集群的扩展是很有必要和好处的;

创建数据时的路由策略

当索引一个文档的时候,文档会被存储到一个主分片中。 Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?当我们创建文档时,它如何决定这个文档应当被存储在分片 1 还是分片 2 中呢

首先这肯定不会是随机的,否则将来要获取文档的时候我们就不知道从何处寻找了。实际上,这个过程是根据下面这个公式决定的:shard = hash(routing) % number_of_primary_shards

routing 是一个可变值,默认是文档的 _ id ,也可以设置成一个自定义的值。 routing 通过 hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到 余数 。这个分布在 0 到 number_of_primary_shards-1 之间的余数,就是我们所寻求的文档所在分片的位置;

这就解释了为什么我们要在创建索引的时候就确定好主分片的数量 并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。

那么前期如何更好的设计好我们的ES集群的主分片数,来保证可以支撑后续的业务呢?
详情可以参考:数据建模-扩容设计章节
https://www.elastic.co/guide/cn/elasticsearch/guide/current/scale.html

所有的文档 API( get 、 index 、 delete 、 bulk 、 update 以及 mget )都接受一个叫做 routing的路由参数 ,通过这个参数我们可以自定义文档到分片的映射。一个自定义的路由参数可以用来确保所有相关的文档——例如所有属于同一个用户的文档——都被存储到同一个分片中。除了索引创建时不支持路由外,其他的数据导入,搜索等操作都可以指定路由进行操作;索引创建肯定是由主节点来操作的

原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94

ES部署安装,要踩的坑少不了

此处直接使用的是elasticsearch-7.3.2的版本,所以提示:Elasticsearch的未来版本将需要Java 11;您的Java版本从[/opt/package/jdk1.8.0_241/jre]不满足此要求,此处直接注释掉了本地环境变量对JDK的映射,直接使用es自带的java启动就行

future versions of Elasticsearch will require Java 11; your Java version from [/opt/package/jdk1.8.0_241/jre] does not meet this requirement

如下所示:为了安全考虑es不支持直接使用root用户启动

[2020-06-15T10:24:11,746][WARN ][o.e.b.ElasticsearchUncaughtExceptionHandler] [VM_0_5_centos] uncaught exception in thread [main]
org.elasticsearch.bootstrap.StartupException: java.lang.RuntimeException: can not run elasticsearch as root

此处新建一个新的elsearch用户进行启动

新建elsearch用户组 以及elsearch用户
groupadd elsearch
useradd elsearch -g elsearch -p elasticsearch

将对应的elasticsearch-7.3.2目录授权给elsearch
chown -R elsearch:elsearch  /opt/shengheApp/elasticsearch/elasticsearch-7.3.2

切换用户进行es的启动

su elsearch #切换账户
cd elasticsearch/bin #进入你的elasticsearch目录下的bin目录
./elasticsearch

# 指定jvm内存启动,
ES_JAVA_OPTS="-Xms512m" ./bin/elasticsearch

后台启动模式
./elasticsearch -d

修改ES配置为所有IP都可以访问,network.host修改为 0.0.0.0

network.host: 0.0.0.0

修改完IP配置项后,启动将会异常,提示如下:

 [VM_0_5_centos] publish_address {172.17.0.5:9300}, bound_addresses {[::]:9300}
[2020-06-15T10:46:53,894][INFO ][o.e.b.BootstrapChecks    ] [VM_0_5_centos] bound or publishing to a non-loopback address, enforcing bootstrap checks
ERROR: [3] bootstrap checks failed
[1]: initial heap size [536870912] not equal to maximum heap size [1073741824]; this can cause resize pauses and prevents mlockall from locking the entire heap
[2]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
[3]: the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured

解决方式如下:
解决第一个警告:

由于我本机内存不足所以启动的时候,直接指定了JVM参数进行了启动:ES_JAVA_OPTS="-Xms512m" ./bin/elasticsearch
但是:bin/elasticsearch在启动的时候会去加载config/jvm.options 下的jvm配置,由于jvm.options下Xms和Xmx配置为1g,所以启动的时候提示冲突了,初始堆大小512M和最大堆大小1g,不匹配;解决方式是,启动的时候将命令更改为: ES_JAVA_OPTS="-Xms512m -Xmx512M" ./bin/elasticsearch , 或者直接修改jvm.options中Xms和Xmx的大小,然后直接 ./bin/elasticsearch启动也行
解决第二个警告:

临时提高了vm.max_map_count的大小,此操作需要root权限:
sysctl -w vm.max_map_count=262144
sysctl -a|grep vm.max_map_count # 设置完成后可以通过该命令查看是否设置成功
永久修改vm.max_map_count的大小:
vi /etc/sysctl.conf 添加下面配置: vm.max_map_count=655360 并执行命令: sysctl -p 然后,重新启动elasticsearch,即可启动成功。

解决第三个警告:

集群节点问题,我们现在只启动一个节点测试,所以修改当前节点的名称为:node-1,然后配置cluster.initial_master_nodes 初始化节点为自身的node-1节点即可;

配置elasticsearch.yml文件如下:
node.name: node-1
cluster.initial_master_nodes: ["node-1"]

然后重启es即可

原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94

ElasticSearch,CRUD操作

索引受文件系统的限制。仅可能为小写字母,不能下划线开头。同时需遵守下列规则:

不能包括 , /, *, ?, ", <, >, |, 空格, 逗号, #
7.0版本之前可以使用冒号:,但不建议使用并在7.0版本之后不再支持
不能以这些字符 -, _, + 开头
不能包括 . 或 …
长度不能超过 255 个字符
以上这些命名限制是因为当Elasticsearch使用索引名称作为磁盘上的目录名称,这些名称必须符合不同操作系统的约定。
我猜想未来可能会放开这些限制,因为我们使用uuid关联索引放在磁盘上,而不使用索引名称。

类型
类型名称可以包括除了null的任何字符,不能以下划线开头。7.0版本之后不再支持类型,默认为_doc.


基于ES7.0+版本
1、

新增一个文档
PUT twitter/_doc/1
{
"user": "GB",
"uid": 1,
"city": "Beijing",
"province": "Beijing",
"country": "China"
}

如上,创建一个twitter的索引,并且创建一个叫做_doc的type,并插入一个文档1,(在ES7中,一个index只能有一个type,如果创建多个type将会提示异常,默认情况下因为只能创建一个type,所以默认叫做_doc即可;)

新插入的数据一般不会实时参与到搜索中,可以通过调用如下接口,使ES强势进行一次refersh操作;(除此之外在创建index时也可以设置referch的周期,默认为1,也就是每一秒刷新一下新的数据到索引中,)
2、

新增数据并实时刷新到索引中
PUT twitter/_doc/1?refresh=true
{
"user": "GB",
"uid": 1,
"city": "Beijing",
"province": "Beijing",
"country": "China"
}

默认情况下执行(上述1的)dsl语句, 第一步判断是确定插入的文档是否指定id,如果没有指定id,系统会默认生成一个唯一id,上述我们指定了id为(1),所以插入es时,将按照我们指定的id进行插入,
并且,如果当前该Id是在es中已经存在的,那么此时将会进行第二次判断,检查插入时是否指定了_version,如果插入时没有指定_version,那么对于已有的doc数据,_version会进行递增,并将文档进行更新覆盖,如果插入时指定了_version,那么判断当前所指定的_version于现有的文档的_version是否相等,相等则覆盖,不相等则插入失败(注意:这里则类似于一个乐观锁的操作了,如果有类似的数据插入时的场景通过该_version则可以实现一个乐观锁的操作);

除此之外,在插入数据时,如果不想做修改操作,那么可以使用

使用create类型,表示只新增,数据存在时不做修改操作
PUT twitter/_doc/1?optype=create

或者

PUT twitter/_create/1

这两个语法都表示使用类型为create的方式来创建文档,如果当前文档已经存在,则直接报错,不会进行覆盖更新;

optype的类型有两个,index和create,我们默认使用    PUT twitter/_doc/1 创建数据时,其实就等价于 PUT twitter/_doc/1?optype=index

使用post新增文档

在上面,特意为我们的文档分配了一个ID。其实在实际的应用中,这个并不必要。相反,当我们分配一个ID时,在数据导入的时候会检查这个ID的文档是否存在,如果是已经存在,那么就更新器版本。如果不存在,就创建一个新的文档。如果我们不指定文档的ID,转而让Elasticsearch自动帮我们生成一个ID,这样的速度更快。在这种情况下,我们必须使用POST,而不是PUT

使用POST请求不指定ID时,es自动对应生成ID
POST twitter/_doc
{
"user": "GB",
"uid": 1,
"city": "Beijing",
"province": "Beijing",
"country": "China"
}

GET数据

获取twitter索引文档为1的数据
GET twitter/_doc/1

获取twitter索引文档为1的数据,并且只返回这个文档的 _source 部分
GET twitter/_doc/1/_source

只获取source的部分字段
GET twitter/_doc/1?_source=city,age,province

_MGET数据

获取多个文档的数据
GET _mget
{
  "docs": [
    {
      "_index": "twitter",
      "_id": 1
    },
    {
      "_index": "twitter",
      "_id": 2
    }
  ]
}

获取多个文档的数据,并且只返回部分字段
GET _mget
{
  "docs": [
    {
      "_index": "twitter",
      "_id": 1,
      "_source":["age", "city"]
    },
    {
      "_index": "twitter",
      "_id": 2,
      "_source":["province", "address"]
    }
  ]
}

直接获取id为1和2的数据,(简化后的写法)
GET twitter/_doc/_mget
{
  "ids": ["1", "2"]
}

修改文档(全量修改,指定字段修改,先查询再修改)

使用PUT的方式默认就会进行数据的更新添加,这个上述也都提到过,但是使用PUT的方式是全量更新,如果那些字段不指定的话,将会更新为一个空值;

PUT twitter/_doc/1
{
   "user": "GB",
   "uid": 1,
   "city": "北京",
   "province": "北京",
   "country": "中国",
   "location":{
     "lat":"29.084661",
     "lon":"111.335210"
   }
}

所以可以使用POST的方式进行更新,只需要将待修改的字段列出来即可;

POST twitter/_update/1
{
  "doc": {
    "city": "成都",
    "province": "四川"
  }
}

先查询再更新_update_by_query


POST twitter/_update_by_query
{
  "query": {
    "match": {
      "user": "GB"
    }
  },
  "script": {
    "source": "ctx._source.city = params.city;ctx._source.province = params.province;ctx._source.country = params.country",
    "lang": "painless",
    "params": {
      "city": "上海",
      "province": "上海",
      "country": "中国"
    }
  }
}

修改一个文档,如果当前文档不存在则新增该文档


doc_as_upsert参数检查具有给定ID的文档是否已经存在,并将提供的doc与现有文档合并。 如果不存在具有给定ID的文档,则会插入具有给定文档内容的新文档。

下面的示例使用doc_as_upsert合并到ID为3的文档中,或者如果不存在则插入一个新文档:

POST /catalog/_update/3
{
     "doc": {
       "author": "Albert Paro",
       "title": "Elasticsearch 5.0 Cookbook",
       "description": "Elasticsearch 5.0 Cookbook Third Edition",
       "price": "54.99"
      },
     "doc_as_upsert": true
}

检查文档是否存在

    
HEAD twitter/_doc/1

删除一个文档

DELETE twitter/_doc/1

搜索并删除_delete_by_query

POST twitter/_delete_by_query
{
  "query": {
    "match": {
      "city": "上海"
    }
  }
}

_bulk批量操作

使用_bulk可以执行批量的数据插入,批量的数据更新,批量的数据删除,

批量的数据插入,使用index类型,表示存在即更新,不存在则新增

POST _bulk
{ "index" : { "_index" : "twitter", "_id": 1} }
{"user":"双榆树-张三","message":"今儿天气不错啊,出去转转去","uid":2,"age":20,"city":"北京","province":"北京","country":"中国","address":"中国北京市海淀区","location":{"lat":"39.970718","lon":"116.325747"}}
{ "index" : { "_index" : "twitter", "_id": 2 }}
{"user":"东城区-老刘","message":"出发,下一站云南!","uid":3,"age":30,"city":"北京","province":"北京","country":"中国","address":"中国北京市东城区台基厂三条3号","location":{"lat":"39.904313","lon":"116.412754"}}

批量数据插入,使用 create类型,id不存在则插入,存在则抛异常不作任何操作
POST _bulk
{ "create" : { "_index" : "twitter", "_id": 1} }
{"user":"双榆树-张三","message":"今儿天气不错啊,出去转转去","uid":2,"age":20,"city":"北京","province":"北京","country":"中国","address":"中国北京市海淀区","location":{"lat":"39.970718","lon":"116.325747"}}

批量数据删除 ,delete类型
POST _bulk
{ "delete" : { "_index" : "twitter", "_id": 1 }}


批量数据更新
POST _bulk
{ "update" : { "_index" : "twitter", "_id": 2 }}
{"doc": { "city": "长沙"}}

系统命令

查看ES信息
GET /


关闭索引(关闭索引后,将阻止读/写操作)
POST twitter/_close
开启索引
POST twitter/_open


冻结索引(冻结索引后,该索引将阻止写操作)

POST twitter/_freeze

索引冻结后,搜索时需加上ignore_throttled=false参数来进行搜索
POST twitter/_search?ignore_throttled=false

索引解冻
POST twitter/_unfreeze

原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94

ElasticSearch,Search操作

query进行全局搜索,aggregation可以进行全局的数据统计和分析

搜索所有文档

搜索该cluster下的所有index,默认返回10个
GET /_search  =  GET /_all/_search
GET /_search?size=20

同时对多个index进行搜索
POST /index1,index2,index3/_search


针对所有以index为开头的索引来进行搜索,但是排除index3索引
POST /index*,-index3/_search


只搜索索引名为twitter的索引
GET twitter/_search

搜索后设置只返回指定的字段

使用_source表示只返回 user,和city字段

GET twitter/_search
{
  "_source": ["user", "city"],
  "query": {
    "match_all": {
    }
  }
}

设置_source 为false表示不返回任何的_source信息
GET twitter/_search
{
  "_source": false,
  "query": {
    "match": {
      "user": "张三"
    }
  }
}

使用通配符的方式表示只返回 user*以及location*的数据,但是对于*.lat字段则不进行返回
GET twitter/_search
{
  "_source": {
    "includes": [
      "user*",
      "location*"
    ],
    "excludes": [
      "*.lat"
    ]
  },
  "query": {
    "match_all": {}
  }
}

创造返回字段script_fields

当我们的想要获取的field可能在_source里根本没有时,那么我们可以使用script field来生成这些field;


GET twitter/_search
{
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "years_to_100": {
      "script": {
        "lang": "painless",
        "source": "100-doc['age'].value"
      }
    },
    "year_of_birth":{
      "script": "2019 - doc['age'].value"
    }
  }
}

返回结果是:
    "hits" : [
      {
        "_index" : "twitter",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "fields" : {
          "years_to_100" : [
            80
          ],
          "year_of_birth" : [
            1999
          ]
        }
      },
      {
        "_index" : "twitter",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "fields" : {
          "years_to_100" : [
            70
          ],
          "year_of_birth" : [ 
            1989
          ]
        }
      },
    ...
  ]

必须注意的是这种使用script的方法来生成查询的结果对于大量的文档来说,可能会占用大量资源。

match和term的区别解释

term是代表完全匹配,也就是精确查询,搜索前不会再对所要搜索的词进行分词拆解。

https://www.jianshu.com/p/d5583dff4157

而使用match进行搜索的时候,会先将所要搜索的词进行分词拆分,拆完后再来进行匹配;

创建一个索引数据结构mapping

后续的演示都是基于如下的结构进行的演示;

PUT twitter/_mapping
{
  "properties": {
    "address": {
      "type": "text",
      "fields": {
        "keyword": {
          "type": "keyword",
          "ignore_above": 256
        }
      }
    },
    "age": {
      "type": "long"
    },
    "city": {
      "type": "text",
      "fields": {
        "keyword": {
          "type": "keyword",
          "ignore_above": 256
        }
      }
    },
    "country": {
      "type": "text",
      "fields": {
        "keyword": {
          "type": "keyword",
          "ignore_above": 256
        }
      }
    },
    "location": {
      "type": "geo_point"
    },
    "message": {
      "type": "text",
      "fields": {
        "keyword": {
          "type": "keyword",
          "ignore_above": 256
        }
      }
    },
    "province": {
      "type": "text",
      "fields": {
        "keyword": {
          "type": "keyword",
          "ignore_above": 256
        }
      }
    },
    "uid": {
      "type": "long"
    },
    "user": {
      "type": "text",
      "fields": {
        "keyword": {
          "type": "keyword",
          "ignore_above": 256
        }
      }
    }
  }
}

查询数据(Match query 搜索词分词匹配)

搜索 twitter索引中user字段为“朝阳区-老贾”的词;(注意,此处我们使用的是match,也就是会将我们的“朝阳区-老贾”按照默认分词器 分词后再进行相关搜索匹配)

GET twitter/_search
{
 "query": {
   "match": {
     "user": "朝阳区-老贾"
   }
 }
}

当我们使用上述的 match query 进行查询的时候,默认的操作是OR的关系,比如上述的DSL语句实际上于以下语句是对等的:

GET twitter/_search
{
  "query": {
    "match": {
      "user": {
        "query": "朝阳区-老贾",
        "operator": "or"
      }
    }
  }
}

默认的match query的操作就是or的关系:上述的dsl语句的查询结果是任何的文档只要匹配:
“朝”,“阳”,“区”,“老”及“贾”这5个字中的任何一个字,则都将会被匹配到;

默认情况下我们指定搜索的词,如果不指定分词器的话,也就是使用的默认分词器,默认分词器在进行中文分词的时候,就只是会把对应的中文词进行一个一个拆开进行分词,所以我们上述由于是使用的match,检索的“朝阳区-老贾”所以分词后的结果就是“朝“阳”区”“老”贾”;所以只要是文档user中存在这几个词中的任何一个,则都会被检索出来

设置最少匹配的词的数量minimum_should_match

使用minimum_should_match来设置至少匹配的索引词term,也就是说我们的搜索结果中:
至少要匹配到:
“朝”,“阳”,“区”,“老”及“贾这5个中的3个字才可以

GET twitter/_search
{
  "query": {
    "match": {
      "user": {
        "query": "朝阳区-老贾",
        "operator": "or",
        "minimum_should_match": 3
      }
    }
  }
}

更改为and关系的match query

默认情况下我们的match query是or的关系,这个上述已经说明过了,不过我们也可以动态的将其更改为and的关系,比如,如下的dsl语句:

GET twitter/_search
{
  "query": {
    "match": {
      "user": {
        "query": "朝阳区-老贾",
        "operator": "and"
      }
    }
  }
}

更改为and关系后,也就是每个分词后的结果都是and的关系,及我们的分词结果
“朝”,“阳”,“区”,“老”“贾”这几个词之间都是and关系,也就是说,我们的搜索结果中是必须要包含这几个词,才可以;

这种写法,其实和直接使用 term 很相似,因为使用term的搜索词,则都不会进行分词处理,默认是必须精确匹配,所以对于上述的这种使用场景,直接使用term效率会更高一些,省略了match的分词这个步骤,并且结果也都是获取精确匹配后的结果;

Multi_query(匹配多个字段)

在上述的搜索中,我们都是特别的指明了个一个user字段来进行的搜索查询,但是在实际使用中,我们可能并不知道那个字段含有这个关键词,所以对于这种情况下则可以使用multi_query来进行搜索;

GET twitter/_search
{
  "query": {
    "multi_match": {
      "query": "朝阳",
      "fields": [
        "user",
        "address^3",
        "message"
      ],
      "type": "best_fields"
    }
  }
}


上述同时对三个fields: user,adress及message进行搜索,
同时对address含有 “朝阳” 的文档的分数进行3倍的加权;
加权的作用是为了计算返回结果的相似度的值,比如此处对“address”进行了3倍的加权,那么此时如果是address中包含“朝阳时”所对应的返回结果的相似度就相对最高,所对应的排名的顺序就越靠前;

默认情况下如果不使用order by进行排序的话,则全部是按照相似度的高低来进行排序的;

Prefix query(只匹配前缀)


返回在提供的字段中包含特定前缀的文档。(是只匹配前缀,)

GET twitter/_search
{
  "query": {
    "prefix": {
      "user": {
        "value": "朝"
      }
    }
  }
}

Term query(精确匹配)

Term query会在给定字段中进行精确的字词匹配,搜索前不会再对搜索词进行分词拆解。

GET twitter/_search
{
  "query": {
    "term": {
      "user.keyword": {
        "value": "朝阳区-老贾"
      }
    }
  }
}

Term query 是精确匹配一个字词,如果是匹配多个字词,则将会无效,如下所示:
查询“朝阳区“空格”老贾“ 因为有空格的存在,所以默认情况下这是两个词了,所以对于搜索的结果,
可能会是无效的(未验证,待验证具体效果是否真的如此)

GET twitter/_search
{
  "query": {
    "term": {
      "user.keyword": {
        "value": "朝阳区 老贾"
      }
    }
  }
}

Terms query(多个词同时精确匹配)

所以,对于两个词的精确匹配,则应该是使用 terms

使用terms进行匹配,默认情况下是精确匹配对应的多个词,并且是OR的关系;也就是说:只要匹配“朝阳区”或者“老贾”则都算是匹配完成;

所以,此处使用term解决了多精确匹配的问题,但是如果是对于要精确匹配到“朝阳区 老贾”这样一个词的场景的话,则使用Terms也是不合适的,此时则需要使用boot query进行must 的 term and term的判断了;

所以其实这也就是一个对应的场景问题了,如果在录入这个数据的时候是按照“朝阳区-老贾”而不是空格来录入的话,那么查询起来则也就方便很多了,但其实不同的query查询器,对应的应用场景则也是各有好处和优劣了;

GET twitter/_search
{
  "query": {
    "terms": {
      "user.keyword": [
        "朝阳区",
        "老贾
      ]
    }
  }
}

city在我们的mapping中是一个multi-field项。它既是text也是keyword类型。对于一个keyword类型的项来说,这个项里面的所有字符都被当做一个字符串。它们在建立文档时,不需要进行index。keyword字段用于精确搜索,aggregation和排序(sorting),所以我们此处也是使用的term来进行的匹配;

bool query(复合查询)

符合查询通过将上述的多个查询方式组合起来,从而形成更大的复杂的查询逻辑,
bool query的查询格式一般情况下是:


must: 必须匹配。贡献算分 (多个term之间是 and 关系)
must_not:过滤子句,必须不能匹配,但不贡献算分 (and 关系)
should: 选择性匹配,至少满足一条。贡献算分 (多个term之间是or的关系)
filter: 过滤子句,必须匹配,但不贡献算分(filter 确定是否包含在检索结果中,后续再针对性说明)

POST _search
{
  "query": {
    "bool" : {
      "must" : {
        "term" : { "user" : "kimchy" }
      },
      "filter": {
        "term" : { "tag" : "tech" }
      },
      "must_not" : {
        "range" : {
          "age" : { "gte" : 10, "lte" : 20 }    
        }
      },
      "should" : [
        { "term" : { "tag" : "wow" } },
        { "term" : { "tag" : "elasticsearch" } }
      ],
      "minimum_should_match" : 1,
      "boost" : 1.0
    }
  }
}

表示查询,user字段既包含 朝阳区 又包含老贾的词,然后进行返回

GET twitter/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "user": "朝阳区"
          }
        },
        {
          "match": {
            "user": "老贾"
          }
        }
      ]
    }
  }
}

以下dsl的意思是,age必须是30岁,但是如果文档里含有“Hanppy birthday”,相关性会更高,那么搜索得到的结果会排在前面;

GET twitter/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "age": "30"
          }
        }
      ],
      "should": [
        {
          "match_phrase": {
            "message": "Happy birthday"
          }
        }
      ]
    }
  }
}

如果是在不使用must,must_not以及filter的情况下,直接使用should进行匹配,则是表示 or 的关系;一个或者更多的should必须有一个匹配才会有搜索结果;

query range(范围查询)


查询年龄介于30到40岁的文档数据

GET twitter/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 30,
        "lte": 40
      }
    }
  }
}

查询字段是否存在(query exists)

如果文档里只要city这个字段不为空,那么就会被返回。反之,如果一个文档里city这个字段是空的,那么就不会返回。

GET twitter/_search
{
  "query": {
    "exists": {
      "field": "city"
    }
  }
}

匹配短语(query match_phrase)

query match_phrase 要求所有的分词必须同时出现在文档中,同时位置必须紧邻一致,
使用slop 1表示Happy和birthday之前是可以允许一个 词 的差别。

GET twitter/_search
{
  "query": {
    "match_phrase": {
      "message": {
        "query": "Happy birthday",
        "slop": 1
      }
    }
  },
  "highlight": {
    "fields": {
      "message": {}
    }
  }
}

Profile API

Profile API是调试工具。 它添加了有关执行的详细信息搜索请求中的每个组件。 它为用户提供有关搜索的每个步骤的洞察力请求执行并可以帮助确定某些请求为何缓慢。

GET twitter/_search
{
  "profile": "true", 
  "query": {
    "match": {
      "city": "北京"
    }
  }
}

在上面,我们加上了"profile":"true"后,除了显示搜索的结果之外,还显示profile的信息:

  "profile" : {
    "shards" : [
      {
        "id" : "[ZXGhn-90SISq1lePV3c1sA][twitter][0]",
        "searches" : [
          {
            "query" : [
              {
                "type" : "BooleanQuery",
                "description" : "city:北 city:京",
                "time_in_nanos" : 1390064,
                "breakdown" : {
                  "set_min_competitive_score_count" : 0,
                  "match_count" : 5,
                  "shallow_advance_count" : 0,
                  "set_min_competitive_score" : 0,
                  "next_doc" : 31728,
                  "match" : 3337,
                  "next_doc_count" : 5,
                  "score_count" : 5,
                  "compute_max_score_count" : 0,
                  "compute_max_score" : 0,
                  "advance" : 22347,
                  "advance_count" : 1,
                  "score" : 16639,
                  "build_scorer_count" : 2,
                  "create_weight" : 342219,
                  "shallow_advance" : 0,
                  "create_weight_count" : 1,
                  "build_scorer" : 973775
                },
                "children" : [
                  {
                    "type" : "TermQuery",
                    "description" : "city:北",
                    "time_in_nanos" : 107949,
                    "breakdown" : {
                      "set_min_competitive_score_count" : 0,
                      "match_count" : 0,
                      "shallow_advance_count" : 3,
                      "set_min_competitive_score" : 0,
                      "next_doc" : 0,
                      "match" : 0,
                      "next_doc_count" : 0,
                      "score_count" : 5,
                      "compute_max_score_count" : 3,
                      "compute_max_score" : 11465,
                      "advance" : 3477,
                      "advance_count" : 6,
                      "score" : 5793,
                      "build_scorer_count" : 3,
                      "create_weight" : 34781,
                      "shallow_advance" : 18176,
                      "create_weight_count" : 1,
                      "build_scorer" : 34236
                    }
                  },
                  {
                    "type" : "TermQuery",
                    "description" : "city:京",
                    "time_in_nanos" : 49929,
                    "breakdown" : {
                      "set_min_competitive_score_count" : 0,
                      "match_count" : 0,
                      "shallow_advance_count" : 3,
                      "set_min_competitive_score" : 0,
                      "next_doc" : 0,
                      "match" : 0,
                      "next_doc_count" : 0,
                      "score_count" : 5,
                      "compute_max_score_count" : 3,
                      "compute_max_score" : 5162,
                      "advance" : 15645,
                      "advance_count" : 6,
                      "score" : 3795,
                      "build_scorer_count" : 3,
                      "create_weight" : 13562,
                      "shallow_advance" : 1087,
                      "create_weight_count" : 1,
                      "build_scorer" : 10657
                    }
                  }
                ]
              }
            ],
            "rewrite_time" : 17930,
            "collector" : [
              {
                "name" : "CancellableCollector",
                "reason" : "search_cancelled",
                "time_in_nanos" : 204082,
                "children" : [
                  {
                    "name" : "SimpleTopScoreDocCollector",
                    "reason" : "search_top_hits",
                    "time_in_nanos" : 23347
                  }
                ]
              }
            ]
          }
        ],
        "aggregations" : [ ]
      }
    ]
  }

从上面我们可以看出来,这个搜索是搜索了“北”及“京”,而不是把北京作为一个整体来进行搜索的。我们可以在以后的文档中可以学习使用中文分词器来进行分词搜索。有兴趣的同学可以把上面的搜索修改为city.keyword来看看。

filter查询和query查询的不同:

https://blog.csdn.net/laoyang360/article/details/80468757

如何在搜索的时候指定分词器

如何指定分词器进行字段的使用,

如何调整java rest client api的线程数,还有全局client api的异常处理的捕获,超时时间等

es 的集群负载方式,节点,副本等,以及节点掉线后的数据恢复等(主要是全部节点都掉线后,如何恢复数据?这个应该也不是重点)另外是,如何进行es集群的迁移,比如把现有集群的数据迁移到另外一个集群中,比如升级等操作时,这些可以在elastic 官网上关于es的2.x的版本介绍中有相关的集群等运维等的说明;
还有垃圾回收器等的说明,这个在文档中:《不要触碰这些配置中都有详细的说明》但这些也都的确是需要了解并熟悉的;
https://www.elastic.co/guide/cn/elasticsearch/guide/current/dont-touch-these-settings.html

原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94

ElasticSearch,分词器analyzer

ElasticSearch3 分词器 analyzer
analyzer分析器将输入的字符流分解为token的过程,主要是发生在两个场合:
1、在index创建索引的时候(也就是在向index中创建文档数据的时候)
2、在search的时候,也就是在搜索时,分析需要搜索的词语;

analyzer 是es在文档存储之前对文档正文内容执行的过程,以添加到反向索引中;在将文档添加到索引之前,es会为每个待分析的字段执行对应的analyzer步骤;

如:我们此时定制化一个新的analyzer,分别是由:character 过滤器,标准的tokenizer及token filter组成;
下图表达了一段原始文本,在经过analyzer分析时的整个过程;

最后在经过了一系列分析后,将所对应的分析后的结果,则添加到对应的反向索引中,如图1的步骤所示;

analyzer分析器的组成

analyzer分析器,常见的分为三个部分组成:

  1. Char Filter:字符过滤器的工作是执行清除任务,例如剥离HTML标记。
  2. Tokenizer:下一步是将文本拆分为称为标记的术语。 这是由tokenizer完成的。 可以基于任何规则(例如空格)来完成拆分
  3. Token Filter:一旦创建了token,它们就会被传递给token filter,这些过滤器会对token进行规范化。 Token filter可以更改token,删除术语或向token添加术语。

很重要哦:Elasticsearch已经提供了比较丰富的analyzer分析器。我们可以自己创建自己的token analyzer,甚至可以利用已经有的char filter,tokenizer及token filter来重新组合成一个新的analyzer,并可以对文档中的每一个字段分别定义自己的analyzer。

在默认情况下,ES中所使用的分析器是standard analyzer分析器;
standard analyzer分析器所使用的特征是:
1、没有Char Filter
2、使用 standard tokonzer
3、把对应字符串转换为小写,同时有选择性的删除一些stop words(停顿词);默认情况下stop words 为 none,及不过滤任何的stop words(停顿词)

下面另外一张图说明了,不同的analyzers分析器,对于token的拆分情况:

https://elasticstack.blog.csdn.net/article/details/100392478

https://blog.csdn.net/UbuntuTouch/article/details/100516428

https://blog.csdn.net/UbuntuTouch/article/details/100697156

相关