04-ElasticSearch入门

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: 04-ElasticSearch入门

一.安装 ElasticSearch

1.注意事项

注意事项:

  • 内存不能太小,否则会启动失败
  • JDK 版本需要对应,es7 需要 Java 11
  • 不能以 root 用户启动

### 2.安装 java11

#java版本查看

java-version

 

#下载安装

yuminstalljava-11-openjdk.x86_64-y

 

#查看位置

ls-rl$(whichjava)

 

#修改环境变量

vim/etc/profile

 

exportJAVA_HOME=/usr/lib/jvm/java-11-openjdk-11.0.19.0.7-1.0.1.al8.x86_64

exportPATH=$PATH:$JAVA_HOME/bin

 

#使生效

source/etc/profile

3.下载 es

下载地址

#解压到到/usr/local/目录下

tar-zxvfelasticsearch-7.12.0-linux-x86_64.tar.gz-C/usr/local/

 

#进入解压后的目录

cd/usr/local/elasticsearch-7.12.0

 

#创建文件和日志目录(日志文件已经存在)

mkdirdata

修改配置:

vim config/elasticsearch.yml

cluster.name: my-application #集群名称

node.name: node-1 #节点名称

path.data: /usr/local/elasticsearch-7.12.0/data

path.logs: /usr/local/elasticsearch-7.12.0/logs

#设置绑定的ip,设置为0.0.0.0以后就可以让任何计算机节点访问到了

network.host: 0.0.0.0

http.port: 9200 #端口

#设置在集群中的所有节点名称,这个节点名称就是之前所修改的,目前是单机,放入一个节点即可

cluster.initial_master_nodes: ["node-1"]

4.增加用户

#创建用户

useradd elasticsearch


#用户加密码(elastic-search1)

passwd elasticsearch

5.赋权限

#修改目录权限至新增的elasticsearch用户

chown -R elasticsearch:elasticsearch /usr/local/elasticsearch-7.12.0

6.修改系统配置

  • 修改系统中允许应用最多创建多少文件等的限制权限。Linux 默认来说,一般限制应用最多创建的文件是 65535 个。但是 ES 至少需要 65536 的文件创建权限。
  • 修改系统中允许用户启动的进程开启多少个线程。默认的 Linux 限制 root 用户开启的进程可以开启任意数量的线程,其他用户开启的进程可以开启 1024 个线程。必须修改限制数为 4096+。因为 ES 至少需要 4096 的线程池预备。ES 在 5.x 版本之后,强制要求在 linux 中不能使用 root 用户启动 ES 进程。所以必须使用其他用户启动 ES 进程才可以。
  • Linux 低版本内核为线程分配的内存是 128K。4.x 版本的内核分配的内存更大。如果虚拟机的内存是 1G,最多只能开启 3000+个线程数。至少为虚拟机分配 1.5G 以上的内存。

#修改系统配置

vim /etc/security/limits.conf


# 追加到末尾即可

elasticsearch soft nofile 65536

elasticsearch hard nofile 65536

elasticsearch soft nproc 4096

elasticsearch hard nproc 4096

7.内存权限

vim /etc/sysctl.conf

vm.max_map_count=262144


#使配置生效

/sbin/sysctl -p

8.启动

#切换用户

su elasticsearch


#进入目录

cd /usr/local/elasticsearch-7.12.0


#启动

./bin/elasticsearch

./bin/elasticsearch -d


#启动Kibana

nohup sh /usr/local/kibana/bin/kibana &

9.验证

#查询端口信息

netstat -ntlp | grep -E '9200|5601'


#linux访问

curl 127.0.0.1:9200


#浏览器访问

http://47.119.162.180:9200


#查询集群状态

http://47.119.162.180:9200/cluster/health


解释:Status:集群状态。Green所有分片可用。Yellow所有主分片可用。Red主分片不可用,集群不可用。

{

 "name": "node-1",

 "cluster_name": "my-application",

 "cluster_uuid": "jFwKZO8cT12BdPtU63m-ew",

 "version": {

   "number": "7.12.0",

   "build_flavor": "default",

   "build_type": "tar",

   "build_hash": "78722783c38caa25a70982b5b042074cde5d3b3a",

   "build_date": "2021-03-18T06:17:15.410153305Z",

   "build_snapshot": false,

   "lucene_version": "8.8.0",

   "minimum_wire_compatibility_version": "6.8.0",

   "minimum_index_compatibility_version": "6.0.0-beta1"

 },

 "tagline": "You Know, for Search"

}

二.安装 Kibana

1.下载 Kibana

下载地址

选择要下载的版本 尽量和 ES 版本保持一致,版本采用 7.12.0 并选择 linux 版本

2.安装

#安装目录

cd  /usr/local


#解压

tar -zxvf kibana-7.12.0-linux-x86_64.tar.gz


#改名

mv kibana-7.12.0-linux-x86_64 kibana

3.修改配置

修改 kibana.yml

cd /usr/local/kibana

vim config/kibana.yml

server.port: 5601         #kibana端口

server.host: "0.0.0.0"   #所有主机都能访问,或者也可以指定一个ip

elasticsearch.hosts: "http://es服务公网IP:9200"     #配置es的访问地址

kibana.index: ".kibana"

i18n.locale: "zh-CN" #配置项默认是英文,配置成中文的

#root

chown -R elasticsearch:elasticsearch /usr/local/kibana

4.启动

#切换用户

su elasticsearch


#启动

cd /usr/local/kibana/bin

./kabana


#后台启动

nohup sh /usr/local/kibana/bin/kibana &

5.验证

#端口号

netstat -ntlp | grep 5601


#浏览器

http://47.119.162.180:5601/


#控制台

http://47.119.162.180:5601/app/dev_tools#/console

6.测试

GET _search

{

 "query": {

   "match_all": {}

 }

}


GET /


GET /_cluster/health


GET /_cat/health?v



GET /_cat/indices?v



PUT /demo_index?pretty



DELETE /demo_index?pretty


PUT /book


PUT /book/_doc/1

{

   "id":1,

   "title":"这是一文章",

   "content":"xxxxx",

   "comment":"备注信息",

   "mobile":"13344556677"

}


PUT /book/_doc/2

{

   "id":1,

   "title":"这是一11文章",

   "content":"xxxxx",

   "comment":"备注信息",

   "mobile":"13344556677"

}


GET /book/_doc/1


POST /book/_doc/1/_update

{

 "doc": {

   "title": "这是一333文章"

 }

}



POST /book/_update/1

{

 "doc": {

   "title": "这是一3333333444555文章"

 }

}


DELETE /book/_doc/1




POST /book/_doc/

{

   "id":1,

   "title":"这是一11文章",

   "content":"xxxxx",

   "comment":"备注信息",

   "mobile":"13344556677"

}


GET /book/_doc/1


GET /book/_doc/1?_source_includes=id,title


PUT /read_index/_doc/1/_create

{

   "id":1,

   "title":"这是一11文章",

   "content":"xxxxx",

   "comment":"备注信息",

   "mobile":"13344556677"

}


#插入数据

PUT /test_index/_doc/6

{

 "num": 0

}


#执行脚本

POST /test_index/_doc/6/_update

{

 "script": "ctx._source.num+=1"

}


#查询数据

GET /test_index/_doc/6


GET /test_index/_search

7.kibana 控制台

http://47.119.162.180:5601/app/dev_tools#/console

8.Grok 测试

9.格式化

10.历史记录

11.仪表板

三.简单使用

1.获取基本信息

get /

{

 "name": "node-1",

 "cluster_name": "my-application",

 "cluster_uuid": "gmjBLVYEQFO6ZR2bveXFig",

 "version": {

   "number": "7.12.0",

   "build_flavor": "default",

   "build_type": "tar",

   "build_hash": "78722783c38caa25a70982b5b042074cde5d3b3a",

   "build_date": "2021-03-18T06:17:15.410153305Z",

   "build_snapshot": false,

   "lucene_version": "8.8.0",

   "minimum_wire_compatibility_version": "6.8.0",

   "minimum_index_compatibility_version": "6.0.0-beta1"

 },

 "tagline": "You Know, for Search"

}

2.集群状态

GET /_cluster/health

解释:Status:集群状态。Green 所有分片可用。Yellow 所有主分片可用。Red 主分片不可用,集群不可用。

{

 "cluster_name": "my-application",

 "status": "yellow",

 "timed_out": false,

 "number_of_nodes": 1,

 "number_of_data_nodes": 1,

 "active_primary_shards": 8,

 "active_shards": 8,

 "relocating_shards": 0,

 "initializing_shards": 0,

 "unassigned_shards": 1,

 "delayed_unassigned_shards": 0,

 "number_of_pending_tasks": 0,

 "number_of_in_flight_fetch": 0,

 "task_max_waiting_in_queue_millis": 0,

 "active_shards_percent_as_number": 88.88888888888889

}

GET /_cat/health?v

  • green:每个索引的 primary shard 和 replica shard 都是 active 状态的.
  • yellow:每个索引的 primary shard 都是 active 状态的,但是部分 replica shard 不是 active 状态,处于不可用的状态
  • red:不是所有索引的 primary shard 都是 active 状态的,部分索引有数据丢失了

3.查看索引

GET /_cat/indices?v

4.创建索引

PUT /demo_index?pretty

{

 "acknowledged": true,

 "shards_acknowledged": true,

 "index": "demo_index"

}

5.删除索引

DELETE /demo_index?pretty

{

 "acknowledged": true

}

6.主键 id

手动 id

PUT /book/_doc/2

{

   "id":1,

   "title":"这是一11文章",

   "content":"xxxxx",

   "comment":"备注信息",

   "mobile":"13344556677"

}

自动 id

POST /book/_doc/

{

   "id":1,

   "title":"这是一11文章",

   "content":"xxxxx",

   "comment":"备注信息",

   "mobile":"13344556677"

}

自动id特点:长度为 20 个字符,URL 安全,base64 编码,GUID,分布式生成不冲突

{

 "_index": "book",

 "_type": "_doc",

 "_id": "Qk9rAIgBMxAD_MReKtAQ",

 "_version": 1,

 "result": "created",

 "_shards": {

   "total": 2,

   "successful": 1,

   "failed": 0

 },

 "_seq_no": 8,

 "_primary_term": 1

}

7.插入文档

put /blog_index/_doc/2

{

   "id":1,

   "title":"这是一篇文章",

   "content":"xxxxx",

   "comment":"备注信息",

   "mobile":"13344556677"

}

{

 "_index": "blog_index",

 "_type": "_doc",

 "_id": "2",

 "_version": 1,

 "result": "created",

 "_shards": {

   "total": 2,

   "successful": 1,

   "failed": 0

 },

 "_seq_no": 1,

 "_primary_term": 1

}

8.查询文档

get /blog_index/_doc/1

{

 "_index": "blog_index",

 "_type": "_doc",

 "_id": "1",

 "_version": 1,

 "_seq_no": 0,

 "_primary_term": 1,

 "found": true,

 "_source": {

   "id": 1,

   "title": "这是一篇文章",

   "content": "xxxxx",

   "comment": "备注信息",

   "mobile": "13344556677"

 }

}

9.图书 crud

创建 book 索引

PUT /book

插入数据

PUT /book/_doc/1

{

   "id":1,

   "title":"这是一篇文章",

   "content":"xxxxx",

   "comment":"备注信息",

   "mobile":"13344556677"

}

查询数据

GET /book/_doc/1

使用 put 全量替换

实质:日文档的内容不会立即删除,只是标记为 deleted。适当的时机,集群会将这些文档删除。

PUT /book/_doc/2

{

   "id":1,

   "title":"这是一11文章",

   "content":"xxxxx",

   "comment":"备注信息",

   "mobile":"13344556677"

}

局部更新

  1. es 内部获取旧文档
  2. 将传来的文档 field 更新到旧数据(内存)
  3. 将旧文档标记问 delete
  4. 创建新文档

POST /book/_doc/1/_update

{

 "doc": {

   "title": "这是一333文章"

 }

}

noop:

  • 多次执行"result" : "noop’
  • no operation

局部更新

POST /book/_update/1

{

 "doc": {

   "title": "这是一3333333444555文章"

 }

}

删除数据

DELETE /book/_doc/1

四.高阶使用

1.属性分析

  • 版本号
  • 删除的时候做标记,使用的是懒删除
  • _id 分为手动 id 和默认 id

{

 "_index": "book",

 "_type": "_doc",

 "_id": "1",

 "_version": 6,

 "_seq_no": 7,

 "_primary_term": 1,

 "found": true,

 "_source": {

   "id": 1,

   "title": "这是一文章",

   "content": "xxxxx",

   "comment": "备注信息",

   "mobile": "13344556677"

 }

}

2._source 字段

含义:插入数据时的所有字段和值。在 get 获取数据时,在 source 字段中原样返回。

GET /book/_doc/1

定制返回:

GET /book/_doc/1?_source_includes=id,title

{

 "_index" : "book",

 "_type" : "_doc",

 "_id" : "1",

 "_version" : 6,

 "_seq_no" : 7,

 "_primary_term" : 1,

 "found" : true,

 "_source" : {

   "id" : 1,

   "title" : "这是一文章"

 }

}

3.强制创建

为防止覆盖原有数据,我们在新增时,设置为强制创建,不会覆盖原有文档。

PUT /index/_doc/1/_create

PUT /read_index/_doc/1/_create

{

   "id":1,

   "title":"这是一11文章",

   "content":"xxxxx",

   "comment":"备注信息",

   "mobile":"13344556677"

}

{

 "_index": "read_index",

 "_type": "_doc",

 "_id": "1",

 "_version": 1,

 "result": "created",

 "_shards": {

   "total": 2,

   "successful": 1,

   "failed": 0

 },

 "_seq_no": 0,

 "_primary_term": 1

}

4.脚本使用

#插入数据

PUT /test_index/_doc/6

{

 "num": 0

}


#执行脚本

POST /test_index/_doc/6/_update

{

 "script": "ctx._source.num+=1"

}


#查询数据

GET /test_index/_doc/6

{

 "_index": "test_index",

 "_type": "_doc",

 "_id": "6",

 "_version": 2,

 "_seq_no": 1,

 "_primary_term": 1,

 "found": true,

 "_source": {

   "num": 1

 }

}

5.查询索引

GET /test_index/_search

{

 "took": 339,

 "timed_out": false,

 "_shards": {

   "total": 1,

   "successful": 1,

   "skipped": 0,

   "failed": 0

 },

 "hits": {

   "total": {

     "value": 1,

     "relation": "eq"

   },

   "max_score": 1.0,

   "hits": [

     {

       "_index": "test_index",

       "_type": "_doc",

       "_id": "6",

       "_score": 1.0,

       "_source": {

         "num": 1

       }

     }

   ]

 }

}

6._version 字段

删除的时候是异步删除,只是做了删除标记,延时删除策略.

es 内部主从同步时,是多线程异步,乐观锁机制。也是基于版本号

  • 线程 1 先到,线程 2 后到,副本数据是没有问题的
  • 线程 2 先到,线程 1 后到:
  • 副本分片先把数据改为 test3,verion=3.
  • 线程 1 请求到了,副本分片,判断请求的 verison=1 太旧了,就会丢弃这个请求。

说明:

  • version 使用数据自带的 version 版本号
  • _version&version_type=external 则是并发时使用程序自己指定的 version,且是不存在的

PUT /test_index/_doc/4?version=2&version_type=external

{

 "test_field": "itcast1"

}

7.重试

指定重试次数

POST /test_index/_doc/5/_update?retry_on_conflict=3

{

 "doc": {

   "test_field": "itcast1"

 }

}

结合 version

POST /test_index/_doc/5/_update?retry_on_conflict=3&version=22&version_type=external

{

 "doc": {

   "test_field": "itcast1"

 }

}

8.批量查询 mget

单条查询 GET /test_index/_doc/1,如果查询多个 id 的文档一条一条查询,网络开销太大。

GET /_mget

{

  "docs" : [

     {

        "_index" : "test_index",

        "_type" :  "_doc",

        "_id" :    1

     },

     {

        "_index" : "test_index",

        "_type" :  "_doc",

        "_id" :    7

     }

  ]

}

返回:

{

 "docs": [

   {

     "_index": "test_index",

     "_type": "_doc",

     "_id": "2",

     "_version": 6,

     "_seq_no": 12,

     "_primary_term": 1,

     "found": true,

     "_source": {

       "test_field": "test12333123321321"

     }

   },

   {

     "_index": "test_index",

     "_type": "_doc",

     "_id": "3",

     "_version": 6,

     "_seq_no": 18,

     "_primary_term": 1,

     "found": true,

     "_source": {

       "test_field": "test3213"

     }

   }

 ]

}

提示去掉 type

GET /_mget

{

  "docs" : [

     {

        "_index" : "test_index",

        "_id" :    2

     },

     {

        "_index" : "test_index",

        "_id" :    3

     }

  ]

}

同一索引下批量查询:

GET /test_index/_mget

{

  "docs" : [

     {

        "_id" :    2

     },

     {

        "_id" :    3

     }

  ]

}

第三种写法:搜索写法

post /test_index/_doc/_search

{

   "query": {

       "ids" : {

           "values" : ["1", "7"]

       }

   }

}

9.bulk

Bulk 操作解释将文档的增删改查一些列操作,通过一次请求全都做完。减少网络传输次数。

#语法

POST /_bulk

{"action": {"metadata"}}

{"data"}

示例:

#如下操作,删除5,新增14,修改2。

POST /_bulk

{ "create": { "_index": "test_index","_id": "8"}}

{ "test_field": "test8" }

{ "update": { "_index": "test_index","_id": "3"} }

{ "doc": {"test_field": "bulk test"} }

{ "delete": { "_index": "test_index","_id": "5" }}


总结:

  1. 功能:
  • delete:删除一个文档,只要 1 个 json 串就可以了
  • create:相当于强制创建 PUT /index/type/id/_create
  • index:普通的 put 操作,可以是创建文档,也可以是全量替换文档
  • update:执行的是局部更新 partial update 操作
  1. 格式:每个 json 不能换行。相邻 json 必须换行。
  2. 隔离:每个操作互不影响。操作失败的行会返回其失败信息。
  3. 实际用法:bulk 请求一次不要太大,否则一下积压到内存中,性能会下降。所以,一次请求几千个操作、大小在几 M 正好。

10.查询节点

/_cat/nodes?v

五.分词器

1.mysql 热更新词库

1.下载源码

https://github.com/medcl/elasticsearch-analysis-ik/releases

ik 分词器,是个标准的 java maven 工程,直接导入 idea 就可以看到源码

2.修改源

  1. org.wltea.analyzer.dic.Dictionary 类,160 行 Dictionary 单例类的初始化方法,在这里需要创建一个我们自定义的线程,并且启动它
  2. org.wltea.analyzer.dic.HotDictReloadThread 类:就是死循环,不断调用 Dictionary.getSingleton().reLoadMainDict(),去重新加载词典
  3. Dictionary 类,399 行:this.loadMySQLExtDict(); 加载 mymsql 字典。
  4. Dictionary 类,609 行:this.loadMySQLStopwordDict();加载 mysql 停用词
  5. config 下 jdbc-reload.properties。mysql 配置文件

3.mvn package 打包代码

target\releases\elasticsearch-analysis-ik-7.3.0.zip

4.解压缩 ik 压缩包

将 mysql 驱动 jar,放入 ik 的目录下

5.修改 jdbc 相关配置

6.重启 es

观察日志,日志中就会显示我们打印的那些东西,比如加载了什么配置,加载了什么词语,什么停用词

7.在 mysql 中添加词库与停用词

8.分词实验,验证热更新生效

GET /_analyze

{

 "analyzer": "ik_smart",

 "text": "喊麦"

}

2.ik 分词器种类

  • standard 分词器
  • ik_max_word 分词器
  • ik_smart 分词器

3.standard 分词器

GET /_analyze

{

 "analyzer": "standard",

 "text": "中华人民共和国人民大会堂"

}

{

 "tokens": [

   {

     "token": "中",

     "start_offset": 0,

     "end_offset": 1,

     "type": "<IDEOGRAPHIC>",

     "position": 0

   },

   {

     "token": "华",

     "start_offset": 1,

     "end_offset": 2,

     "type": "<IDEOGRAPHIC>",

     "position": 1

   },

   {

     "token": "人",

     "start_offset": 2,

     "end_offset": 3,

     "type": "<IDEOGRAPHIC>",

     "position": 2

   },

   {

     "token": "民",

     "start_offset": 3,

     "end_offset": 4,

     "type": "<IDEOGRAPHIC>",

     "position": 3

   },

   {

     "token": "共",

     "start_offset": 4,

     "end_offset": 5,

     "type": "<IDEOGRAPHIC>",

     "position": 4

   },

   {

     "token": "和",

     "start_offset": 5,

     "end_offset": 6,

     "type": "<IDEOGRAPHIC>",

     "position": 5

   },

   {

     "token": "国",

     "start_offset": 6,

     "end_offset": 7,

     "type": "<IDEOGRAPHIC>",

     "position": 6

   },

   {

     "token": "人",

     "start_offset": 7,

     "end_offset": 8,

     "type": "<IDEOGRAPHIC>",

     "position": 7

   },

   {

     "token": "民",

     "start_offset": 8,

     "end_offset": 9,

     "type": "<IDEOGRAPHIC>",

     "position": 8

   },

   {

     "token": "大",

     "start_offset": 9,

     "end_offset": 10,

     "type": "<IDEOGRAPHIC>",

     "position": 9

   },

   {

     "token": "会",

     "start_offset": 10,

     "end_offset": 11,

     "type": "<IDEOGRAPHIC>",

     "position": 10

   },

   {

     "token": "堂",

     "start_offset": 11,

     "end_offset": 12,

     "type": "<IDEOGRAPHIC>",

     "position": 11

   }

 ]

}

4.ik_max_word

GET /_analyze

{

 "analyzer": "ik_max_word",

 "text": "中华人民共和国人民大会堂"

}

{

 "tokens": [

   {

     "token": "中华人民共和国",

     "start_offset": 0,

     "end_offset": 7,

     "type": "CN_WORD",

     "position": 0

   },

   {

     "token": "中华人民",

     "start_offset": 0,

     "end_offset": 4,

     "type": "CN_WORD",

     "position": 1

   },

   {

     "token": "中华",

     "start_offset": 0,

     "end_offset": 2,

     "type": "CN_WORD",

     "position": 2

   },

   {

     "token": "华人",

     "start_offset": 1,

     "end_offset": 3,

     "type": "CN_WORD",

     "position": 3

   },

   {

     "token": "人民共和国",

     "start_offset": 2,

     "end_offset": 7,

     "type": "CN_WORD",

     "position": 4

   },

   {

     "token": "人民",

     "start_offset": 2,

     "end_offset": 4,

     "type": "CN_WORD",

     "position": 5

   },

   {

     "token": "共和国",

     "start_offset": 4,

     "end_offset": 7,

     "type": "CN_WORD",

     "position": 6

   },

   {

     "token": "共和",

     "start_offset": 4,

     "end_offset": 6,

     "type": "CN_WORD",

     "position": 7

   },

   {

     "token": "国人",

     "start_offset": 6,

     "end_offset": 8,

     "type": "CN_WORD",

     "position": 8

   },

   {

     "token": "人民大会堂",

     "start_offset": 7,

     "end_offset": 12,

     "type": "CN_WORD",

     "position": 9

   },

   {

     "token": "人民大会",

     "start_offset": 7,

     "end_offset": 11,

     "type": "CN_WORD",

     "position": 10

   },

   {

     "token": "人民",

     "start_offset": 7,

     "end_offset": 9,

     "type": "CN_WORD",

     "position": 11

   },

   {

     "token": "大会堂",

     "start_offset": 9,

     "end_offset": 12,

     "type": "CN_WORD",

     "position": 12

   },

   {

     "token": "大会",

     "start_offset": 9,

     "end_offset": 11,

     "type": "CN_WORD",

     "position": 13

   },

   {

     "token": "会堂",

     "start_offset": 10,

     "end_offset": 12,

     "type": "CN_WORD",

     "position": 14

   }

 ]

}

5.ik_smart

GET /_analyze

{

 "analyzer": "ik_smart",

 "text": "中华人民共和国人民大会堂"

}

{

 "tokens": [

   {

     "token": "中华人民共和国",

     "start_offset": 0,

     "end_offset": 7,

     "type": "CN_WORD",

     "position": 0

   },

   {

     "token": "人民大会堂",

     "start_offset": 7,

     "end_offset": 12,

     "type": "CN_WORD",

     "position": 1

   }

 ]

}

六.search 搜索

1.query string search

无条件搜索所有

GET /book/_search

{

 "took": 969,

 "timed_out": false,

 "_shards": {

   "total": 1,

   "successful": 1,

   "skipped": 0,

   "failed": 0

 },

 "hits": {

   "total": {

     "value": 3,

     "relation": "eq"

   },

   "max_score": 1.0,

   "hits": [

     {

       "_index": "book",

       "_type": "_doc",

       "_id": "1",

       "_score": 1.0,

       "_source": {

         "name": "Bootstrap开发",

         "description": "Bootstrap是由Twitter推出的一个前台页面开发css框架,是一个非常流行的开发框架,此框架集成了多种页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长css页面开发的程序人员)轻松的实现一个css,不受浏览器限制的精美界面css效果。",

         "studymodel": "201002",

         "price": 38.6,

         "timestamp": "2019-08-25 19:11:35",

         "pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",

         "tags": ["bootstrap", "dev"]

       }

     }

     }

   ]

 }

}

2.带参数搜索

与 http 请求传参类似

GET /book/_search?q=name:java&sort=price:desc

类比 sql: select * from book where name like ’ %java%’ order by price desc

{

 "took": 2,

 "timed_out": false,

 "_shards": {

   "total": 1,

   "successful": 1,

   "skipped": 0,

   "failed": 0

 },

 "hits": {

   "total": {

     "value": 1,

     "relation": "eq"

   },

   "max_score": null,

   "hits": [

     {

       "_index": "book",

       "_type": "_doc",

       "_id": "2",

       "_score": null,

       "_source": {

         "name": "java编程思想",

         "description": "java语言是世界第一编程语言,在软件开发领域使用人数最多。",

         "studymodel": "201001",

         "price": 68.6,

         "timestamp": "2019-08-25 19:11:35",

         "pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",

         "tags": ["java", "dev"]

       },

       "sort": [68.6]

     }

   ]

 }

}

3. 设置 timeout

GET /book/_search?timeout=10ms

设置搜索的超时时间,到超时就返回

全局设置:配置文件中设置 search.default_search_timeout:100ms。默认不超时。

4.多索引搜索

multi-index 搜索模式

告诉你如何一次性搜索多个 index 和多个 type 下的数据

#所有索引下的所有数据都搜索出来

/_search


#指定一个index,搜索其下所有的数据

/index1/_search


#同时搜索两个index下的数据

/index1,index2/_search


#按照通配符去匹配多个索引

/index*/_search

5.分页搜索

分页搜索的语法:

select * from book limit 1,5

GET /book/_search?size=10


GET /book/_search?size=10&from=0


GET /book/_search?size=10&from=20


GET /book/_search?from=0&size=3

6.query string 基础语法

  • GET /book/_search?q=name:java
  • GET /book/_search?q=+name:java
  • GET /book/_search?q=-name:java

说明:

q=field:search content 的语法,就是根据字段进行搜索

+与没有+一样都是必须包含,-是必须不包含

7._all metadata 的原理和作用

不带字段的查询,会存在 all 索引中_,方便查询

GET /book/_search?q=java

直接可以搜索所有的 field,任意一个 field 包含指定的关键字就可以搜索出来。我们在进行中搜索的时候,难道是对 document 中的每一个 field 都进行一次搜索吗?不是的。

es 中_all 元数据。建立索引的时候,插入一条 docunment,es 会将所有的 field 值经行全量分词,把这些分词,放到_all field 中。在搜索的时候,没有指定 field,就在_all 搜索。

8.query DSL

查询全部 GET /book/_search

GET /book/_search

{

 "query": { "match_all": {} }

}

排序 GET /book/_search?sort=price:desc

GET /book/_search

{

   "query" : {

       "match" : {

           "name" : " java"

       }

   },

   "sort": [

       { "price": "desc" }

   ]

}

分页查询 GET /book/_search?size=10&from=0

GET  /book/_search

{

 "query": { "match_all": {} },

 "from": 0,

 "size": 1

}

指定返回字段 GET /book/ _search? _source=name,studymodel

GET /book/_search

{

 "query": { "match_all": {} },

 "_source": ["name", "studymodel"]

}

通过组合以上各种类型查询,实现复杂查询。

9.组合多个搜索条件

搜索需求:title 必须包含 elasticsearch,content 可以包含 elasticsearch 也可以不包含,author_id 必须不为 111

sql where and or !=

初始数据:

POST /website/_doc/1

{

   "title": "my hadoop article",

   "content": "hadoop is very bad",

   "author_id": 111

}


POST /website/_doc/2

{

   "title": "my elasticsearch  article",

   "content": "es is very bad",

   "author_id": 112

}

POST /website/_doc/3

{

   "title": "my elasticsearch article",

   "content": "es is very goods",

   "author_id": 111

}

搜索:

GET /website/_doc/_search

{

 "query": {

   "bool": {

     "must": [

       {

         "match": {

           "title": "elasticsearch"

         }

       }

     ],

     "should": [

       {

         "match": {

           "content": "elasticsearch"

         }

       }

     ],

     "must_not": [

       {

         "match": {

           "author_id": 111

         }

       }

     ]

   }

 }

}

返回:

{

 "took": 488,

 "timed_out": false,

 "_shards": {

   "total": 1,

   "successful": 1,

   "skipped": 0,

   "failed": 0

 },

 "hits": {

   "total": {

     "value": 1,

     "relation": "eq"

   },

   "max_score": 0.47000363,

   "hits": [

     {

       "_index": "website",

       "_type": "_doc",

       "_id": "2",

       "_score": 0.47000363,

       "_source": {

         "title": "my elasticsearch  article",

         "content": "es is very bad",

         "author_id": 112

       }

     }

   ]

 }

}

更复杂的搜索需求:

select * from test_index where name='tom' or (hired =true and (personality ='good' and rude != true ))

GET /test_index/_search

{

   "query": {

           "bool": {

               "must": { "match":{ "name": "tom" }},

               "should": [

                   { "match":{ "hired": true }},

                   { "bool": {

                       "must":{ "match": { "personality": "good" }},

                       "must_not": { "match": { "rude": true }}

                   }}

               ],

               "minimum_should_match": 1

           }

   }

}

10.full-text search 全文检索

重新创建 book 索引

PUT /book/

{

 "settings": {

   "number_of_shards": 1,

   "number_of_replicas": 0

 },

 "mappings": {

   "properties": {

     "name":{

       "type": "text",

       "analyzer": "ik_max_word",

       "search_analyzer": "ik_smart"

     },

     "description":{

       "type": "text",

       "analyzer": "ik_max_word",

       "search_analyzer": "ik_smart"

     },

     "studymodel":{

       "type": "keyword"

     },

     "price":{

       "type": "double"

     },

     "timestamp": {

        "type": "date",

        "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"

     },

     "pic":{

       "type":"text",

       "index":false

     }

   }

 }

}

插入数据

PUT /book/_doc/1

{

"name": "Bootstrap开发",

"description": "Bootstrap是由Twitter推出的一个前台页面开发css框架,是一个非常流行的开发框架,此框架集成了多种页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长css页面开发的程序人员)轻松的实现一个css,不受浏览器限制的精美界面css效果。",

"studymodel": "201002",

"price":38.6,

"timestamp":"2019-08-25 19:11:35",

"pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",

"tags": [ "bootstrap", "dev"]

}


PUT /book/_doc/2

{

"name": "java编程思想",

"description": "java语言是世界第一编程语言,在软件开发领域使用人数最多。",

"studymodel": "201001",

"price":68.6,

"timestamp":"2019-08-25 19:11:35",

"pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",

"tags": [ "java", "dev"]

}


PUT /book/_doc/3

{

"name": "spring开发基础",

"description": "spring 在java领域非常流行,java程序员都在用。",

"studymodel": "201001",

"price":88.6,

"timestamp":"2019-08-24 19:11:35",

"pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",

"tags": [ "spring", "java"]

}

搜索

GET  /book/_search

{

   "query" : {

       "match" : {

           "description" : "java程序员"

       }

   }

}

11._score

{

 "took": 1,

 "timed_out": false,

 "_shards": {

   "total": 1,

   "successful": 1,

   "skipped": 0,

   "failed": 0

 },

 "hits": {

   "total": {

     "value": 2,

     "relation": "eq"

   },

   "max_score": 2.137549,

   "hits": [

     {

       "_index": "book",

       "_type": "_doc",

       "_id": "3",

       "_score": 2.137549,

       "_source": {

         "name": "spring开发基础",

         "description": "spring 在java领域非常流行,java程序员都在用。",

         "studymodel": "201001",

         "price": 88.6,

         "timestamp": "2019-08-24 19:11:35",

         "pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",

         "tags": ["spring", "java"]

       }

     },

     {

       "_index": "book",

       "_type": "_doc",

       "_id": "2",

       "_score": 0.57961315,

       "_source": {

         "name": "java编程思想",

         "description": "java语言是世界第一编程语言,在软件开发领域使用人数最多。",

         "studymodel": "201001",

         "price": 68.6,

         "timestamp": "2019-08-25 19:11:35",

         "pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",

         "tags": ["java", "dev"]

       }

     }

   ]

 }

}

结果分析:

  • 建立索引时, description 字段 term 倒排索引
  • java 2,3
  • 程序员 3
  • 搜索时,直接找 description 中含有 java 的文档 2,3,并且 3 号文档含有两个 java 字段,一个程序员,所以得分高,排在前面。2 号文档含有一个 java,排在后面。

12.Filter

需求:用户查询 description 中有"java 程序员",并且价格大于 80 小于 90 的数据。

GET /book/_search

{

 "query": {

   "bool": {

     "must": [

       {

         "match": {

           "description": "java程序员"

         }

       },

       {

         "range": {

           "price": {

             "gte": 80,

       "lte": 90

           }

         }

       }

     ]

   }

 }

}

使用 filter:

GET /book/_search

{

 "query": {

   "bool": {

     "must": [

       {

         "match": {

           "description": "java程序员"

         }

       }

     ],

     "filter": {

       "range": {

         "price": {

           "gte": 80,

      "lte": 90

         }

       }

     }

   }

 }

}

13.filter 与 query 对比

filter,仅仅只是按照搜索条件过滤出需要的数据而已,不计算任何相关度分数,对相关度没有任何影响。

query,会去计算每个 document 相对于搜索条件的相关度,并按照相关度进行排序。

应用场景:

一般来说,如果你是在进行搜索,需要将最匹配搜索条件的数据先返回,那么用 query 如果你只是要根据一些条件筛选出一部分数据,不关注其排序,那么用 filter

性能比较:

  • filter,不需要计算相关度分数,不需要按照相关度分数进行排序,同时还有内置的自动 cache 最常使用 filter 的数据
  • query,相反,要计算相关度分数,按照分数进行排序,而且无法 cache 结果

14.定位错误语法

验证错误语句:

GET /book/_validate/query?explain

{

 "query": {

   "mach": {

     "description": "java程序员"

   }

 }

}

返回:

{

 "valid": false,

 "error": "org.elasticsearch.common.ParsingException: no [query] registered for [mach]"

}

正确

GET /book/_validate/query?explain

{

 "query": {

   "match": {

     "description": "java程序员"

   }

 }

}

返回

{

 "_shards": {

   "total": 1,

   "successful": 1,

   "failed": 0

 },

 "valid": true,

 "explanations": [

   {

     "index": "book",

     "valid": true,

     "explanation": "description:java description:程序员"

   }

 ]

}

一般用在那种特别复杂庞大的搜索下,比如你一下子写了上百行的搜索,这个时候可以先用 validate api 去验证一下,搜索是否合法。

合法以后,explain 就像 mysql 的执行计划,可以看到搜索的目标等信息。

15.默认排序规则

默认情况下,是按照_score 降序排序的

然而,某些情况下,可能没有有用的_score,比如说 filter

GET book/_search

{

 "query": {

   "bool": {

     "must": [

       {

         "match": {

           "description": "java程序员"

         }

       }

     ]

   }

 }

}

当然,也可以是 constant_score

16.定制排序规则

相当于 sql 中 order by ?sort=sprice:desc

GET /book/_search

{

 "query": {

   "constant_score": {

     "filter" : {

           "term" : {

               "studymodel" : "201001"

           }

       }

   }

 },

 "sort": [

   {

     "price": {

       "order": "asc"

     }

   }

 ]

}

17.Text 字段排序问题

如果对一个 text field 进行排序,结果往往不准确,因为分词后是多个单词,再排序就不是我们想要的结果了。

通常解决方案是,将一个 text field 建立两次索引,一个分词,用来进行搜索;一个不分词,用来进行排序。

PUT /website

{

 "mappings": {

 "properties": {

   "title": {

     "type": "text",

     "fields": {

       "keyword": {

         "type": "keyword"

       }

     }

   },

   "content": {

     "type": "text"

   },

   "post_date": {

     "type": "date"

   },

   "author_id": {

     "type": "long"

   }

 }

}

}

插入数据

PUT /website/_doc/1

{

 "title": "first article",

 "content": "this is my second article",

 "post_date": "2019-01-01",

 "author_id": 110

}


PUT /website/_doc/2

{

   "title": "second article",

   "content": "this is my second article",

    "post_date": "2019-01-01",

   "author_id": 110

}


PUT /website/_doc/3

{

    "title": "third article",

    "content": "this is my third article",

    "post_date": "2019-01-02",

    "author_id": 110

}

搜索

GET /website/_search

{

 "query": {

   "match_all": {}

 },

 "sort": [

   {

     "title.keyword": {

       "order": "desc"

     }

   }

 ]

}

18.Scroll 分批查询

场景:下载某一个索引中 1 亿条数据,到文件或是数据库。

不能一下全查出来,系统内存溢出。所以使用 scoll 滚动搜索技术,一批一批查询。

scoll 搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该旧的视图快照提供数据搜索,如果这个期间数据变更,是不会让用户看到的

每次发送 scroll 请求,我们还需要指定一个 scoll 参数,指定一个时间窗口,每次搜索请求只要在这个时间窗口内能完成就可以了。

搜索

GET /book/_search?scroll=1m

{

 "query": {

   "match_all": {}

 },

 "size": 3

}

返回

{

 "_scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAMOkWTURBNDUtcjZTVUdKMFp5cXloVElOQQ==",

 "took": 3,

 "timed_out": false,

 "_shards": {

   "total": 1,

   "successful": 1,

   "skipped": 0,

   "failed": 0

 },

 "hits": {

   "total": {

     "value": 3,

     "relation": "eq"

   },

   "max_score": 1.0,

   "hits": []

 }

}

获得的结果会有一个 scoll_id,下一次再发送 scoll 请求的时候,必须带上这个 scoll_id

GET /_search/scroll

{

   "scroll": "1m",

   "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAMOkWTURBNDUtcjZTVUdKMFp5cXloVElOQQ=="

}

与分页区别:

  • 分页是给用户看的 deep paging
  • scroll 是用户系统内部操作,如下载批量数据,数据转移。零停机改变索引映射。

19.DSL 语法练习

1.match_all

GET /book/_search

{

   "query": {

       "match_all": {}

   }

}

2.match

GET /book/_search

{

"query": {

"match": {

  "description": "java程序员"

}

}

}

3.multi_match

GET /book/_search

{

 "query": {

   "multi_match": {

     "query": "java程序员",

     "fields": ["name", "description"]

   }

 }

}

4.range query 范围查询

GET /book/_search

{

 "query": {

   "range": {

     "price": {

       "gte": 80,

"lte": 90

     }

   }

 }

}

5.term query

字段为 keyword 时,存储和搜索都不分词

GET /book/_search

{

 "query": {

   "term": {

     "description": "java程序员"

   }

 }

}

6.terms query

GET /book/_search

{

   "query": { "terms": { "tag": [ "search", "full_text", "nosql" ] }}

}

7.exist query

查询有某些字段值的文档

GET /_search

{

   "query": {

       "exists": {

           "field": "join_date"

       }

   }

}

8.Fuzzy query

返回包含与搜索词类似的词的文档,该词由 Levenshtein 编辑距离度量。

包括以下几种情况:

  • 更改角色(box→fox)
  • 删除字符(aple→apple)
  • 插入字符(sick→sic)
  • 调换两个相邻字符(ACT→CAT)

GET /book/_search

{

   "query": {

       "fuzzy": {

           "description": {

               "value": "jave"

           }

       }

   }

}

9.IDs

GET /book/_search

{

   "query": {

       "ids" : {

           "values" : ["1", "4", "100"]

       }

   }

}

10.prefix 前缀查询

GET /book/_search

{

   "query": {

       "prefix": {

           "description": {

               "value": "spring"

           }

       }

   }

}

11.regexp query

正则查询

GET /book/_search

{

   "query": {

       "regexp": {

           "description": {

               "value": "j.*a",

               "flags" : "ALL",

               "max_determinized_states": 10000,

               "rewrite": "constant_score"

           }

       }

   }

}

七.聚合示例

1.DSL API

1.all-分组-计数

计算每个 studymodel 下的商品数量

sql 语句: select studymodel,count(*) from book group by studymodel

GET /book/_search

{

 "size": 0,

 "query": {

   "match_all": {}

 },

 "aggs": {

   "group_by_model": {

     "terms": { "field": "studymodel" }

   }

 }

}

2.分组-计数

计算每个 tags 下的商品数量

设置字段"fielddata": true,这个步骤很重要,不然会报错

在 es 中,text 类型的字段使用一种叫做 fielddata 的查询时内存数据结构。当字段被排序,聚合或者通过脚本访问时这种数据结构会被创建。它是通过从磁盘读取每个段的整个反向索引来构建的,然后存存储在 java 的堆内存中。

fileddata 默认是不开启的。Fielddata 可能会消耗大量的堆空间,尤其是在加载高基数文本字段时。一旦 fielddata 已加载到堆中,它将在该段的生命周期内保留。此外,加载 fielddata 是一个昂贵的过程,可能会导致用户遇到延迟命中。这就是默认情况下禁用 fielddata 的原因。如果尝试对文本字段进行排序,聚合或脚本访问,将看到以下异常:

“Fielddata is disabled on text fields by default. Set fielddata=true on [your_field_name] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory.”

在启用 fielddata 之前,请考虑使用文本字段进行聚合,排序或脚本的原因。这样做通常没有意义。text 字段在索引例如 New York 这样的词会被分词,会被拆成 new,york。在此字段上面来一个 terms 的聚合会返回一个 new 的 bucket 和一个 york 的 bucket,当你想只返回一个 New York 的 bucket 的时候就会出现问题。

PUT /book/_mapping/

{

 "properties": {

   "tags": {

     "type": "text",

     "fielddata": true

   }

 }

}

查询

GET /book/_search

{

 "size": 0,

 "query": {

   "match_all": {}

 },

 "aggs": {

   "group_by_tags": {

     "terms": { "field": "tags" }

   }

 }

}

3.搜索-分组

加上搜索条件,计算每个 tags 下的商品数量

GET /book/_search

{

 "size": 0,

 "query": {

   "match": {

     "description": "java程序员"

   }

 },

 "aggs": {

   "group_by_tags": {

     "terms": { "field": "tags" }

   }

 }

}

4.分组-平均

先分组,再算每组的平均值,计算每个 tag 下的商品的平均价格

GET /book/_search

{

   "size": 0,

   "aggs" : {

       "group_by_tags" : {

           "terms" : {

             "field" : "tags"

           },

           "aggs" : {

               "avg_price" : {

                   "avg" : { "field" : "price" }

               }

           }

       }

   }

}

5.分组-平均-排序

计算每个 tag 下的商品的平均价格,并且按照平均价格降序排序

GET /book/_search

{

   "size": 0,

   "aggs" : {

       "group_by_tags" : {

           "terms" : {

             "field" : "tags",

             "order": {

               "avg_price": "desc"

             }

           },

           "aggs" : {

               "avg_price" : {

                   "avg" : { "field" : "price" }

               }

           }

       }

   }

}

6.分组-分组-组内平均

按照指定的价格范围区间进行分组,然后在每组内再按照 tag 进行分组,最后再计算每组的平均价格

GET /book/_search

{

 "size": 0,

 "aggs": {

   "group_by_price": {

     "range": {

       "field": "price",

       "ranges": [

         {

           "from": 0,

           "to": 40

         },

         {

           "from": 40,

           "to": 60

         },

         {

           "from": 60,

           "to": 80

         }

       ]

     },

     "aggs": {

       "group_by_tags": {

         "terms": {

           "field": "tags"

         },

         "aggs": {

           "average_price": {

             "avg": {

               "field": "price"

             }

           }

         }

       }

     }

   }

 }

}

六.电视案例

创建索引及映射

PUT /tvs

PUT /tvs/_search

{

  "properties": {

  "price": {

    "type": "long"

  },

  "color": {

    "type": "keyword"

  },

  "brand": {

    "type": "keyword"

  },

  "sold_date": {

    "type": "date"

  }

  }

}

插入数据

POST /tvs/_bulk

{ "index": {}}

{ "price" : 1000, "color" : "红色", "brand" : "长虹", "sold_date" : "2019-10-28" }

{ "index": {}}

{ "price" : 2000, "color" : "红色", "brand" : "长虹", "sold_date" : "2019-11-05" }

{ "index": {}}

{ "price" : 3000, "color" : "绿色", "brand" : "小米", "sold_date" : "2019-05-18" }

{ "index": {}}

{ "price" : 1500, "color" : "蓝色", "brand" : "TCL", "sold_date" : "2019-07-02" }

{ "index": {}}

{ "price" : 1200, "color" : "绿色", "brand" : "TCL", "sold_date" : "2019-08-19" }

{ "index": {}}

{ "price" : 2000, "color" : "红色", "brand" : "长虹", "sold_date" : "2019-11-05" }

{ "index": {}}

{ "price" : 8000, "color" : "红色", "brand" : "三星", "sold_date" : "2020-01-01" }

{ "index": {}}

{ "price" : 2500, "color" : "蓝色", "brand" : "小米", "sold_date" : "2020-02-12" }

2.电视示例

1.统计哪种颜色的电视销量最高

GET /tvs/_search

{

   "size" : 0,

   "aggs" : {

       "popular_colors" : {

           "terms" : {

             "field" : "color"

           }

       }

   }

}

查询条件解析

size:只获取聚合结果,而不要执行聚合的原始数据aggs:固定语法,要对一份数据执行分组聚合操作popular_colors:就是对每个 aggs,都要起一个名字,terms:根据字段的值进行分组field:根据指定的字段的值进行分组

返回

{

 "took" : 18,

 "timed_out" : false,

 "_shards" : {

   "total" : 1,

   "successful" : 1,

   "skipped" : 0,

   "failed" : 0

 },

 "hits" : {

   "total" : {

     "value" : 8,

     "relation" : "eq"

   },

   "max_score" : null,

   "hits" : [ ]

 },

 "aggregations" : {

   "popular_colors" : {

     "doc_count_error_upper_bound" : 0,

     "sum_other_doc_count" : 0,

     "buckets" : [

       {

         "key" : "红色",

         "doc_count" : 4

       },

       {

         "key" : "绿色",

         "doc_count" : 2

       },

       {

         "key" : "蓝色",

         "doc_count" : 2

       }

     ]

   }

 }

}

返回结果解析

hits.hits:我们指定了 size 是 0,所以 hits.hits 就是空的aggregations:聚合结果popular_color:我们指定的某个聚合的名称buckets:根据我们指定的 field 划分出的 bucketskey:每个 bucket 对应的那个值doc_count:这个 bucket 分组内,有多少个数据数量,其实就是这种颜色的销量

每种颜色对应的 bucket 中的数据的默认的排序规则:按照 doc_count 降序排序

2,统计每种颜色电视平均价格

GET /tvs/_search

{

  "size" : 0,

  "aggs": {

     "colors": {

        "terms": {

           "field": "color"

        },

        "aggs": {

           "avg_price": {

              "avg": {

                 "field": "price"

              }

           }

        }

     }

  }

}

在一个 aggs 执行的 bucket 操作(terms),平级的 json 结构下,再加一个 aggs,这个第二个 aggs 内部,同样取个名字,执行一个 metric 操作,avg,对之前的每个 bucket 中的数据的指定的 field,price field,求一个平均值

返回:

{

 "took": 4,

 "timed_out": false,

 "_shards": {

   "total": 1,

   "successful": 1,

   "skipped": 0,

   "failed": 0

 },

 "hits": {

   "total": {

     "value": 8,

     "relation": "eq"

   },

   "max_score": null,

   "hits": []

 },

 "aggregations": {

   "colors": {

     "doc_count_error_upper_bound": 0,

     "sum_other_doc_count": 0,

     "buckets": [

       {

         "key": "红色",

         "doc_count": 4,

         "avg_price": {

           "value": 3250.0

         }

       },

       {

         "key": "绿色",

         "doc_count": 2,

         "avg_price": {

           "value": 2100.0

         }

       },

       {

         "key": "蓝色",

         "doc_count": 2,

         "avg_price": {

           "value": 2000.0

         }

       }

     ]

   }

 }

}

buckets,除了 key 和 doc_countavg_price:我们自己取的 metric aggs 的名字value:我们的 metric 计算的结果,每个 bucket 中的数据的 price 字段求平均值后的结果

相当于 sql: select avg(price) from tvs group by color

3.继续下钻分析

每个颜色下,平均价格及每个颜色下,每个品牌的平均价格

GET /tvs/_search

{

 "size": 0,

 "aggs": {

   "group_by_color": {

     "terms": {

       "field": "color"

     },

     "aggs": {

       "color_avg_price": {

         "avg": {

           "field": "price"

         }

       },

       "group_by_brand": {

         "terms": {

           "field": "brand"

         },

         "aggs": {

           "brand_avg_price": {

             "avg": {

               "field": "price"

             }

           }

         }

       }

     }

   }

 }

}

4.更多的 metric

count:bucket,terms,自动就会有一个 doc_count,就相当于是 countavg:avg aggs,求平均值max:求一个 bucket 内,指定 field 值最大的那个数据min:求一个 bucket 内,指定 field 值最小的那个数据sum:求一个 bucket 内,指定 field 值的总和

GET /tvs/_search

{

  "size" : 0,

  "aggs": {

     "colors": {

        "terms": {

           "field": "color"

        },

        "aggs": {

           "avg_price": { "avg": { "field": "price" } },

           "min_price" : { "min": { "field": "price"} },

           "max_price" : { "max": { "field": "price"} },

           "sum_price" : { "sum": { "field": "price" } }

        }

     }

  }

}

5.划分范围 histogram

GET /tvs/_search

{

  "size" : 0,

  "aggs":{

     "price":{

        "histogram":{

           "field": "price",

           "interval": 2000

        },

        "aggs":{

           "income": {

              "sum": {

                "field" : "price"

              }

            }

        }

     }

  }

}

histogram:类似于 terms,也是进行 bucket 分组操作,接收一个 field,按照这个 field 的值的各个范围区间,进行 bucket 分组操作

"histogram":{

 "field": "price",

 "interval": 2000

}

interval:2000,划分范围,02000,20004000,40006000,60008000,8000~10000,buckets

bucket 有了之后,一样的,去对每个 bucket 执行 avg,count,sum,max,min,等各种 metric 操作,聚合分析

6.按照日期分组聚合

date_histogram,按照我们指定的某个 date 类型的日期 field,以及日期 interval,按照一定的日期间隔,去划分 bucket

min_doc_count:即使某个日期 interval,2017-01-01~2017-01-31 中,一条数据都没有,那么这个区间也是要返回的,不然默认是会过滤掉这个区间的extended_bounds,min,max:划分 bucket 的时候,会限定在这个起始日期,和截止日期内

GET /tvs/_search

{

  "size" : 0,

  "aggs": {

     "sales": {

        "date_histogram": {

           "field": "sold_date",

           "interval": "month",

           "format": "yyyy-MM-dd",

           "min_doc_count" : 0,

           "extended_bounds" : {

               "min" : "2019-01-01",

               "max" : "2020-12-31"

           }

        }

     }

  }

}

7.统计每季度每个品牌的销售额

GET /tvs/_search

{

 "size": 0,

 "aggs": {

   "group_by_sold_date": {

     "date_histogram": {

       "field": "sold_date",

       "interval": "quarter",

       "format": "yyyy-MM-dd",

       "min_doc_count": 0,

       "extended_bounds": {

         "min": "2019-01-01",

         "max": "2020-12-31"

       }

     },

     "aggs": {

       "group_by_brand": {

         "terms": {

           "field": "brand"

         },

         "aggs": {

           "sum_price": {

             "sum": {

               "field": "price"

             }

           }

         }

       },

       "total_sum_price": {

         "sum": {

           "field": "price"

         }

       }

     }

   }

 }

}

8.搜索与聚合结合,查询某个品牌按颜色销量

搜索与聚合可以结合起来。

sql select count(*)

from tvs

where brand like "%小米%"

group by color

es aggregation,scope,任何的聚合,都必须在搜索出来的结果数据中之行,搜索结果,就是聚合分析操作的 scope

GET /tvs/_search

{

 "size": 0,

 "query": {

   "term": {

     "brand": {

       "value": "小米"

     }

   }

 },

 "aggs": {

   "group_by_color": {

     "terms": {

       "field": "color"

     }

   }

 }

}

9.global bucket:单个品牌与所有品牌销量对比

aggregation,scope,一个聚合操作,必须在 query 的搜索结果范围内执行

出来两个结果,一个结果,是基于 query 搜索结果来聚合的; 一个结果,是对所有数据执行聚合的

GET /tvs/_search

{

 "size": 0,

 "query": {

   "term": {

     "brand": {

       "value": "小米"

     }

   }

 },

 "aggs": {

   "single_brand_avg_price": {

     "avg": {

       "field": "price"

     }

   },

   "all": {

     "global": {},

     "aggs": {

       "all_brand_avg_price": {

         "avg": {

           "field": "price"

         }

       }

     }

   }

 }

}

10.过滤+聚合:统计价格大于 1200 的电视平均价格

搜索+聚合

过滤+聚合

GET /tvs/_search

{

 "size": 0,

 "query": {

   "constant_score": {

     "filter": {

       "range": {

         "price": {

           "gte": 1200

         }

       }

     }

   }

 },

 "aggs": {

   "avg_price": {

     "avg": {

       "field": "price"

     }

   }

 }

}

11.bucket filter:统计品牌最近一个月的平均价格

GET /tvs/_search

{

 "size": 0,

 "query": {

   "term": {

     "brand": {

       "value": "小米"

     }

   }

 },

 "aggs": {

   "recent_150d": {

     "filter": {

       "range": {

         "sold_date": {

           "gte": "now-150d"

         }

       }

     },

     "aggs": {

       "recent_150d_avg_price": {

         "avg": {

           "field": "price"

         }

       }

     }

   },

   "recent_140d": {

     "filter": {

       "range": {

         "sold_date": {

           "gte": "now-140d"

         }

       }

     },

     "aggs": {

       "recent_140d_avg_price": {

         "avg": {

           "field": "price"

         }

       }

     }

   },

   "recent_130d": {

     "filter": {

       "range": {

         "sold_date": {

           "gte": "now-130d"

         }

       }

     },

     "aggs": {

       "recent_130d_avg_price": {

         "avg": {

           "field": "price"

         }

       }

     }

   }

 }

}

aggs.filter,针对的是聚合去做的

如果放 query 里面的 filter,是全局的,会对所有的数据都有影响

但是,如果,比如说,你要统计,长虹电视,最近 1 个月的平均值; 最近 3 个月的平均值; 最近 6 个月的平均值

bucket filter:对不同的 bucket 下的 aggs,进行 filter

12.按每种颜色的平均销售额降序排序

GET /tvs/_search

{

 "size": 0,

 "aggs": {

   "group_by_color": {

     "terms": {

       "field": "color",

       "order": {

         "avg_price": "asc"

       }

     },

     "aggs": {

       "avg_price": {

         "avg": {

           "field": "price"

         }

       }

     }

   }

 }

}

相当于 sql 子表数据字段可以立刻使用。

13.排序:按每种颜色的每种品牌平均销售额降序排序

GET /tvs/_search

{

 "size": 0,

 "aggs": {

   "group_by_color": {

     "terms": {

       "field": "color"

     },

     "aggs": {

       "group_by_brand": {

         "terms": {

           "field": "brand",

           "order": {

             "avg_price": "desc"

           }

         },

         "aggs": {

           "avg_price": {

             "avg": {

               "field": "price"

             }

           }

         }

       }

     }

   }

 }

}

八.ELK 分析日志

1.日志分析

需求:集中收集分布式服务的日志

逻辑模块程序随时输出日志

package com.itheima.es;


import org.junit.Test;

import org.junit.runner.RunWith;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;


import java.util.Random;


/**

* creste by itheima.itcast

*/

@SpringBootTest

@RunWith(SpringRunner.class)

public class TestLog {

   private static final Logger LOGGER= LoggerFactory.getLogger(TestLog.class);


   @Test

   public void testLog(){

       Random random =new Random();


       while (true){

           int userid=random.nextInt(10);

           LOGGER.info("userId:{},send:{}",userid,"hello world.I am "+userid);

           try {

               Thread.sleep(500);

           } catch (InterruptedException e) {

               e.printStackTrace();

           }

       }

   }

}

  • logstash 收集日志到 es
  • 在 kibana 中查看日志,展示数据

2.grok 内置类型

写 logstash 配置文件

USERNAME [a-zA-Z0-9._-]+

USER %{USERNAME}

INT (?:[+-]?(?:[0-9]+))

BASE10NUM (?<![0-9.+-])(?>[+-]?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+)))

NUMBER (?:%{BASE10NUM})

BASE16NUM (?<![0-9A-Fa-f])(?:[+-]?(?:0x)?(?:[0-9A-Fa-f]+))

BASE16FLOAT \b(?<![0-9A-Fa-f.])(?:[+-]?(?:0x)?(?:(?:[0-9A-Fa-f]+(?:\.[0-9A-Fa-f]*)?)|(?:\.[0-9A-Fa-f]+)))\b


POSINT \b(?:[1-9][0-9]*)\b

NONNEGINT \b(?:[0-9]+)\b

WORD \b\w+\b

NOTSPACE \S+

SPACE \s*

DATA .*?

GREEDYDATA .*

QUOTEDSTRING (?>(?<!\\)(?>"(?>\\.|[^\\"]+)+"|""|(?>'(?>\\.|[^\\']+)+')|''|(?>`(?>\\.|[^\\`]+)+`)|``))

UUID [A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}


# Networking

MAC (?:%{CISCOMAC}|%{WINDOWSMAC}|%{COMMONMAC})

CISCOMAC (?:(?:[A-Fa-f0-9]{4}\.){2}[A-Fa-f0-9]{4})

WINDOWSMAC (?:(?:[A-Fa-f0-9]{2}-){5}[A-Fa-f0-9]{2})

COMMONMAC (?:(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2})

IPV6 ((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?

IPV4 (?<![0-9])(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))(?![0-9])

IP (?:%{IPV6}|%{IPV4})

HOSTNAME \b(?:[0-9A-Za-z][0-9A-Za-z-]{0,62})(?:\.(?:[0-9A-Za-z][0-9A-Za-z-]{0,62}))*(\.?|\b)

HOST %{HOSTNAME}

IPORHOST (?:%{HOSTNAME}|%{IP})

HOSTPORT %{IPORHOST}:%{POSINT}


# paths

PATH (?:%{UNIXPATH}|%{WINPATH})

UNIXPATH (?>/(?>[\w_%!$@:.,-]+|\\.)*)+

TTY (?:/dev/(pts|tty([pq])?)(\w+)?/?(?:[0-9]+))

WINPATH (?>[A-Za-z]+:|\\)(?:\\[^\\?*]*)+

URIPROTO [A-Za-z]+(\+[A-Za-z+]+)?

URIHOST %{IPORHOST}(?::%{POSINT:port})?

# uripath comes loosely from RFC1738, but mostly from what Firefox

# doesn't turn into %XX

URIPATH (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%_\-]*)+

#URIPARAM \?(?:[A-Za-z0-9]+(?:=(?:[^&]*))?(?:&(?:[A-Za-z0-9]+(?:=(?:[^&]*))?)?)*)?

URIPARAM \?[A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]*

URIPATHPARAM %{URIPATH}(?:%{URIPARAM})?

URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?


# Months: January, Feb, 3, 03, 12, December

MONTH \b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\b

MONTHNUM (?:0?[1-9]|1[0-2])

MONTHNUM2 (?:0[1-9]|1[0-2])

MONTHDAY (?:(?:0[1-9])|(?:[12][0-9])|(?:3[01])|[1-9])


# Days: Monday, Tue, Thu, etc...

DAY (?:Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)


# Years?

YEAR (?>\d\d){1,2}

HOUR (?:2[0123]|[01]?[0-9])

MINUTE (?:[0-5][0-9])

# '60' is a leap second in most time standards and thus is valid.

SECOND (?:(?:[0-5]?[0-9]|60)(?:[:.,][0-9]+)?)

TIME (?!<[0-9])%{HOUR}:%{MINUTE}(?::%{SECOND})(?![0-9])

# datestamp is YYYY/MM/DD-HH:MM:SS.UUUU (or something like it)

DATE_US %{MONTHNUM}[/-]%{MONTHDAY}[/-]%{YEAR}

DATE_EU %{MONTHDAY}[./-]%{MONTHNUM}[./-]%{YEAR}

ISO8601_TIMEZONE (?:Z|[+-]%{HOUR}(?::?%{MINUTE}))

ISO8601_SECOND (?:%{SECOND}|60)

TIMESTAMP_ISO8601 %{YEAR}-%{MONTHNUM}-%{MONTHDAY}[T ]%{HOUR}:?%{MINUTE}(?::?%{SECOND})?%{ISO8601_TIMEZONE}?

DATE %{DATE_US}|%{DATE_EU}

DATESTAMP %{DATE}[- ]%{TIME}

TZ (?:[PMCE][SD]T|UTC)

DATESTAMP_RFC822 %{DAY} %{MONTH} %{MONTHDAY} %{YEAR} %{TIME} %{TZ}

DATESTAMP_RFC2822 %{DAY}, %{MONTHDAY} %{MONTH} %{YEAR} %{TIME} %{ISO8601_TIMEZONE}

DATESTAMP_OTHER %{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{TZ} %{YEAR}

DATESTAMP_EVENTLOG %{YEAR}%{MONTHNUM2}%{MONTHDAY}%{HOUR}%{MINUTE}%{SECOND}


# Syslog Dates: Month Day HH:MM:SS

SYSLOGTIMESTAMP %{MONTH} +%{MONTHDAY} %{TIME}

PROG (?:[\w._/%-]+)

SYSLOGPROG %{PROG:program}(?:\[%{POSINT:pid}\])?

SYSLOGHOST %{IPORHOST}

SYSLOGFACILITY <%{NONNEGINT:facility}.%{NONNEGINT:priority}>

HTTPDATE %{MONTHDAY}/%{MONTH}/%{YEAR}:%{TIME} %{INT}


# Shortcuts

QS %{QUOTEDSTRING}


# Log formats

SYSLOGBASE %{SYSLOGTIMESTAMP:timestamp} (?:%{SYSLOGFACILITY} )?%{SYSLOGHOST:logsource} %{SYSLOGPROG}:

COMMONAPACHELOG %{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-)

COMBINEDAPACHELOG %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent}


# Log Levels

LOGLEVEL ([Aa]lert|ALERT|[Tt]race|TRACE|[Dd]ebug|DEBUG|[Nn]otice|NOTICE|[Ii]nfo|INFO|[Ww]arn?(?:ing)?|WARN?(?:ING)?|[Ee]rr?(?:or)?|ERR?(?:OR)?|[Cc]rit?(?:ical)?|CRIT?(?:ICAL)?|[Ff]atal|FATAL|[Ss]evere|SEVERE|EMERG(?:ENCY)?|[Ee]merg(?:ency)?)

九.Java API

代码地址

1.document

1.pom

<dependencies>

   <!--es客户端-->

   <dependency>

       <groupId>org.elasticsearch.client</groupId>

      <!-- low:偏向底层。high :高級封装。足够。-->

       <artifactId>elasticsearch-rest-high-level-client</artifactId>

       <version>7.3.0</version>

       <exclusions>

           <exclusion>

               <groupId>org.elasticsearch</groupId>

               <artifactId>elasticsearch</artifactId>

           </exclusion>

       </exclusions>

   </dependency>

   <dependency>

       <groupId>org.elasticsearch</groupId>

       <artifactId>elasticsearch</artifactId>

       <version>7.3.0</version>

   </dependency>


   <dependency>

       <groupId>org.springframework.boot</groupId>

       <artifactId>spring-boot-starter</artifactId>

       <version>2.0.6.RELEASE</version>

   </dependency>

   <dependency>

       <groupId>org.springframework.boot</groupId>

       <artifactId>spring-boot-starter-test</artifactId>

       <scope>test</scope>

       <version>2.0.6.RELEASE</version>

   </dependency>

   <dependency>

       <groupId>org.projectlombok</groupId>

       <artifactId>lombok</artifactId>

       <version>1.16.10</version>

   </dependency>

</dependencies>

2.yaml

spring:

 application:

   name: search-service

kwan:

 elasticsearch:

   hostlist: 47.119.162.180:9200 #多个节点用逗号分隔

3.config

@Configuration

public class ElasticsearchConfig {


   @Value("${kwan.elasticsearch.hostlist}")

   private String hostlist;


   @Bean(destroyMethod = "close")

   public RestHighLevelClient restHighLevelClient() {

       String[] split = hostlist.split(",");

       HttpHost[] httpHostsArray = new HttpHost[split.length];

       for (int i = 0; i < split.length; i++) {

           String item = split[i];

           httpHostsArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");

       }

       return new RestHighLevelClient(RestClient.builder(httpHostsArray));

   }

}

4.get

@Slf4j

@SpringBootTest(classes = SearchApplication.class)

@RunWith(SpringRunner.class)

public class TestDocument_01_get {


   @Autowired

   RestHighLevelClient client;


   @Test

   public void testGet() throws IOException {

       //构建请求

       GetRequest getRequest = new GetRequest("test_post", "1");

       //添加可选参数

       String[] includes = new String[]{"id", "comment"};

       String[] excludes = Strings.EMPTY_ARRAY;

       FetchSourceContext fetchSourceContext = new FetchSourceContext(true, includes, excludes);

       getRequest.fetchSourceContext(fetchSourceContext);

       //同步查询

       GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);

       //获取结果

       if (getResponse.isExists()) {

           log.info(getResponse.getId());

           log.info(String.valueOf(getResponse.getVersion()));

           log.info(getResponse.getSourceAsString());//以string获取数据

           log.info(String.valueOf(getResponse.getSourceAsBytes()));////以Bytes获取数据

           log.info(String.valueOf(getResponse.getSourceAsMap()));//以Map获取数据

       } else {

           log.info("数据不存在");

       }

   }

}

@Test

   public void testGet() {

       //构建请求

       GetRequest getRequest = new GetRequest("test_post", "1");

       //添加可选参数

       String[] includes = new String[]{"id", "title"};

       String[] excludes = Strings.EMPTY_ARRAY;

       FetchSourceContext fetchSourceContext = new FetchSourceContext(true, includes, excludes);

       getRequest.fetchSourceContext(fetchSourceContext);

       //设置监听器

       ActionListener<GetResponse> listener = new ActionListener<GetResponse>() {

           //成功时

           public void onResponse(GetResponse getResponse) {

               log.info(getResponse.getId());

               log.info(String.valueOf(getResponse.getVersion()));

               log.info(getResponse.getSourceAsString());

           }


           //失败时

           public void onFailure(Exception e) {

               e.printStackTrace();

               log.info("数据获取异常");

           }

       };

       //异步查询

       client.getAsync(getRequest, RequestOptions.DEFAULT, listener);

       try {

           Thread.sleep(5000);

       } catch (InterruptedException e) {

           e.printStackTrace();

       }

   }

5.add

@Test

   public void testAdd() throws IOException {

       //构建请求

       IndexRequest request = new IndexRequest("test_post");

       request.id("5");

       //构建文档数据

       String jsonString = "{\n" +

               "  \"user\":\"tomas\",\n" +

               "  \"postDate\":\"2019-07-18\",\n" +

               "  \"message\":\"trying out es1\"\n" +

               "}";

       request.source(jsonString, XContentType.JSON);

       //同步

       IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);

       //获取结果

       log.info(indexResponse.getIndex());

       log.info(indexResponse.getId());

       log.info(String.valueOf(indexResponse.getResult()));

       if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) {

           DocWriteResponse.Result result = indexResponse.getResult();

           log.info("CREATE" + result);

       } else if (indexResponse.getResult() == DocWriteResponse.Result.UPDATED) {

           DocWriteResponse.Result result = indexResponse.getResult();

           log.info("UPDATED" + result);

       } else {

           log.info("其他操作");

       }

       //获取分片信息

       ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo();

       if (shardInfo.getTotal() != shardInfo.getSuccessful()) {

           log.info("处理成功的分片数少于总分片!");

       }

       if (shardInfo.getFailed() > 0) {

           for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) {

               String reason = failure.reason();//每一个错误的原因

               log.info(reason);

           }

       }

   }

@Test

   public void testAdd() throws IOException {

       //构建请求

       IndexRequest request = new IndexRequest("test_post");

       request.id("6");

       //构建文档数据

       Map<String, Object> jsonMap = new HashMap<String, Object>();

       jsonMap.put("user", "tomas");

       jsonMap.put("postDate", "2019-07-18");

       jsonMap.put("message", "trying out es1");

       request.source(jsonMap);

       //同步执行

       IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);

       //获取结果

       log.info(indexResponse.getIndex());

       log.info(indexResponse.getId());

       log.info(String.valueOf(indexResponse.getResult()));

       if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) {

           DocWriteResponse.Result result = indexResponse.getResult();

           log.info("CREATE" + result);

       } else if (indexResponse.getResult() == DocWriteResponse.Result.UPDATED) {

           DocWriteResponse.Result result = indexResponse.getResult();

           log.info("UPDATED" + result);

       }

       ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo();

       if (shardInfo.getTotal() != shardInfo.getSuccessful()) {

           log.info("处理成功的分片数少于总分片!");

       }

       if (shardInfo.getFailed() > 0) {

           for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) {

               String reason = failure.reason();//每一个错误的原因

               log.info(reason);

           }

       }

   }

@Test

   public void testAdd() throws IOException {

       //构建请求

       IndexRequest request = new IndexRequest("test_post");

       request.id("7");

       //构建文档数据

       XContentBuilder builder = XContentFactory.jsonBuilder();

       builder.startObject();

       {

           builder.field("user", "tomas");

           builder.field("message", "trying out es1");

           builder.timeField("postDate", "2019-07-18");

       }

       builder.endObject();

       request.source(builder);

       //同步执行

       IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);

       //获取结果

       log.info(indexResponse.getIndex());

       log.info(indexResponse.getId());

       log.info(String.valueOf(indexResponse.getResult()));

       if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) {

           DocWriteResponse.Result result = indexResponse.getResult();

           log.info("CREATE" + result);

       } else if (indexResponse.getResult() == DocWriteResponse.Result.UPDATED) {

           DocWriteResponse.Result result = indexResponse.getResult();

           log.info("UPDATED" + result);

       }

       ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo();

       if (shardInfo.getTotal() != shardInfo.getSuccessful()) {

           log.info("处理成功的分片数少于总分片!");

       }

       if (shardInfo.getFailed() > 0) {

           for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) {

               String reason = failure.reason();//每一个错误的原因

               log.info(reason);

           }

       }

   }

@Test

   public void testAdd() throws IOException {

       //构建请求

       IndexRequest request = new IndexRequest("test_post");

       request.id("9");

       //构建文档数据

       request.source("user", "tomas",

               "message", "trying out es1",

               "postDate", "2019-07-18");

       //同步执行

       IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);

       //获取结果

       log.info(indexResponse.getIndex());

       log.info(indexResponse.getId());

       log.info(String.valueOf(indexResponse.getResult()));

       if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) {

           DocWriteResponse.Result result = indexResponse.getResult();

           log.info("CREATE" + result);

       } else if (indexResponse.getResult() == DocWriteResponse.Result.UPDATED) {

           DocWriteResponse.Result result = indexResponse.getResult();

           log.info("UPDATED" + result);

       }

       ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo();

       if (shardInfo.getTotal() != shardInfo.getSuccessful()) {

           log.info("处理成功的分片数少于总分片!");

       }

       if (shardInfo.getFailed() > 0) {

           for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) {

               String reason = failure.reason();//每一个错误的原因

               log.info(reason);

           }

       }

   }

@Test

   public void testAdd() throws IOException {

       //构建请求

       IndexRequest request = new IndexRequest("test_post");

       request.id("10");

       //构建文档数据

       String jsonString = "{\n" +

               "  \"user\":\"tomas\",\n" +

               "  \"postDate\":\"2019-07-18\",\n" +

               "  \"message\":\"trying out es1\"\n" +

               "}";

       request.source(jsonString, XContentType.JSON);

       //设置超时时间

       request.timeout("1s");

       request.timeout(TimeValue.timeValueSeconds(1));

       //手动维护版本号

       request.version(4);

       request.versionType(VersionType.EXTERNAL);

       //同步执行

       IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);

       //获取结果

       log.info(indexResponse.getIndex());

       log.info(indexResponse.getId());

       log.info(String.valueOf(indexResponse.getResult()));

       if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) {

           DocWriteResponse.Result result = indexResponse.getResult();

           log.info("CREATE" + result);

       } else if (indexResponse.getResult() == DocWriteResponse.Result.UPDATED) {

           DocWriteResponse.Result result = indexResponse.getResult();

           log.info("UPDATED" + result);

       }

       ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo();

       if (shardInfo.getTotal() != shardInfo.getSuccessful()) {

           log.info("处理成功的分片数少于总分片!");

       }

       if (shardInfo.getFailed() > 0) {

           for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) {

               String reason = failure.reason();//每一个错误的原因

               log.info(reason);

           }

       }

   }

6.update

@Test

   public void testUpdate() throws IOException {

       //创建请求

       UpdateRequest request = new UpdateRequest("test_post", "5");

       Map<String, Object> jsonMap = new HashMap<>();

       jsonMap.put("user", "tomas Lee");

       request.doc(jsonMap);

       request.timeout("1s");

       request.retryOnConflict(3);//重试次数

       //同步执行

       UpdateResponse updateResponse = client.update(request, RequestOptions.DEFAULT);

       //获取结果

       updateResponse.getId();

       updateResponse.getIndex();

       //判断结果

       if (updateResponse.getResult() == DocWriteResponse.Result.CREATED) {

           DocWriteResponse.Result result = updateResponse.getResult();

           log.info("CREATED:" + result);

       } else if (updateResponse.getResult() == DocWriteResponse.Result.UPDATED) {

           DocWriteResponse.Result result = updateResponse.getResult();

           log.info("UPDATED:" + result);

       } else if (updateResponse.getResult() == DocWriteResponse.Result.DELETED) {

           DocWriteResponse.Result result = updateResponse.getResult();

           log.info("DELETED:" + result);

       } else if (updateResponse.getResult() == DocWriteResponse.Result.NOOP) {

           //没有操作

           DocWriteResponse.Result result = updateResponse.getResult();

           log.info("NOOP:" + result);

       }

   }

7.delete

@Test

   public void testDelete() throws IOException {

       //创建请求

       DeleteRequest request = new DeleteRequest("test_post", "3");

       //执行

       DeleteResponse deleteResponse = client.delete(request, RequestOptions.DEFAULT);

       //获取结果

       deleteResponse.getId();

       deleteResponse.getIndex();

       DocWriteResponse.Result result = deleteResponse.getResult();

       log.info(result.toString());

   }

8.bulk

@Test

   public void testBulk() throws IOException {

       //创建请求

       BulkRequest request = new BulkRequest();

       request.add(new IndexRequest("post").id("1").source(XContentType.JSON, "field", "1"));

       request.add(new IndexRequest("post").id("2").source(XContentType.JSON, "field", "2"));

       request.add(new UpdateRequest("post", "1").doc(XContentType.JSON, "field", "3"));

       request.add(new DeleteRequest("post").id("2"));

       //执行

       BulkResponse bulkResponse = client.bulk(request, RequestOptions.DEFAULT);

       //获取结果

       for (BulkItemResponse itemResponse : bulkResponse) {

           DocWriteResponse response = itemResponse.getResponse();

           switch (itemResponse.getOpType()) {

               case INDEX:

                   IndexResponse indexResponse = (IndexResponse) response;

                   log.info("INDEX:" + indexResponse.getResult());

                   break;

               case CREATE:

                   IndexResponse createResponse = (IndexResponse) response;

                   log.info("CREATE:" + createResponse.getResult());

                   break;

               case UPDATE:

                   UpdateResponse updateResponse = (UpdateResponse) response;

                   log.info("UPDATE:" + updateResponse.getResult());

                   break;

               case DELETE:

                   DeleteResponse deleteResponse = (DeleteResponse) response;

                   log.info("DELETE:" + deleteResponse.getResult());

                   break;

           }

       }

   }

2.index

1.创建索引

@SpringBootTest

@RunWith(SpringRunner.class)

public class TestIndex_01_Create {

   @Autowired

   private RestHighLevelClient client;


   //创建索引

   @Test

   public void testCreateIndex() throws IOException {

//        PUT /my_index

//        {

//            "settings": {

//            "number_of_shards": 1,

//                    "number_of_replicas": 1

//        },

//            "mappings": {

//            "properties": {

//                "field1":{

//                    "type": "text"

//                },

//                "field2":{

//                    "type": "text"

//                }

//            }

//        },

//            "aliases": {

//            "default_index": {}

//        }

//        }

       //创建索引对象

       CreateIndexRequest createIndexRequest = new CreateIndexRequest("itheima_book");

       //设置参数

       createIndexRequest.settings(Settings.builder().put("number_of_shards", "1").put("number_of_replicas", "0"));

       //指定映射1

       createIndexRequest.mapping(" {\n" +

               " \t\"properties\": {\n" +

               "            \"name\":{\n" +

               "             \"type\":\"keyword\"\n" +

               "           },\n" +

               "           \"description\": {\n" +

               "              \"type\": \"text\"\n" +

               "           },\n" +

               "            \"price\":{\n" +

               "             \"type\":\"long\"\n" +

               "           },\n" +

               "           \"pic\":{\n" +

               "             \"type\":\"text\",\n" +

               "             \"index\":false\n" +

               "           }\n" +

               " \t}\n" +

               "}", XContentType.JSON);

       //设置别名

       createIndexRequest.alias(new Alias("itheima_index_new"));

       // 额外参数

       //设置超时时间

       createIndexRequest.setTimeout(TimeValue.timeValueMinutes(2));

       //设置主节点超时时间

       createIndexRequest.setMasterTimeout(TimeValue.timeValueMinutes(1));

       //在创建索引API返回响应之前等待的活动分片副本的数量,以int形式表示

       createIndexRequest.waitForActiveShards(ActiveShardCount.from(2));

       createIndexRequest.waitForActiveShards(ActiveShardCount.DEFAULT);

       //操作索引的客户端

       IndicesClient indices = client.indices();

       //执行创建索引库

       CreateIndexResponse createIndexResponse = indices.create(createIndexRequest, RequestOptions.DEFAULT);

       //得到响应

       boolean acknowledged = createIndexResponse.isAcknowledged();

       //得到响应 指示是否在超时前为索引中的每个分片启动了所需数量的碎片副本

       boolean shardsAcknowledged = createIndexResponse.isShardsAcknowledged();

       System.out.println("acknowledged:" + acknowledged);

       System.out.println("shardsAcknowledged:" + shardsAcknowledged);

   }

}

@SpringBootTest

@RunWith(SpringRunner.class)

public class TestIndex_04_Async {

   @Autowired

   private RestHighLevelClient client;


   //创建索引异步方式

   @Test

   public void testCreateIndexAsync() {

       //创建索引对象

       CreateIndexRequest createIndexRequest = new CreateIndexRequest("itheima_book");

       //设置参数

       createIndexRequest.settings(Settings.builder().put("number_of_shards", "1").put("number_of_replicas", "0"));

       //指定映射1

       createIndexRequest.mapping(" {\n" +

               " \t\"properties\": {\n" +

               "            \"name\":{\n" +

               "             \"type\":\"keyword\"\n" +

               "           },\n" +

               "           \"description\": {\n" +

               "              \"type\": \"text\"\n" +

               "           },\n" +

               "            \"price\":{\n" +

               "             \"type\":\"long\"\n" +

               "           },\n" +

               "           \"pic\":{\n" +

               "             \"type\":\"text\",\n" +

               "             \"index\":false\n" +

               "           }\n" +

               " \t}\n" +

               "}", XContentType.JSON);

       //设置别名

       createIndexRequest.alias(new Alias("itheima_index_new"));

       // 额外参数

       //设置超时时间

       createIndexRequest.setTimeout(TimeValue.timeValueMinutes(2));

       //设置主节点超时时间

       createIndexRequest.setMasterTimeout(TimeValue.timeValueMinutes(1));

       //在创建索引API返回响应之前等待的活动分片副本的数量,以int形式表示

       createIndexRequest.waitForActiveShards(ActiveShardCount.from(2));

       createIndexRequest.waitForActiveShards(ActiveShardCount.DEFAULT);

       //操作索引的客户端

       IndicesClient indices = client.indices();

       //执行创建索引库

       ActionListener<CreateIndexResponse> listener = new ActionListener<CreateIndexResponse>() {

           @Override

           public void onResponse(CreateIndexResponse createIndexResponse) {

               //得到响应(全部)

               boolean acknowledged = createIndexResponse.isAcknowledged();

               //得到响应 指示是否在超时前为索引中的每个分片启动了所需数量的碎片副本

               boolean shardsAcknowledged = createIndexResponse.isShardsAcknowledged();

               System.out.println("acknowledged:" + acknowledged);

               System.out.println("shardsAcknowledged:" + shardsAcknowledged);

           }


           @Override

           public void onFailure(Exception e) {

               e.printStackTrace();

           }

       };

       client.indices().createAsync(createIndexRequest, RequestOptions.DEFAULT, listener);

       try {

           Thread.sleep(5000);

       } catch (InterruptedException e) {

           e.printStackTrace();

       }

   }

}

2.删除索引

@Test

   public void testDeleteIndex() throws IOException {

       //创建删除索引请求

       DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("itheima_book");

       //        执行

       AcknowledgedResponse delete = client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);

       //得到相应

       boolean acknowledged = delete.isAcknowledged();

       System.out.println("acknowledged:" + acknowledged);


   }

3.是否存在索引

@Test

   public void testExistIndex() throws IOException {

       GetIndexRequest request = new GetIndexRequest("itheima_book");

       //参数

       request.local(false);//从主节点返回本地索引信息状态

       request.humanReadable(true);//以适合人类的格式返回

       request.includeDefaults(false);//是否返回每个索引的所有默认配置

       boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);

       System.out.println("exists:" + exists);

   }

4.关闭索引

@Test

   public void testCloseIndex() throws IOException {

       CloseIndexRequest request = new CloseIndexRequest("itheima_book");

       AcknowledgedResponse close = client.indices().close(request, RequestOptions.DEFAULT);

       boolean acknowledged = close.isAcknowledged();

       System.out.println("acknowledged:" + acknowledged);

   }

5.打开索引

@Test

   public void testOpenIndex() throws IOException {

       OpenIndexRequest request = new OpenIndexRequest("itheima_book");

       OpenIndexResponse open = client.indices().open(request, RequestOptions.DEFAULT);

       boolean acknowledged = open.isAcknowledged();

       System.out.println("acknowledged:" + acknowledged);

   }

3.search

1.all

@SpringBootTest

@RunWith(SpringRunner.class)

public class TestSearch_01_all {


   @Autowired

   RestHighLevelClient client;


   @Test

   public void testSearchAll() throws IOException {

//        GET book/_search

//        {

//            "query": {

//                "match_all": {}

//             }

//        }

       //1构建搜索请求

       SearchRequest searchRequest = new SearchRequest("book");

       SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

       searchSourceBuilder.query(QueryBuilders.matchAllQuery());

       //获取某些字段

       searchSourceBuilder.fetchSource(new String[]{"name"}, new String[]{});

       searchRequest.source(searchSourceBuilder);

       //2执行搜索

       SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

       //3获取结果

       SearchHits hits = searchResponse.getHits();

       //数据数据

       SearchHit[] searchHits = hits.getHits();

       System.out.println("--------------------------");

       for (SearchHit hit : searchHits) {

           String id = hit.getId();

           float score = hit.getScore();

           Map<String, Object> sourceAsMap = hit.getSourceAsMap();

           String name = (String) sourceAsMap.get("name");

           String description = (String) sourceAsMap.get("description");

           Double price = (Double) sourceAsMap.get("price");

           System.out.println("id:" + id);

           System.out.println("score:" + score);

           System.out.println("name:" + name);

           System.out.println("description:" + description);

           System.out.println("price:" + price);

           System.out.println("==========================");

       }

   }

}

2.page

@Test

   public void testSearchPage() throws IOException {

//    GET book/_search

//    {

//        "query": {

//          "match_all": {}

//       },

//        "from": 0,

//        "size": 2

//    }

       //1构建搜索请求

       SearchRequest searchRequest = new SearchRequest("book");

       SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

       searchSourceBuilder.query(QueryBuilders.matchAllQuery());

       //第几页

       int page = 1;

       //每页几个

       int size = 2;

       //下标计算

       int from = (page - 1) * size;

       searchSourceBuilder.from(from);

       searchSourceBuilder.size(size);

       searchRequest.source(searchSourceBuilder);

       //2执行搜索

       SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

       //3获取结果

       SearchHits hits = searchResponse.getHits();

       //数据数据

       SearchHit[] searchHits = hits.getHits();

       System.out.println("--------------------------");

       for (SearchHit hit : searchHits) {

           String id = hit.getId();

           float score = hit.getScore();

           Map<String, Object> sourceAsMap = hit.getSourceAsMap();

           String name = (String) sourceAsMap.get("name");

           String description = (String) sourceAsMap.get("description");

           Double price = (Double) sourceAsMap.get("price");

           System.out.println("id:" + id);

           System.out.println("name:" + name);

           System.out.println("description:" + description);

           System.out.println("price:" + price);

           System.out.println("==========================");

       }

   }

3.ids

@Test

   public void testSearchIds() throws IOException {

//    GET /book/_search

//    {

//        "query": {

//           "ids" : {

//             "values" : ["1", "4", "100"]

//          }

//     }

//    }

       //1构建搜索请求

       SearchRequest searchRequest = new SearchRequest("book");

       SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

       searchSourceBuilder.query(QueryBuilders.idsQuery().addIds("1", "4", "100"));

       searchRequest.source(searchSourceBuilder);

       //2执行搜索

       SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

       //3获取结果

       SearchHits hits = searchResponse.getHits();

       //数据数据

       SearchHit[] searchHits = hits.getHits();

       System.out.println("--------------------------");

       for (SearchHit hit : searchHits) {

           String id = hit.getId();

           float score = hit.getScore();

           Map<String, Object> sourceAsMap = hit.getSourceAsMap();

           String name = (String) sourceAsMap.get("name");

           String description = (String) sourceAsMap.get("description");

           Double price = (Double) sourceAsMap.get("price");

           System.out.println("id:" + id);

           System.out.println("name:" + name);

           System.out.println("description:" + description);

           System.out.println("price:" + price);

           System.out.println("==========================");

       }

   }

4.match

@Test

   public void testSearchMatch() throws IOException {

//    GET /book/_search

//    {

//        "query": {

//           "match": {

//            "description": "java程序员"

//        }

//      }

//    }

       //1构建搜索请求

       SearchRequest searchRequest = new SearchRequest("book");

       SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

       searchSourceBuilder.query(QueryBuilders.matchQuery("description", "java程序员"));

       searchRequest.source(searchSourceBuilder);

       //2执行搜索

       SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

       //3获取结果

       SearchHits hits = searchResponse.getHits();

       //数据数据

       SearchHit[] searchHits = hits.getHits();

       System.out.println("--------------------------");

       for (SearchHit hit : searchHits) {

           String id = hit.getId();

           float score = hit.getScore();

           Map<String, Object> sourceAsMap = hit.getSourceAsMap();

           String name = (String) sourceAsMap.get("name");

           String description = (String) sourceAsMap.get("description");

           Double price = (Double) sourceAsMap.get("price");

           System.out.println("id:" + id);

           System.out.println("name:" + name);

           System.out.println("description:" + description);

           System.out.println("price:" + price);

           System.out.println("==========================");

       }

   }

5.term

@Test

   public void testSearchTerm() throws IOException {

//

//    GET /book/_search

//    {

//        "query": {

//           "term": {

//            "description": "java程序员"

//        }

//      }

//    }

       //1构建搜索请求

       SearchRequest searchRequest = new SearchRequest("book");

       SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

       searchSourceBuilder.query(QueryBuilders.termQuery("description", "java程序员"));

       searchRequest.source(searchSourceBuilder);

       //2执行搜索

       SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

       //3获取结果

       SearchHits hits = searchResponse.getHits();

       //数据数据

       SearchHit[] searchHits = hits.getHits();

       System.out.println("--------------------------");

       for (SearchHit hit : searchHits) {

           String id = hit.getId();

           float score = hit.getScore();

           Map<String, Object> sourceAsMap = hit.getSourceAsMap();

           String name = (String) sourceAsMap.get("name");

           String description = (String) sourceAsMap.get("description");

           Double price = (Double) sourceAsMap.get("price");

           System.out.println("id:" + id);

           System.out.println("name:" + name);

           System.out.println("description:" + description);

           System.out.println("price:" + price);

           System.out.println("==========================");

       }

   }

6.multi_match

@Test

   public void testSearchMultiMatch() throws IOException {

//    GET /book/_search

//    {

//        "query": {

//          "multi_match": {

//            "query": "java程序员",

//            "fields": ["name", "description"]

//        }

//      }

//    }

       //1构建搜索请求

       SearchRequest searchRequest = new SearchRequest("book");

       SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

       searchSourceBuilder.query(QueryBuilders.multiMatchQuery("java程序员", "name", "description"));

       searchRequest.source(searchSourceBuilder);

       //2执行搜索

       SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

       //3获取结果

       SearchHits hits = searchResponse.getHits();

       //数据数据

       SearchHit[] searchHits = hits.getHits();

       System.out.println("--------------------------");

       for (SearchHit hit : searchHits) {

           String id = hit.getId();

           float score = hit.getScore();

           Map<String, Object> sourceAsMap = hit.getSourceAsMap();

           String name = (String) sourceAsMap.get("name");

           String description = (String) sourceAsMap.get("description");

           Double price = (Double) sourceAsMap.get("price");

           System.out.println("id:" + id);

           System.out.println("name:" + name);

           System.out.println("description:" + description);

           System.out.println("price:" + price);

           System.out.println("==========================");

       }

   }

7.bool

@Test

   public void testSearchBool() throws IOException {

//    GET /book/_search

//    {

//        "query": {

//          "bool": {

//            "must": [

//            {

//                "multi_match": {

//                  "query": "java程序员",

//                  "fields": ["name","description"]

//            }

//            }

//      ],

//            "should": [

//            {

//                "match": {

//                "studymodel": "201001"

//            }

//            }

//      ]

//        }

//    }

//    }

       //1构建搜索请求

       SearchRequest searchRequest = new SearchRequest("book");

       SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

       //构建multiMatch请求

       MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery("java程序员", "name", "description");

       //构建match请求

       MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("studymodel", "201001");

       BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

       boolQueryBuilder.must(multiMatchQueryBuilder);

       boolQueryBuilder.should(matchQueryBuilder);

       searchSourceBuilder.query(boolQueryBuilder);

       searchRequest.source(searchSourceBuilder);

       //2执行搜索

       SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

       //3获取结果

       SearchHits hits = searchResponse.getHits();

       //数据数据

       SearchHit[] searchHits = hits.getHits();

       System.out.println("--------------------------");

       for (SearchHit hit : searchHits) {

           String id = hit.getId();

           float score = hit.getScore();

           Map<String, Object> sourceAsMap = hit.getSourceAsMap();

           String name = (String) sourceAsMap.get("name");

           String description = (String) sourceAsMap.get("description");

           Double price = (Double) sourceAsMap.get("price");

           System.out.println("id:" + id);

           System.out.println("name:" + name);

           System.out.println("description:" + description);

           System.out.println("price:" + price);

           System.out.println("==========================");

       }

   }

8.filter

@Test

   public void testSearchFilter() throws IOException {

//    GET /book/_search

//    {

//        "query": {

//          "bool": {

//            "must": [

//            {

//                "multi_match": {

//                "query": "java程序员",

//                        "fields": ["name","description"]

//            }

//            }

//      ],

//            "should": [

//            {

//                "match": {

//                "studymodel": "201001"

//            }

//            }

//          ],

//            "filter": {

//                "range": {

//                    "price": {

//                        "gte": 50,

//                         "lte": 90

//                    }

//                }

//

//            }

//        }

//    }

//    }

       //1构建搜索请求

       SearchRequest searchRequest = new SearchRequest("book");

       SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

       //构建multiMatch请求

       MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery("java程序员", "name", "description");

       //构建match请求

       MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("studymodel", "201001");

       BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

       boolQueryBuilder.must(multiMatchQueryBuilder);

       boolQueryBuilder.should(matchQueryBuilder);

       boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(50).lte(90));

       searchSourceBuilder.query(boolQueryBuilder);

       searchRequest.source(searchSourceBuilder);

       //2执行搜索

       SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

       //3获取结果

       SearchHits hits = searchResponse.getHits();

       //数据数据

       SearchHit[] searchHits = hits.getHits();

       System.out.println("--------------------------");

       for (SearchHit hit : searchHits) {

           String id = hit.getId();

           float score = hit.getScore();

           Map<String, Object> sourceAsMap = hit.getSourceAsMap();

           String name = (String) sourceAsMap.get("name");

           String description = (String) sourceAsMap.get("description");

           Double price = (Double) sourceAsMap.get("price");

           System.out.println("id:" + id);

           System.out.println("name:" + name);

           System.out.println("description:" + description);

           System.out.println("price:" + price);

           System.out.println("==========================");

       }

   }

9.sort

@Test

   public void testSearchSort() throws IOException {

//    GET /book/_search

//    {

//        "query": {

//        "bool": {

//            "must": [

//            {

//                "multi_match": {

//                "query": "java程序员",

//                        "fields": ["name","description"]

//            }

//            }

//      ],

//            "should": [

//            {

//                "match": {

//                "studymodel": "201001"

//            }

//            }

//      ],

//            "filter": {

//                "range": {

//                    "price": {

//                        "gte": 50,

//                                "lte": 90

//                    }

//                }

//

//            }

//        }

//    },

//        "sort": [

//        {

//            "price": {

//            "order": "asc"

//        }

//        }

//  ]

//    }

       //1构建搜索请求

       SearchRequest searchRequest = new SearchRequest("book");

       SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

       //构建multiMatch请求

       MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery("java程序员", "name", "description");

       //构建match请求

       MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("studymodel", "201001");

       BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

       boolQueryBuilder.must(multiMatchQueryBuilder);

       boolQueryBuilder.should(matchQueryBuilder);

       boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(50).lte(90));

       searchSourceBuilder.query(boolQueryBuilder);

       //按照价格升序

       searchSourceBuilder.sort("price", SortOrder.ASC);

       searchRequest.source(searchSourceBuilder);

       //2执行搜索

       SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

       //3获取结果

       SearchHits hits = searchResponse.getHits();

       //数据数据

       SearchHit[] searchHits = hits.getHits();

       System.out.println("--------------------------");

       for (SearchHit hit : searchHits) {

           String id = hit.getId();

           float score = hit.getScore();

           Map<String, Object> sourceAsMap = hit.getSourceAsMap();

           String name = (String) sourceAsMap.get("name");

           String description = (String) sourceAsMap.get("description");

           Double price = (Double) sourceAsMap.get("price");

           System.out.println("id:" + id);

           System.out.println("name:" + name);

           System.out.println("description:" + description);

           System.out.println("price:" + price);

           System.out.println("==========================");

       }

   }

4.sql 功能

前提 es 拥有白金版功能:

kibana 中管理-》许可管理 开启白金版试用

导入依赖:

<dependency>

 <groupId>org.elasticsearch.plugin</groupId>

 <artifactId>x-pack-sql-jdbc</artifactId>

 <version>7.3.0</version>

</dependency>


<repositories>

 <repository>

     <id>elastic.co</id>

     <url>https://artifacts.elastic.co/maven</url>

 </repository>

</repositories>

代码:

public class TestJdbc {

   public static void main(String[] args) {

       //1创建连接

       try {

           Connection connection = DriverManager.getConnection("jdbc:es://http://localhost:9200");

           //2创建statement

           Statement statement = connection.createStatement();

           //3执行sql语句

           ResultSet resultSet = statement.executeQuery("SELECT * FROM tvs");

           //4获取结果

           while (resultSet.next()) {

               System.out.println(resultSet.getString(1));

               System.out.println(resultSet.getString(2));

               System.out.println(resultSet.getString(3));

               System.out.println(resultSet.getString(4));

               System.out.println("======================================");

           }

       } catch (SQLException e) {

           e.printStackTrace();

       }

   }

}

5.学成在线站内搜索模块

1.mysql 导入学生数据

/*

Navicat Premium Data Transfer


Source Server         : local

Source Server Type    : MySQL

Source Server Version : 50721

Source Host           : localhost:3306

Source Schema         : xc_course


Target Server Type    : MySQL

Target Server Version : 50721

File Encoding         : 65001


Date: 10/11/2019 02:50:34

*/

SET

 NAMES utf8mb4;


SET

 FOREIGN_KEY_CHECKS = 0;


-- ----------------------------

-- Table structure for course_pub

-- ----------------------------

DROP TABLE IF EXISTS `course_pub`;


CREATE TABLE `course_pub` (

 `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',

 `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '课程名称',

 `users` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '适用人群',

 `mt` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '大分类',

 `st` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '小分类',

 `grade` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '课程等级',

 `studymodel` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '学习模式',

 `teachmode` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '教育模式',

 `description` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '课程介绍',

 `timestamp` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '时间戳logstash使用',

 `charge` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '收费规则,对应数据字典',

 `valid` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '有效性,对应数据字典',

 `qq` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '咨询qq',

 `price` float(10, 2) NULL DEFAULT NULL COMMENT '价格',

 `price_old` float(10, 2) NULL DEFAULT NULL COMMENT '原价格',

 `expires` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '过期时间',

 `start_time` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '课程有效期-开始时间',

 `end_time` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '课程有效期-结束时间',

 `pic` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '课程图片',

 `teachplan` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '课程计划',

 `pub_time` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '发布时间',

 PRIMARY KEY (`id`) USING BTREE

) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;


-- ----------------------------

-- Records of course_pub

-- ----------------------------

INSERT INTO

 `course_pub`

VALUES

 (

   '297e7c7c62b888f00162b8a7dec20000',

   'test_java基础33',

   'b1',

   '1-3',

   '1-3-3',

   '200002',

   '201002',

   NULL,

   'java 从入门到删库跑路',

   '2019-10-28 11:26:25',

   '203002',

   '204002',

   '32432',

   NULL,

   NULL,

   NULL,

   NULL,

   NULL,

   'group1/M00/00/00/wKgZhV2tIgiAaYVMAAA2T52Dthw246.jpg',

   '{\"children\":[{\"children\":[],\"id\":\"40288f9b6e0c10d8016e0c37f72a0000\",\"pname\":\"1\"},{\"children\":[{\"id\":\"40288581632b593e01632bd53ff10001\",\"mediaFileoriginalname\":\"solr.avi\",\"mediaId\":\"5fbb79a2016c0eb609ecd0cd3dc48016\",\"pname\":\"Hello World\"},{\"id\":\"40288f9b6e106273016e106485f30000\",\"mediaFileoriginalname\":\"lucene.avi\",\"mediaId\":\"c5c75d70f382e6016d2f506d134eee11\",\"pname\":\"java基础\"}],\"id\":\"40288581632b593e01632bd4ec360000\",\"pname\":\"程序入门\"},{\"children\":[{\"id\":\"40288f9b6dce18e3016dcef16d860001\",\"mediaFileoriginalname\":\"solr.avi\",\"mediaId\":\"5fbb79a2016c0eb609ecd0cd3dc48016\",\"pname\":\"三级节点\"}],\"id\":\"40288f9b6dce18e3016dcef12a1d0000\",\"pname\":\"二级节点\"},{\"children\":[{\"id\":\"40288c9a6ca3968e016ca417fa8d0001\",\"mediaFileoriginalname\":\"lucene.avi\",\"mediaId\":\"c5c75d70f382e6016d2f506d134eee11\",\"pname\":\"test04-01\"}],\"id\":\"40288c9a6ca3968e016ca417b4a50000\",\"pname\":\"test04\"},{\"children\":[{\"id\":\"40288581632b593e01632bd5d31f0003\",\"mediaFileoriginalname\":\"solr.avi\",\"mediaId\":\"5fbb79a2016c0eb609ecd0cd3dc48016\",\"pname\":\"表达式\"},{\"id\":\"40288581632b593e01632bd606480004\",\"pname\":\"逻辑运算\"}],\"id\":\"40288581632b593e01632bd597810002\",\"pname\":\"编程基础\"},{\"children\":[{\"id\":\"402881e764034e4301640351f3d70003\",\"pname\":\"一切皆为对象\"}],\"id\":\"402881e764034e430164035091a00002\",\"pname\":\"面向对象\"},{\"children\":[{\"id\":\"402899816ad8457c016ad9282a330001\",\"pname\":\"test06\"}],\"id\":\"402899816ad8457c016ad927ba540000\",\"pname\":\"test05\"}],\"id\":\"4028858162bec7f30162becad8590000\",\"pname\":\"test_java基础33\"}',

   '2019-10-28 11:26:24'

 );


INSERT INTO

 `course_pub`

VALUES

 (

   '297e7c7c62b888f00162b8a965510001',

   'test_java基础node',

   'test_java基础',

   '1-3',

   '1-3-2',

   '200001',

   '201001',

   NULL,

   'test_java基础2test_java基础2test_java基础2test_java基础2test_java基础2test_java基础2test_java基础2test_java基础2test_java基础2test_java基础2',

   '2019-10-24 16:26:34',

   '203001',

   '204001',

   '443242',

   NULL,

   NULL,

   NULL,

   NULL,

   NULL,

   NULL,

   '{\"children\":[{\"children\":[{\"id\":\"402881e66417407b01641744fc650001\",\"pname\":\"入门程序\"}],\"id\":\"402881e66417407b01641744afc30000\",\"pname\":\"基础知识\"},{\"children\":[],\"id\":\"4028858162e5d6e00162e5e0727d0001\",\"pname\":\"java基础语法\"},{\"children\":[{\"id\":\"4028d0866b158241016b502433d60002\",\"pname\":\"第二节\"}],\"id\":\"4028d0866b158241016b5023f51e0001\",\"pname\":\"第二章\"}],\"id\":\"4028858162e5d6e00162e5e0227b0000\",\"pname\":\"test_java基础2\"}',

   '2019-10-24 16:26:33'

 );


SET

 FOREIGN_KEY_CHECKS = 1;

2.创建索引 xc_course

3.创建映射

PUT /xc_course

{

 "settings": {

   "number_of_shards": 1,

   "number_of_replicas": 0

 },

 "mappings": {

   "properties": {

     "description" : {

               "analyzer" : "ik_max_word",

               "search_analyzer": "ik_smart",

              "type" : "text"

           },

           "grade" : {

              "type" : "keyword"

           },

           "id" : {

              "type" : "keyword"

           },

           "mt" : {

              "type" : "keyword"

           },

           "name" : {

               "analyzer" : "ik_max_word",

          "search_analyzer": "ik_smart",

              "type" : "text"

           },

           "users" : {

              "index" : false,

              "type" : "text"

           },

           "charge" : {

              "type" : "keyword"

           },

           "valid" : {

              "type" : "keyword"

           },

           "pic" : {

              "index" : false,

              "type" : "keyword"

           },

           "qq" : {

              "index" : false,

              "type" : "keyword"

           },

           "price" : {

              "type" : "float"

           },

           "price_old" : {

              "type" : "float"

           },

           "st" : {

              "type" : "keyword"

           },

           "status" : {

              "type" : "keyword"

           },

           "studymodel" : {

              "type" : "keyword"

           },

           "teachmode" : {

              "type" : "keyword"

           },

           "teachplan" : {

               "analyzer" : "ik_max_word",

          "search_analyzer": "ik_smart",

              "type" : "text"

           },

          "expires" : {

              "type" : "date",

           "format": "yyyy-MM-dd HH:mm:ss"

           },

           "pub_time" : {

              "type" : "date",

            "format": "yyyy-MM-dd HH:mm:ss"

           },

           "start_time" : {

              "type" : "date",

          "format": "yyyy-MM-dd HH:mm:ss"

           },

         "end_time" : {

                "type" : "date",

          "format": "yyyy-MM-dd HH:mm:ss"

           }

   }

 }

}

4.logstash 创建模板文件

Logstash 的工作是从 MySQL 中读取数据,向 ES 中创建索引,这里需要提前创建 mapping 的模板文件以便 logstash 使用。

在 logstach 的 config 目录创建 xc_course_template.json,内容如下:

{

 "mappings": {

   "doc": {

     "properties": {

       "charge": {

         "type": "keyword"

       },

       "description": {

         "analyzer": "ik_max_word",

         "search_analyzer": "ik_smart",

         "type": "text"

       },

       "end_time": {

         "format": "yyyy-MM-dd HH:mm:ss",

         "type": "date"

       },

       "expires": {

         "format": "yyyy-MM-dd HH:mm:ss",

         "type": "date"

       },

       "grade": {

         "type": "keyword"

       },

       "id": {

         "type": "keyword"

       },

       "mt": {

         "type": "keyword"

       },

       "name": {

         "analyzer": "ik_max_word",

         "search_analyzer": "ik_smart",

         "type": "text"

       },

       "pic": {

         "index": false,

         "type": "keyword"

       },

       "price": {

         "type": "float"

       },

       "price_old": {

         "type": "float"

       },

       "pub_time": {

         "format": "yyyy-MM-dd HH:mm:ss",

         "type": "date"

       },

       "qq": {

         "index": false,

         "type": "keyword"

       },

       "st": {

         "type": "keyword"

       },

       "start_time": {

         "format": "yyyy-MM-dd HH:mm:ss",

         "type": "date"

       },

       "status": {

         "type": "keyword"

       },

       "studymodel": {

         "type": "keyword"

       },

       "teachmode": {

         "type": "keyword"

       },

       "teachplan": {

         "analyzer": "ik_max_word",

         "search_analyzer": "ik_smart",

         "type": "text"

       },

       "users": {

         "index": false,

         "type": "text"

       },

       "valid": {

         "type": "keyword"

       }

     }

   }

 },

 "template": "xc_course"

}

5.logstash 配置 mysql.conf

ES 采用 UTC 时区问题

ES 采用 UTC 时区,比北京时间早 8 小时,所以 ES 读取数据时让最后更新时间加 8 小时

where timestamp > date_add(:sql_last_value,INTERVAL 8 HOUR)

logstash 每个执行完成会在/config/logstash_metadata 记录执行时间下次以此时间为基准进行增量同步数据到索引库。

6.启动

.\logstash.bat -f ..\config\mysql.conf

7.后端代码

代码地址

7.1.Controller

@RestController

@RequestMapping("/search/course")

public class EsCourseController {


   @Autowired

   private EsCourseServiceImpl esCourseServiceImpl;


   @GetMapping(value = "/list/{page}/{size}")

   public QueryResponseResult<CoursePub> list(@PathVariable("page") int page

           , @PathVariable("size") int size, CourseSearchParam courseSearchParam) {

       return esCourseServiceImpl.list(page, size, courseSearchParam);

   }

}

7.2,service

@Service

publicclassEsCourseServiceImplimplementsEsCourseService {

 

   @Value("${kwan.course.source_field}")

   privateStringsource_field;

   @Autowired

   privateRestHighLevelClientrestHighLevelClient;

 

   /**

    * 课程搜索

    *

    * @param page

    * @param size

    * @param courseSearchParam

    * @return

    */

   @Override

   publicQueryResponseResult<CoursePub>list(intpage, intsize, CourseSearchParamcourseSearchParam) {

       if (courseSearchParam==null) {

           courseSearchParam=newCourseSearchParam();

       }

       //1创建搜索请求对象

       SearchRequestsearchRequest=newSearchRequest("xc_course");

       SearchSourceBuildersearchSourceBuilder=newSearchSourceBuilder();

       //过虑源字段

       String[] source_field_array=source_field.split(",");

       searchSourceBuilder.fetchSource(source_field_array, newString[]{});

       //创建布尔查询对象

       BoolQueryBuilderboolQueryBuilder=QueryBuilders.boolQuery();

       //搜索条件

       //根据关键字搜索

       if (StringUtils.isNotEmpty(courseSearchParam.getKeyword())) {

           MultiMatchQueryBuildermultiMatchQueryBuilder=

                   QueryBuilders

                           .multiMatchQuery(courseSearchParam.getKeyword(), "name", "description", "teachplan")

                           .minimumShouldMatch("70%")

                           .field("name", 10);

           boolQueryBuilder.must(multiMatchQueryBuilder);

       }

       if (StringUtils.isNotEmpty(courseSearchParam.getMt())) {

           //根据一级分类

           boolQueryBuilder.filter(QueryBuilders.termQuery("mt", courseSearchParam.getMt()));

       }

       if (StringUtils.isNotEmpty(courseSearchParam.getSt())) {

           //根据二级分类

           boolQueryBuilder.filter(QueryBuilders.termQuery("st", courseSearchParam.getSt()));

       }

       if (StringUtils.isNotEmpty(courseSearchParam.getGrade())) {

           //根据难度等级

           boolQueryBuilder.filter(QueryBuilders.termQuery("grade", courseSearchParam.getGrade()));

       }

       //设置boolQueryBuilder到searchSourceBuilder

       searchSourceBuilder.query(boolQueryBuilder);

       //设置分页参数

       if (page<=0) {

           page=1;

       }

       if (size<=0) {

           size=12;

       }

       //起始记录下标

       intfrom= (page-1) *size;

       searchSourceBuilder.from(from);

       searchSourceBuilder.size(size);

       //设置高亮

       HighlightBuilderhighlightBuilder=newHighlightBuilder();

       highlightBuilder.preTags("<font class='eslight'>");

       highlightBuilder.postTags("</font>");

       //设置高亮字段

       highlightBuilder.fields().add(newHighlightBuilder.Field("name"));

       searchSourceBuilder.highlighter(highlightBuilder);

       searchRequest.source(searchSourceBuilder);

       QueryResult<CoursePub>queryResult=newQueryResult();

       List<CoursePub>list=newArrayList<>();

       try {

           //2执行搜索

           SearchResponsesearchResponse=restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

           //3获取响应结果

           SearchHitshits=searchResponse.getHits();

           longtotalHits=hits.getTotalHits().value;

           //匹配的总记录数

           queryResult.setTotal(totalHits);

           SearchHit[] searchHits=hits.getHits();

           for (SearchHithit : searchHits) {

               CoursePubcoursePub=newCoursePub();

               //源文档

               Map<String, Object>sourceAsMap=hit.getSourceAsMap();

               //取出id

               Stringid= (String) sourceAsMap.get("id");

               coursePub.setId(id);

               //取出name

               Stringname= (String) sourceAsMap.get("name");

               //取出高亮字段name

               Map<String, HighlightField>highlightFields=hit.getHighlightFields();

               if (highlightFields!=null) {

                   HighlightFieldhighlightFieldName=highlightFields.get("name");

                   if (highlightFieldName!=null) {

                       Text[] fragments=highlightFieldName.fragments();

                       StringBufferstringBuffer=newStringBuffer();

                       for (Texttext : fragments) {

                           stringBuffer.append(text);

                       }

                       name=stringBuffer.toString();

                   }

               }

               coursePub.setName(name);

               //图片

               Stringpic= (String) sourceAsMap.get("pic");

               coursePub.setPic(pic);

               //价格

               Doubleprice=null;

               try {

                   if (sourceAsMap.get("price") !=null) {

                       price= (Double) sourceAsMap.get("price");

                   }

               } catch (Exceptione) {

                   e.printStackTrace();

               }

               coursePub.setPrice(price);

               //旧价格

               Doubleprice_old=null;

               try {

                   if (sourceAsMap.get("price_old") !=null) {

                       price_old= (Double) sourceAsMap.get("price_old");

                   }

               } catch (Exceptione) {

                   e.printStackTrace();

               }

               coursePub.setPrice_old(price_old);

               //将coursePub对象放入list

               list.add(coursePub);

           }

       } catch (IOExceptione) {

           e.printStackTrace();

       }

       queryResult.setList(list);

       QueryResponseResult<CoursePub>queryResponseResult=newQueryResponseResult<>(CommonCode.SUCCESS, queryResult);

       returnqueryResponseResult;

   }

}


相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
6月前
|
安全 Linux 开发工具
Elasticsearch 搜索入门技术之一
Elasticsearch 搜索入门技术之一
257 1
|
6月前
|
JSON 自然语言处理 数据库
数据库-ElasticSearch入门(索引、文档、查询)
数据库-ElasticSearch入门(索引、文档、查询)
386 0
|
存储 关系型数据库 数据库
ElasticSearch深度解析入门篇:高效搜索解决方案的介绍与实战案例讲解,带你避坑
ElasticSearch深度解析入门篇:高效搜索解决方案的介绍与实战案例讲解,带你避坑
ElasticSearch深度解析入门篇:高效搜索解决方案的介绍与实战案例讲解,带你避坑
|
1月前
|
存储 Java API
Elasticsearch 7.8.0从入门到精通
这篇文章详细介绍了Elasticsearch 7.8.0的安装、核心概念(如正排索引和倒排索引)、RESTful风格、各种索引和文档操作、条件查询、聚合查询以及在Spring Boot中整合Elasticsearch的步骤和示例。
128 1
Elasticsearch 7.8.0从入门到精通
|
2月前
|
数据可视化 Java Windows
Elasticsearch入门-环境安装ES和Kibana以及ES-Head可视化插件和浏览器插件es-client
本文介绍了如何在Windows环境下安装Elasticsearch(ES)、Elasticsearch Head可视化插件和Kibana,以及如何配置ES的跨域问题,确保Kibana能够连接到ES集群,并提供了安装过程中可能遇到的问题及其解决方案。
Elasticsearch入门-环境安装ES和Kibana以及ES-Head可视化插件和浏览器插件es-client
|
2月前
|
存储 关系型数据库 MySQL
浅谈Elasticsearch的入门与实践
本文主要围绕ES核心特性:分布式存储特性和分析检索能力,介绍了概念、原理与实践案例,希望让读者快速理解ES的核心特性与应用场景。
|
5天前
|
存储 JSON Java
ELK 圣经:Elasticsearch、Logstash、Kibana 从入门到精通
ELK是一套强大的日志管理和分析工具,广泛应用于日志监控、故障排查、业务分析等场景。本文档将详细介绍ELK的各个组件及其配置方法,帮助读者从零开始掌握ELK的使用。
|
3月前
|
JSON 搜索推荐 数据挖掘
ElasticSearch的简单介绍与使用【入门篇】
这篇文章是Elasticsearch的入门介绍,涵盖了Elasticsearch的基本概念、特点、安装方法以及如何进行基本的数据操作,包括索引文档、查询、更新、删除和使用bulk API进行批量操作。
ElasticSearch的简单介绍与使用【入门篇】
|
2月前
|
JSON 监控 Java
Elasticsearch 入门:搭建高性能搜索集群
【9月更文第2天】Elasticsearch 是一个分布式的、RESTful 风格的搜索和分析引擎,基于 Apache Lucene 构建。它能够处理大量的数据,提供快速的搜索响应。本教程将指导你如何从零开始搭建一个基本的 Elasticsearch 集群,并演示如何进行简单的索引和查询操作。
217 3
|
3月前
|
JSON 测试技术 API
黑马商城 Elasticsearch从入门到部署 RestClient操作文档
这篇文章详细介绍了如何使用Java的RestHighLevelClient客户端与Elasticsearch进行文档操作,包括新增、查询、删除、修改文档以及批量导入文档的方法,并提供了相应的代码示例和操作步骤。
下一篇
无影云桌面