1. Elasticsearch核心概念和原理

1.1 Elasticsearch 特点

分布式、高性能、高可用、可伸缩、易维护 Elasticsearch≈搜索引擎

  1. 分布式的搜索,存储数据分析引擎
  2. 面向开发者友好,屏蔽了Lucene的复杂特性,集群自动发现(cluster discovery)
  3. 自动维护数据在多个节点上的建立
  4. 会帮我做搜索请求的负载均衡
  5. 自动维护冗余副本,保证了部分节点宕机的情况下仍然不会有任何数据丢失
  6. Elasticsearch基于Lucene提供了很多高级功能:复合查询、聚合分析、基于地理位置等。
  7. 对于大公司,可以构建几百台服务器的大型分布式集群,处理PB级别数据;对于小公司,开箱即用,门槛低上手简单
  8. 相遇传统数据库,提供了全文检索,同义词处理(美丽的cls>漂亮的cls),相关度排名。聚合分析以及海量数据的近实时(NTR)处理,这些传统数据库完全做不到。

    1.2 Elasticsearch 的使用场景

  9. 百度(全文检索、高亮、搜索推荐)

  10. 各大网站的用户行为日志(用户点击、浏览、收藏、评论)
  11. BI(Business Intelligence商业智能),数据分析:数据挖掘统计。
  12. Github:代码托管平台,几千亿行代码
  13. ELK:Elasticsearch(数据存储)、Logstash(日志采集)、Kibana(可视化)

    1.3 Elasticsearch 核心概念

  14. node(节点),就是一个一个Elasticsearch实例,只不过由于他们在一个集群中的,所以每个Elasticsearch是一个节点。

  15. cluster(集群):由多个node节点组成 每个集群至少包含两个节点.
  16. doc(文档):Elasticsearch中最小的数据单元,类似于关系型数据库中的表。但是在Elasticsearch中表现为JSON格式。这很像MongoDB。
  17. field(字段):一个数据字段,与index和type一起,可以定位一个doc
  18. type(类型):逻辑上的数据分类,es 7.x中删除了type的概念
  19. index(索引):索引,一类相同或者类似的doc,比如一个员工索引,商品索引。
  20. shard分片:
    1. 一个index包含多个shard,默认5个primary-shard,默认每个primary-shard分配一个read-shard,P的数量在创建索引的时候设置,如果想修改,需要重建索引。
    2. 每个shard都是一个Lucene实例,有完整的创建索引的处理请求能力。
    3. Elasticsearch会自动在nodes上为我们做shard均衡。
    4. 一个doc是不可能同时存在于多个primary-shard中的,但是可以存在于多个read-shard中。
    5. primary-shard和对应的read-shard不能同时存在于同一个节点,所以最低的可用配置是两个节点,互为主备。

1.4 全文检索

我们生活中的数据总体分为2种:结构化数据和非结构化数据。

  • 结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等。
  • 非结构化数据:又叫做**全文数据**,指不定长或无固定格式的数据,如邮件,word文档等。

当然有的地方还会提到第三种,半结构化数据,如XML,HTML等,当根据需要可按结构化数据来处理,也可抽取出纯文本按非结构化数据来处理。

按照数据的分类,搜索也分为2种:
对结构化数据的搜索
如对数据库的搜索,用SQL语句。再如对元数据的搜索,如利用windows搜索对文件名,类型,修改时间进行搜索等。
对非结构化数据的搜索
由于非结构化数据没有规律性,所以如windows的搜索文件内容,Linux下的grep命令,这些都是顺序扫描,搜索目标集越大,性能越慢。例如在windows中如果你有一个80G硬盘,如果想在上面找到一个内容包含某字符串的文件,不花他几个小时,怕是做不到。Linux下的grep命令也是这一种方式。大家可能觉得这种方法比较原始,但对于小数据量的文件,这种方法还是最直接,最方便的。但是对于大量的文件,这种方法就很慢了。

所以需要一个更好的检索方式, 全文检索就提供了这么一个思路,全文检索的基本思路是:**将非结构化数据中的关键信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。** 这些重新组织的信息,我们称之**索引**

**这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。**

image.png

1.4 倒排索引

倒排索引和正排索引的区别:
正排索引:**正排索引以每个记录的id为索引**,对应的是id所对应的记录。 查找记录内某个内容时,需要遍历整个id库,从而找到id对应的记录匹配到某个内容。 正排索引的特点是插入速度快,查找性能差。正排索引的实现:mysql、oracle等关系型数据库。

倒排索引:**倒排索引以每个关键词为索引**,对应的的关键词在所有记录中出现的次数的列表,查找某个关键词时可以直接命中对应的记录。但是由于插入时要维护关键词和文档的列表。所以插入性能会很慢。

image.png

2. 安装环境

(Windows和Linux:课件中我整理了图文安装教程,如果遇到问题可以自行百度,难度不大,实在不行也可以问我,这里就不演示步骤了)

2.1 安装Elasticsearch

假设:

  • Elasticsearch的解压目录为{ES_source_path}
  1. 下载

请进官网下载:http://elastic.co/
国内镜像:https://mirrors.huaweicloud.com/elasticsearch

  1. 解压

    1. $ tar xf elasticsearch-7.11.1-linux-x86_64.tar.gz
  2. 启动Elasticsearch

    1. $ {ES_source_path}/bin/elasticsearch

    或者

    1. $ {ES_source_path}/bin/elasticsearch -d

    “-d”是后台启动

  3. 验证

打开浏览器输入:http://{Elasticsearch安装服务器的ip}:9200, 看到一串json表示安装成功

  1. {
  2. "name" : "iZ2ze8b3wpoxtvocjln8osZ",
  3. "cluster_name" : "elasticsearch",
  4. "cluster_uuid" : "PObK89hjTWyUmlVvmF1M7g",
  5. "version" : {
  6. "number" : "7.11.1",
  7. "build_flavor" : "default",
  8. "build_type" : "tar",
  9. "build_hash" : "ff17057114c2199c9c1bbecc727003a907c0db7a",
  10. "build_date" : "2021-02-15T13:44:09.394032Z",
  11. "build_snapshot" : false,
  12. "lucene_version" : "8.7.0",
  13. "minimum_wire_compatibility_version" : "6.8.0",
  14. "minimum_index_compatibility_version" : "6.0.0-beta1"
  15. },
  16. "tagline" : "You Know, for Search"
  17. }

【安装Elasticsearch注意事项】:

  1. 安装Elasticsearch之前必须安装JDK,因为Elasticsearch是依赖java的。
  2. 系统环境变量中必须要有“JAVA_HOME
  3. Elasticsearch不能使用root账号启动,所以在linux中必须创建一个非root账号来启动Elasticsearch。

假设:Elasticsearch解压目录为: /root/soft/elasticsearch/elasticsearch-7.11.1
a. 创建用户 {user_name}

  1. $ adduser {user_name}

b. 创建用户密码,需要输入两次

  1. $ passwd {user_name}

c. 将对应的文件夹权限赋予给创建的账户(根目录开始就要给)

  1. $ chown -R {user_name}:{user_name} /root

d. 切换至{user_name}用户

  1. $ su {user_name}

e. 启动elasticsearch

  1. $ /root/soft/elasticsearch/elasticsearch-7.11.1/bin/elasticsearch -d
  1. 报错“max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]”

a. 切换到root用户,执行命令:

  1. $ sysctl -w vm.max_map_count=262144

b. 查看结果:

  1. $ sysctl -a|grep vm.max_map_count
  2. $ vm.max_map_count = 262144

c. 永久解决办法
  在/etc/sysctl.conf文件最后添加一行

  1. vm.max_map_count=262144

  执行“sysctl -p

  1. $ sysctl -p
  1. 无法外网访问9200端口
    1. 首先确定linux是否开启了防火墙,如果是阿里云ECS请打开9200端口
    2. 因为Elasticsearch监听的事本地回环端口,所以只能本地访问。解决方法:
      1. $ vim {ES_source_path}/conf/elasticsearch.yml
      在最下面加入两个配置:
      1. network.host: 0.0.0.0
      2. discovery.seed_hosts: ["127.0.0.1", "[::1]"]
      然后重启Elasticsearch即可。

Elasticsearch开发模式和生产模式
开发模式:
默认配置(未配置发现设置),用于学习阶段。在同一台机器上解压多个Elasticsearch并启动,Elasticsearch会自动发现并形成集群。

生产模式:
修改配置文件内容会触发Elasticsearch的引导检查(CPU、内存、线程、JVM、网络、磁盘等等),如果有一项不合格Elasticsearch会阻止启动,所以学习阶段不建议修改集群相关的配置。

Elasticsearch在生产环境中不建议使用docker部署,因为:

  1. Elasticsearch在生产环境中基本上是要使用整台机器的资源的。
  2. Elasticsearch的部署极其简单,不需要docker。

2.2 安装Kibana

(从版本6.0.0开始,Kibana仅支持64位操作系统。)

  1. 下载

https://www.elastic.co/cn/downloads/kibana
https://mirrors.huaweicloud.com/kibana

  1. 启动

依然是开箱即用
Linux:./kibana
Windows:.\kibana.bat

  1. 验证

http://localhost:5601

linux下不挂起运行:

  1. nohup /root/soft/kibana/kibana-7.11.1-linux-x86_64/bin/kibana > /dev/null 2>&1 &

如果windows版本启动时提示无法运行此程序,换个低版本的kibana即可。

2.3 安装elasticsearch-head插件(选装)

提供可视化的操作页面对Elasticsearch搜索引擎进行各种设置和数据检索功能,可以很直观的查看集群的健康状况,索引分配情况,还可以管理索引和集群以及提供方便快捷的搜索功能等等。
假设:

  • elasticsearch-head的下载目录为 {elasticsearch-head_downland_path}
  • elasticsearch-head的解压目录为 {elasticsearch-head_source_path}
  1. 下载elasticsearch-head

    1. $ git clone https://github.com/mobz/elasticsearch-head
  2. elasticsearch-head依赖于node和grunt管理工具,所以需要下载,已有请忽略。

    1. $ yum install node -y
    2. $ npm install -g grunt-cli
  3. 启动elasticsearch-head

    1. $ cd {elasticsearch-head_source_path}
    2. $ npm run start
  4. 验证:http://localhost:9100/

2.5 Elasticsearch基础操作

1、集群健康

  1. 健康值检查

    1. $ curl http://127.0.0.1:9200/_cat/health
    2. $ curl http://127.0.0.1:9200/_cluster/health
  2. 健康值状态说明

green:所有primary和replica均为active,集群健康
yellow:至少一个replica不可用,但是所有primary均为active,数据仍然是可以保证完整性的。
red:至少有一个primary为不可用状态,数据不完整,集群不可用。

2、基础CRUD

  1. 创建索引{index_name}

    1. $ curl -X PUT http://{elasticsearch-ip}:9200/{index_name}?pretty
    2. {
    3. "acknowledged" : true,
    4. "shards_acknowledged" : true,
    5. "index" : "product"
    6. }
  2. 查询索引:GET _cat/indices?v

    1. $ curl http://{elasticsearch-ip}:9200/_cat/health?v
    2. epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
    3. 1614058851 05:40:51 elasticsearch green 1 1 0 0 0 0 0 0 - 100.0%
  3. 删除索引:DELETE /{index_name}?pretty

    1. $ curl -X DELETE http://{elasticsearch-ip}:9200/{index_name}?pretty
    2. {
    3. "acknowledged" : true
    4. }
  4. {index_name}索引下插入数据:

    1. $ curl -X PUT http://{elasticsearch-ip}:9200/{index_name}/_doc/{id}
    2. {
    3. "_index" : "product",
    4. "_type" : "_doc",
    5. "_id" : "10",
    6. "_version" : 1,
    7. "result" : "created",
    8. "_shards" : {
    9. "total" : 2,
    10. "successful" : 1,
    11. "failed" : 0
    12. },
    13. "_seq_no" : 25,
    14. "_primary_term" : 1
    15. }
  5. 更新数据

全量替换,就是再插入一遍数据,但是version会递增, result会变为updated

  1. $ curl -X PUT http://{elasticsearch-ip}:9200/{index_name}/_doc/{id} -d '{"name":"xiaomi phone","desc":"shouji zhong de zhandouji","price":3999,"tags":["xingjiabi","fashao","buka"]}' -H 'Content-Type:application/json'
  2. {
  3. "_index":"product",
  4. "_type":"_doc",
  5. "_id":"10",
  6. "_version":2,
  7. "result":"updated",
  8. "_shards":{
  9. "total":2,
  10. "successful":1,
  11. "failed":0
  12. },
  13. "_seq_no":26,
  14. "_primary_term":1
  15. }

指定字段更新

  1. 删除数据
    1. $ curl -X DELETE http://{Elasticsearch-ip}:9200/{index_name}/_doc/{id}

3. Elasticsearch分布式文档系统

3.1 Elasticsearch如何实现高可用

  • Elasticsearch在分配单个索引的分片时会将每个分片尽可能分配到更多的节点上。但是,实际情况取决于集群拥有的分片和索引的数量以及它们的大小,不一定总是能均匀地分布。
  • Elasticsearch不允许primary和它的replica放在同一个节点中,并且同一个节点不接受完全相同的两个replica

    3.2 容错机制

  1. 主节点(master-node)
    1. 主节点,每个集群都有且只有一个
    2. 尽量避免Master节点 node.data = true
  2. 投票节点(voting-node)

node.voting_only = true(仅投票节点,即使配置了data.master = true,也不会参选, 但是仍然可以作为数据节点)。

  1. 协调节点(coordinating-node)

每一个节点都隐式的是一个协调节点,如果同时设置了data.master = falsedata.data=false,那么此节点将成为仅协调节点。

  1. master候选节点(master-eligible-node)
  2. 数据节点(data-node)
  3. Ingest node
  4. 机器学习节点(machine-learning-node)

脑裂问题:
图片.png
图片.png

3.3 关于master节点和data节点

节点 node.master node.data 说明
master和data true true 这是Elasticsearch节点默认配置,既作为候选节点又作为数据节点,这样的节点一旦被选举为master,压力是比较大的,通常来说master节点应该只承担较为轻量级的任务,比如创建删除索引,分片均衡等。
master候选节点 true false 只作为候选节点,不作为数据节点,可参选master节点,当选后成为真正的master节点。
仅协调节点 false false 既不当候选节点,也不作为数据节点,那就是仅协调节点,负责负载均衡
数据节点 false true 不作为候选节点,但是作为数据节点,这样的节点主要负责数据存储和查询服务。

3.4 图解容错机制

  1. 第一步:master选举(假如宕机节点是master)

脑裂:可能会产生多个Master节点
解决:discovery.zen.minimum_master_nodes=N/2+1

  1. 第二步:

replica容错,新的(或者原有)master节点会将丢失的primary对应的某个副本提升为primary

  1. 第三步

master节点会尝试重启故障机

  1. 第四步

数据同步,master会将宕机期间丢失的数据同步到重启机器对应的分片上去

1. 安装与基本语法 - 图7

3.5 总结

如何提高Elasticsearch分布式系统的可用性以及性能最大化:

  1. 每台节点的shard数量越少,每个shard分配的CPU、内存和IO资源越多,单个shard的性能越好,当一台机器一个shard时,单个shard性能最好。
  2. 稳定的master节点对于群集健康非常重要!理论上讲,应该尽可能的减轻master节点的压力,分片数量越多,master节点维护管理shard的任务越重,并且节点可能就要承担更多的数据转发任务,可增加“仅协调”节点来缓解master节点和data节点的压力,但是在集群中添加过多的仅协调节点会增加整个集群的负担,因为选择的主节点必须等待每个节点的集群状态更新确认。
  3. 反过来说,如果相同资源分配相同的前提下,shard数量越少,单个shard的体积越大,查询性能越低,速度越慢,这个取舍应根据实际集群状况和结合应用场景等因素综合考虑。
  4. 数据节点和master节点一定要分开,集群规模越大,这样做的意义也就越大。
  5. 数据节点处理与数据相关的操作,例如CRUD,搜索和聚合。这些操作是I/O,内存和CPU密集型的,所以他们需要更高配置的服务器以及更高的带宽,并且集群的性能冗余非常重要。
  6. 由于仅投票节不参与master竞选,所以和真正的master节点相比,它需要的内存和CPU较少。但是,所有候选节点以及仅投票节点都可能是数据节点,所以他们都需要快速稳定低延迟的网络。
  7. 高可用性(HA)群集至少需要三个主节点,其中至少两个不是仅投票节点。即使其中一个节点发生故障,这样的群集也将能够选举一个主节点。生产环境最好设置3台仅Master候选节点(node.master = true`` node.data = true
  8. 为确保群集仍然可用,集群不能同时停止投票配置中的一半或更多节点。只要有一半以上的投票节点可用,群集仍可以正常工作。这意味着,如果存在三个或四个主节点合格的节点,则群集可以容忍其中一个节点不可用。如果有两个或更少的主机资格节点,则它们必须都保持可用

4. Elasticsearch查询语法

4.1 timeout

假设用户查询结果有1W条数据,但是需要10s才能查询完毕,但是用户设置了1s的timeout,那么不管当前一共查询到了多少数据,都会在1s后Elasticsearch讲停止查询,并返回当前已查询到的数据。
默认没有timeout,如果设置了timeout,那么会执行timeout机制。

  1. GET /{index_name}/_search?timeout=1s/ms/m

4.2 Elasticsearch常用查询

  1. 插入一些测试用的小米手机产品数据 ```json PUT /product/_doc/1 { “name” : “xiaomi phone”, “desc” : “shouji zhong de zhandouji”, “price” : 3999, “tags”: [ “xingjiabi”, “fashao”, “buka” ] }

PUT /product/_doc/2 { “name” : “xiaomi nfc phone”, “desc” : “zhichi quangongneng nfc,shouji zhong de jianjiji”, “price” : 4999, “tags”: [ “xingjiabi”, “fashao”, “gongjiaoka” ] }

PUT /product/_doc/3 { “name” : “nfc phone”, “desc” : “shouji zhong de hongzhaji”, “price” : 2999, “tags”: [ “xingjiabi”, “fashao”, “menjinka” ] }

PUT /product/_doc/4 { “name” : “xiaomi erji”, “desc” : “erji zhong de huangmenji”, “price” : 999, “tags”: [ “low”, “bufangshui”, “yinzhicha” ] }

PUT /product/_doc/5 { “name” : “hongmi erji”, “desc” : “erji zhong de kendeji”, “price” : 399, “tags”: [ “lowbee”, “xuhangduan”, “zhiliangx” ] }

  1. <a name="aEVyA"></a>
  2. ## 4.3 RESTFul API查询
  3. 查询所有:
  4. ```sql
  5. select * from product;
  1. GET /product/_search

查询name中包含“xiaomi”的数据

  1. select * from product where name like "%xiaomi%";
  1. GET /product/_search?q=name:xiaomi

排序+分页:

  1. select * from product order by price asc limit 2,2;
  1. GET /product/_search?from=1&size=2&sort=price:asc

查询name中包含”xiaomi”的数据并排序后每2条数据为一页,获取第2页

  1. select * from product where name like "%xiaomi%" order by price asc limit 2,2;
  1. GET /product/_search?q=name:xiaomi&from=1&size=2&sort=price:asc

4.4 match分词匹配

  1. match_all:匹配所有

    1. #查询所有相关数据
    2. GET /product/_search
    3. {
    4. "query":{
    5. "match_all": {}
    6. }
    7. }
  2. match:分词匹配

    1. #查询name中与“nfc”相关的数据
    2. GET /product/_search
    3. {
    4. "query": {
    5. "match": {
    6. "name": "nfc"
    7. }
    8. }
    9. }
  3. multi_match:分词匹配多项

    1. #查询查询namedesc中都和“nfc”相关数据
    2. GET /product/_search
    3. {
    4. "query": {
    5. "multi_match": {
    6. "query": "nfc",
    7. "fields": ["name","desc"]
    8. }
    9. }
    10. }

    4.5 sort 排序

    1. #查询namedesc中都与“nfc”相关数据,并按照价格倒序排序
    2. GET /product/_search
    3. {
    4. "query": {
    5. "multi_match": {
    6. "query": "nfc",
    7. "fields": ["name","desc"]
    8. }
    9. },
    10. "sort": [
    11. {
    12. "price": {
    13. "order": "desc"
    14. }
    15. }
    16. ],
    17. }

    4.5 from+size 分页

    1. #查询namedesc中都和“nfc”的相关数据,并按照价格倒序排序后,每2条数据为一页,获取第二页数据
    2. GET /product/_search
    3. {
    4. "query": {
    5. "multi_match": {
    6. "query": "nfc",
    7. "fields": ["name","desc"]
    8. }
    9. },
    10. "sort": [
    11. {
    12. "price": {
    13. "order": "desc"
    14. }
    15. }
    16. ],
    17. "from": 1,
    18. "size": 2
    19. }

    deep paging

    假设有5万条数据,分散在5个shard里面,那个每个shard里面是1万条数据,现在要按照某个字段排序后按照每页10条分页,然后取出第500页的数据来。

    1. GET /product/_search
    2. {
    3. "query": {
    4. "multi_match": {}
    5. },
    6. "sort": [
    7. {
    8. "price": {
    9. "order": "desc"
    10. }
    11. }
    12. ],
    13. "from": 500,
    14. "size": 10
    15. }

    Elasticsearch经过计算得出每个分片要排序后获取0~500*100+10=5010条数据,然后把这些分片数据集合到一起再次进行排序,获取第5000~5010条数据。
    图片.png

deep pageing分页总结:

  • 当你的数据超过1W,不要使用deep pageing
  • 返回结果不要超过1000个,500以下为宜。

deep pageing解决办法:
使用scroll_search(只能下一页,没办法上一页,不适合实时查询)

scroll (deep paging优化)

第一次查询时url后面增加一个参数?scroll={超时时间}{超时时间单位m/h/}, 第一次查询的结果会返回一个scroll_id, 以后的查询只需要指定scroll_id就可以。

  1. 第1次查询:
    1. GET /order/_search?scroll=1m
    2. {
    3. "query": {
    4. "match": {
    5. "insuranceCompany": "平安"
    6. }
    7. },
    8. "_source": ["insuranceCompany","orderNo","premium"],
    9. "sort": [
    10. {
    11. "premium": {
    12. "order": "desc"
    13. }
    14. }
    15. ]
    16. }
  2. 以后的查询
    1. GET /_search/scroll?pretty
    2. {
    3. "scroll":"{超时时间}{超时时间单位}",
    4. "scroll_id":"{第一次scroll查询结果中的scroll_id}"
    5. }

需要注意:

  1. 如果遇到“No search context found for id [577]”错误请调大scroll等待时间重来。
  2. 以后的查询不要忘记加scroll

4.7 _source 取特定字段

  1. #只查询name中和“nfc”相关的数据的name字段和price字段。
  2. GET /product/_search
  3. {
  4. "query":{
  5. "match": {
  6. "name": "nfc"
  7. }
  8. },
  9. "_source": ["name","price"]
  10. }

4.7 range范围查询

类似于SQL里面的 between…and…

  1. GET baobei-product-pro-2021.04/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "filter": [
  6. {
  7. "range": {
  8. "time.keyword": {
  9. "format": "yyyy-MM-dd HH:mm:ss.SSS",
  10. "gte": "2021-04-15 10:00:09.000",
  11. "lte": "2021-04-15 10:00:10.999"
  12. }
  13. }
  14. }
  15. ]
  16. }
  17. }
  18. }

4.8 全文检索

match

  1. GET /product/_search
  2. {
  3. "query": {
  4. "match": {
  5. "name": "xiaomi nfc zhineng phone"
  6. }
  7. }
  8. }
  9. #验证分词
  10. GET /_analyze
  11. {
  12. "analyzer": "standard",
  13. "text":"xiaomi nfc zhineng phone"
  14. }

match_phrase

短语搜索,和全文检索相反,“nfc phone”会作为一个短语去检索

  1. GET /product/_search
  2. {
  3. "query": {
  4. "match_phrase": {
  5. "name": "nfc phone"
  6. }
  7. }
  8. }

4.9 term 不被分词匹配

  1. GET /product/_search
  2. {
  3. "query": {
  4. "term": {
  5. "name": "nfc"
  6. }
  7. }
  8. }

match和term区别:

  1. GET /product/_search
  2. {
  3. "query": {
  4. "term": {
  5. "name": "nfc phone" //这里因为没有分词,所以查询没有结果
  6. }
  7. }
  8. }
  9. GET /product/_search
  10. {
  11. "query": {
  12. "bool": {
  13. "must": [
  14. {"term":{"name":"nfc"}},
  15. {"term":{"name":"phone"}}
  16. ]
  17. }
  18. }
  19. }
  20. GET /product/_search
  21. {
  22. "query": {
  23. "terms": {
  24. "name":["nfc","phone"]
  25. }
  26. }
  27. }
  28. GET /product/_search
  29. {
  30. "query": {
  31. "match": {
  32. "name": "nfc phone"
  33. }
  34. }
  35. }

4.10 filter 查询和过滤

bool

bool可以组合多个查询条件,bool查询也是采用more_matches_is_better的机制,因此满足must和should子句的文档将会合并起来计算分值。bool可以和 mustmust_notshouldfilter组合使用

  1. must:必须满足

子句(查询)必须出现在匹配的文档中,并将有助于得分。

  1. must_not:必须不满足 不计算相关度分数

子句(查询)不得出现在匹配的文档中。子句在过滤器上下文中执行,这意味着计分被忽略,并且子句被视为用于缓存。由于忽略计分,0因此将返回所有文档的分数

  1. should:可能满足,类似于SQL中的or

子句(查询)应出现在匹配的文档中。

  1. filter 过滤器 不计算相关度分数,重要☆

子句(查询)必须出现在匹配的文档中。但是不像 must查询的分数将被忽略。filter子句在filter上下文中执行,这意味着计分被忽略,并且子句被考虑用于缓存。

  1. "minimum_should_match": {N}最少应该匹配N个。

bool案例

首先筛选name中包含“xiaomi phone”并且价格大于1999的数据(不排序),然后搜索name包含“xiaomi”并且 desc 中包含“shouji”

  1. GET /product/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {
  7. "match": { "name": "xiaomi" }
  8. },
  9. {
  10. "match": { "desc": "shouji" }
  11. }
  12. ],
  13. "filter": [
  14. {
  15. "match_phrase": { "name":"xiaomi phone"}
  16. },
  17. {
  18. "range": {
  19. "price": { "gt": 1999 }
  20. }
  21. }
  22. ]
  23. }
  24. }
  25. }

bool多条件 name包含xiaomi 不包含erji 描述里包不包含nfc都可以,价钱要大于等于4999

  1. GET /product/_search
  2. {
  3. "query": {
  4. "bool":{
  5. #name中必须包含“xiaomi
  6. "must": [
  7. {"match": { "name": "xiaomi"}}
  8. ],
  9. #name中必须不能包含“erji
  10. "must_not": [
  11. {"match": { "name": "erji"}}
  12. ],
  13. #should中至少满足0个条件,参见下面的minimum_should_match的解释
  14. "should": [
  15. {
  16. "match": {}
  17. }
  18. ],
  19. #筛选价格大于4999doc
  20. "filter": [
  21. {
  22. "range": {
  23. "price": { "gt": 4999 }
  24. }
  25. }
  26. ]
  27. }
  28. }
  29. }

bool嵌套查询:

1) minimum_should_match:参数指定should返回的文档必须匹配的子句的数量或百分比。如果bool查询包含至少一个should子句,而没有must或 filter子句,则默认值为1。否则,默认值为0

  1. GET /product/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {
  7. "match": {
  8. "name": "nfc"
  9. }
  10. }
  11. ],
  12. "should": [
  13. {
  14. "range": {
  15. "price": { "gt": 1999 }
  16. }
  17. },
  18. {
  19. "range": {
  20. "price": { "gt": 3999 }
  21. }
  22. }
  23. ],
  24. "minimum_should_match": 1
  25. }
  26. }
  27. }

bool案例:

  1. GET /product/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "filter": {
  6. "bool": {
  7. "should": [
  8. { "range": {"price": {"gt": 1999}}},
  9. { "range": {"price": {"gt": 3999}}}
  10. ],
  11. "must": [
  12. { "match": {"name": "nfc"}}
  13. ]
  14. }
  15. }
  16. }
  17. }
  18. }

(6) Compound queries:组合查询
① 想要一台带NFC功能的 或者 小米的手机 但是不要耳机

  1. SELECT
  2. *
  3. from product
  4. where
  5. (`name` like "%xiaomi%" or `name` like '%nfc%')
  6. AND `name` not LIKE '%erji%'
  1. GET /product/_search
  2. {
  3. "query": {
  4. "constant_score": {
  5. "filter": {
  6. "bool": {
  7. "should": [
  8. {
  9. "term": { "name": "xiaomi" }
  10. },
  11. {
  12. "term": { "name": "nfc" }
  13. }
  14. ],
  15. "must_not": [
  16. {
  17. "term": { "name": "erji" }
  18. }
  19. ]
  20. }
  21. },
  22. "boost": 1.2
  23. }
  24. }
  25. }

② 搜索一台“xiaomi nfc phone”或者一个加个区间在“399~2999”之间的“erji”

  1. select
  2. *
  3. from product
  4. where
  5. name like '%xiaomi nfc phone%'
  6. OR
  7. ( name like '%erji%' and price > 399 and price <=2999);
  1. GET /product/_search
  2. {
  3. "query": {
  4. "constant_score": {
  5. "filter": {
  6. "bool": {
  7. "should": [
  8. {
  9. "match_phrase": {
  10. "name": "xiaomi nfc phone"
  11. }
  12. },
  13. {
  14. "bool": {
  15. "must": [
  16. {
  17. "term": { "name": "phone" }
  18. },
  19. {
  20. "range": {
  21. "price": { "lte": "2999" }
  22. }
  23. }
  24. ]
  25. }
  26. }
  27. ]
  28. }
  29. }
  30. }
  31. }
  32. }

(7) Highlight search:

  1. GET /product/_search
  2. {
  3. "query": {
  4. "match_phrase": {
  5. "name": "nfc phone"
  6. }
  7. },
  8. "highlight": {
  9. "fields": {
  10. "name": {}
  11. }
  12. }
  13. }
  1. filter缓存原理:图解
[“Tom is a boy”,”Tom’s teacher is Jon”,”Jon works at school”,”Tom is a student too”]
Tom [1,1,0,1]
is [1,1,0,1]
a [1,0,0,1]
boy [1,0,0,0]
Jon [0,1,1,0]
hahahaha [0,0,0,0]

4.12 aggs 聚合查询

  1. 以applicantName分组查询相同的applicantName的总量。

    1. GET /product/_search?timeout=1000m
    2. {
    3. "aggs": {
    4. "{自定义聚合的名字}": {
    5. "terms": {
    6. "field": "applicantName.keyword"
    7. }
    8. }
    9. },
    10. "size": 0
    11. }
  2. 查询premium大于1小于2的数据,并以applicantName分组查询相同的applicantName的总量。

    1. GET /product/_search?timeout=1000m
    2. {
    3. "query": {
    4. "bool": {
    5. "filter": {
    6. "range": { //用range指定范围查询
    7. "premium": {
    8. "gte": 1,
    9. "lte": 2
    10. }
    11. }
    12. }
    13. }
    14. },
    15. "aggs": {
    16. "{自定义聚合的名字}": {
    17. "terms": {
    18. "field": "applicantName.keyword"
    19. }
    20. }
    21. },
    22. "size": 0
    23. }
  3. 平均价格

    1. #平均价格
    2. GET /product/_search
    3. {
    4. "aggs": {
    5. "tag_agg_avg": {
    6. "terms": {
    7. "field": "tags.keyword",
    8. "order": {
    9. "avg_price": "desc"
    10. }
    11. },
    12. "aggs": {
    13. "avg_price": {
    14. "avg": {
    15. "field": "price"
    16. }
    17. }
    18. }
    19. }
    20. },
    21. "size":0
    22. }
  4. 安保费分组后的平均保费

    1. GET /product/_search
    2. {
    3. "aggs": {
    4. "tag_agg_group": {
    5. "range": {
    6. "field": "price",
    7. "ranges": [
    8. {
    9. "from": 100,
    10. "to": 1000
    11. },
    12. {
    13. "from": 1000,
    14. "to": 3000
    15. },
    16. {
    17. "from": 3000
    18. }
    19. ]
    20. },
    21. "aggs": {
    22. "price_agg": {
    23. "avg": {
    24. "field": "price"
    25. }
    26. }
    27. }
    28. }
    29. },
    30. "size": 0
    31. }

    4.12 _mget批量查询

    _mget只支持元数据(metadata)查询, 一般常见的元数据有3个,分别是: _index(索引名字)、_type(文档类型)、_id(id)。
    所以可以写成下面这样:

    1. GET _mget
    2. {
    3. "docs":[
    4. {
    5. "_index" : "{索引名字}",
    6. "_type":"{类型名字}",
    7. "_id" : "{id}"
    8. },
    9. {
    10. "_index" : "{索引名字}",
    11. "_type":"{类型名字}",
    12. "_id" : "{id}"
    13. },
    14. {
    15. "_index" : "{索引名字}",
    16. "_type":"{类型名字}",
    17. "_id" : "{id}"
    18. }
    19. ]
    20. }

    只查询某些字段:

    1. GET _mget
    2. {
    3. "docs":[
    4. {
    5. "_index" : "order",
    6. "_type":"_doc",
    7. "_id" : "1",
    8. "_source":["proCode","rate","premium"] # 指定某些字段
    9. },
    10. {
    11. "_index" : "order",
    12. "_id" : "1",
    13. "_source":{
    14. "exclude":["userId"] #不包含某些字段
    15. }
    16. },
    17. {
    18. "_index" : "order",
    19. "_type":"_doc",
    20. "_id" : "1",
    21. "_source":{
    22. "include":["proCode","rate","premium"] # 指定某些字段
    23. }
    24. }
    25. ]
    26. }

    如果 exclude 和 include 同时出现时, Elasticsearch会执行inlcude操作

如果是一个索引内的批量查询可以在URL上指定索引名字

  1. GET {索引名字}/_mget
  2. {
  3. "docs":[
  4. { "_id" : "{id}", "_type":"{类型名字}" },
  5. { "_id" : "{id}", "_type":"{类型名字}" },
  6. { "_id" : "{id}", "_type":"{类型名字}" },
  7. { "_id" : "{id}", "_type":"{类型名字}" }
  8. ]
  9. }

如果是一个索引和一个类型内内的批量查询可以在URL上指定索引名字和类型名字

  1. GET {索引名字}/{类型名字}/_mget
  2. {
  3. "docs":[
  4. { "_id" : "{id}" },
  5. { "_id" : "{id}" },
  6. { "_id" : "{id}" },
  7. { "_id" : "{id}" }
  8. ]
  9. }

一个索引内、一个类型、一个id内的批量查询

  1. GET {索引名字}/{类型名字}/_mget
  2. {
  3. "ids":["{id}","{id}","{id}","{id}"]
  4. }

4.13 _bulk 批量操作

_bulk的模板为为:

  1. POST _bulk
  2. {"<action>":{"_index":"<索引名字>","_id":"<id>"}}\n
  3. {doc}\n
  4. {"<action>":{"_index":"<索引名字>","_id":"<id>"}}\n
  5. {doc}\n

action必须是下面4种之一

action 说明
index 如果文档不存在就创建他,如果文档存在就更新他
create 如果文档不存在就创建他,但如果文档存在就返回错误, 使用时一定要在metadata设置_id值,他才能去判断这个文档是否存在
update 更新一个文档,如果文档不存在就返回错误使用时也要给_id值,且后面文档的格式和其他人不一样,需要注意的是,使用update,下一行的文档内容需要用”doc“包起来。
delete 删除一个文档,如果要删除的文档id不存在,就返回错误使用时也必须在metadata中设置文档_id,且后面不能带一个doc,因为没意义,他是用_id去删除文档的

metadata : 设置这个文档的metadata,像是_id_index_type
doc : 就是一般的文档数据。

_bulk 批量插入

所以一个批量插入的demo是下面这样的:

  1. POST /_bulk
  2. {"index":{"_index":"order","_id":"1"}}
  3. {"age":"18","name":"wanfan1"}
  4. {"index":{"_index":"order","_id":"2"}}
  5. {"age":"19","name":"wanfan2"}
  6. {"index":{"_index":"order","_id":"3"}}
  7. {"age":"20","name":"wanfan3"}

_bulk批量修改

需要注意的是:update的内容要要在doc内,doc内才是你要修改的内容。

  1. POST _bulk
  2. { "update" : {"_id" : "1", "_index" : "index1", "retry_on_conflict" : 3} }
  3. { "doc" : {"field" : "value"} }
  4. { "update" : { "_id" : "0", "_index" : "index1", "retry_on_conflict" : 3} }
  5. { "script" : { "source": "ctx._source.counter += params.param1", "lang" : "painless", "params" : {"param1" : 1}}, "upsert" : {"counter" : 1}}
  6. { "update" : {"_id" : "2", "_index" : "index1", "retry_on_conflict" : 3} }
  7. { "doc" : {"field" : "value"}, "doc_as_upsert" : true }
  8. { "update" : {"_id" : "3", "_index" : "index1", "_source" : true} }
  9. { "doc" : {"field" : "value"} }
  10. { "update" : {"_id" : "4", "_index" : "index1"} }
  11. { "doc" : {"field" : "value"}, "_source": true}

_bulk文件

还可以把请求体数据写在文件里面:
**$ES_HOME**里,新建一文件,命名为requests.json

  1. $ cd $ES_HOME
  2. $ echo -e '{"index":{"_index":"order","_id":"1"}}\n{"age":"18","name":"wanfan1"}\n{"index":{"_index":"order","_id":"2"}}\n{"age":"19","name":"wanfan2"}\n{"index":{"_index":"order","_id":"3"}}\n{"age":"20","name":"wanfan3"}\n' > requests.json
  3. $ curl -H "Content-Type: application/json" -XPOST 'http://{ES服务器ip}:9200/_bulk' --data-binary @requests.json

_bulk需要注意的坑

  1. 数据必须是一行一行,不能换行,必须紧凑。
  2. 每一行必须跟随一个换行符,java代码可以用System.lineSeparator()获取。
  3. 请求头必须是Content-Type: application/x-ndjson
  4. 最后一行数据后面也需要一个换行符。
  5. 如果是delete操作,下一行不需要doc。
  6. 各种action可以混合使用。

**_bulk**文件方式插入
新建文件 person.json

  1. {"index":{"_index":"person","_type":"_doc","_id":451}}
  2. {"birdthday":"2000-04-23","color":"Yellow","create_time":"2010-07-09 04:44:46","name":"应民印","weight":70.03,"addr":"新疆乌鲁木齐市米东南路东四巷661号","age":42,"height":176.38,"tags":["易怒","勇敢","健谈","卑鄙"]}
  3. {"index":{"_index":"person","_type":"_doc","_id":452}}
  4. {"birdthday":"1973-06-14","color":"Yellow","create_time":"1985-03-01 05:44:46","name":"束禽油","weight":42.94,"addr":"安徽省安庆市大观区石狮路16号","age":43,"height":167.88,"tags":["胸无大志","贪小便宜","易怒","木讷","外向开朗","好吃懒做","温柔"]}

插入

  1. curl -H "Content-Type: application/json" -XPOST http://localhost:9200/_bulk --data-binary @person.json

4.14 constant_score 忽略TF/IDF算法

有时我们不需要TF/IDF。我们想知道的只是一个特定的单词是否出现在了字段中。比如我们搜索日志中出现的异常, 我们不需要相关度评分,我们只需要日志中包含“Exception”或者“异常”就可以了。 可以使用constant_score

  1. GET baobei-product-pro-2021.03/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "should": [
  6. {
  7. "constant_score": {
  8. "filter": {
  9. "match_phrase":{
  10. "msg":"Exception"
  11. }
  12. },
  13. "boost": 1.5 //Exception 这个比较重要,权重大一点
  14. }
  15. },
  16. {
  17. "constant_score": {
  18. "filter": {
  19. "match_phrase":{
  20. "msg":"异常"
  21. }
  22. },
  23. "boost": 1.2 //这个没上一个重要,权重小一点
  24. }
  25. }
  26. ]
  27. }
  28. }
  29. }

使用match_phrase不会被分词