ElasticSearch初步学习

docker环境下安装elasticsearch和kibana:

# 下载elasticsearch和kibana
docker pull elasticsearch:7.9.3
docker pull kibana:7.9.3

# 配置
mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data
echo "http.host: 0.0.0.0" >/mydata/elasticsearch/config/elasticsearch.yml
chmod -R 777 /mydata/elasticsearch/

# 启动elasticsearch
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e  "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v  /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.9.3

# 启动kibana
docker run --name kibana --link elasticsearch:elasticsearch -p 5601:5601 -e kibana:7.9.3

# 设置开机自启
docker update elasticsearch --restart=always
docker update kibana  --restart=always

docker环境下安装nginx:

# 随便启动一个nginx实例,只是为了复制出配置
docker run -p 80:80 --name nginx -d nginx:1.19.6

# 将容器内的配置文件拷贝到/mydata下(/mydata目录下会自动创建一个nginx文件夹)
docker container cp nginx:/etc/nginx /mydata
mv /mydata/nginx/ /mydata/conf
mkdir /mydata/nginx
mv /mydata/conf/ /mydata/nginx

# 停止nginx并删除容器
docker stop nginx
docker rm nginx

# 创建新的nginx容器
docker run -p 80:80 --name nginx \
 -v /mydata/nginx/html:/usr/share/nginx/html \
 -v /mydata/nginx/logs:/var/log/nginx \
 -v /mydata/nginx/conf/:/etc/nginx \
 -d nginx:1.19.6

# 设置开机自启
docker update nginx --restart=always

# 创建"/mydata/nginx/html/index.html"文件,测试是否能够正常访问
echo '<h2>hello nginx!</h2>' >index.html

1、初步检索(_cat)

/_cat/nodes:查看所有节点

:GET http://127.0.0.1:9200/_cat/nodes
127.0.0.1 61 91 11 0.08 0.49 0.87 dilm * 0adeb7852e00
注:*表示集群中的主节点

/_cat/health:查看es健康状况

:GET http://127.0.0.1:9200/_cat/health
1588332616 11:30:16 elasticsearch green 1 1 3 3 0 0 0 0 - 100.0%
注:green表示健康值正常

/_cat/master:查看主节点

:GET http://127.0.0.1:9200/_cat/master
vfpgxbusTC6-W3C2Np31EQ 127.0.0.1 127.0.0.1 0adeb7852e00

/_cat/indices:查看所有索引 ,等价于mysql数据库的show databases;

:GET http://127.0.0.1:9200/_cat/indices
green open .kibana_task_manager_1   KWLtjcKRRuaV9so_v15WYg 1 0 2 0 39.8kb 39.8kb
green open .apm-agent-configuration cuwCpJ5ER0OYsSgAJ7bVYA 1 0 0 0   283b   283b
green open .kibana_1                PqK_LdUYRpWMy4fK0tMSPw 1 0 7 0 31.2kb 31.2kb

2、索引一个文档

保存一个数据,保存在哪个索引的哪个类型下,指定用哪个唯一标识
如:PUT http://127.0.0.1:9200/customer/external/1
在customer索引下的external类型下保存1号数据为
{
 "name":"John Doe"
}
  • PUT和POST都可以进行索引文档
  • POST新增。如果不指定id,会自动生成id。指定id就会修改这个数据,并新增版本号;
  • PUT可以新增也可以修改。PUT必须指定id;由于PUT需要指定id,我们一般用来做修改操作,不指定id会报错。
创建成功返回实例:
{
	"_index": "customer",
	"_type": "external",
	"_id": "1",
	"_version": 1,
	"result": "created",
	"_shards": {
		"total": 2,
		"successful": 1,
		"failed": 0
	},
	"_seq_no": 0,
	"_primary_term": 1
}
带有下划线开头的,称为元数据,反映了当前的基本信息。

"_index": "customer" 表明该数据在哪个数据库下;
"_type": "external" 表明该数据在哪个类型下;
"_id": "1" 表明被保存数据的id;
"_version": 1, 被保存数据的版本
"result": "created" 这里是创建了一条数据,如果重新put一条数据,则该状态会变为updated,并且版本号也会发生变化。

3、查看文档

:GET http://127.0.0.1:9200/customer/external/1
返回示例:
{
	"_index": "customer",//在哪个索引
	"_type": "external",//在哪个类型
	"_id": "1",//记录id
	"_version": 3,//版本号
	"_seq_no": 6,//并发控制字段,每次更新都会+1,用来做乐观锁
	"_primary_term": 1,//同上,主分片重新分配,如重启,就会变化
	"found": true,
	"_source": {
		"name": "John Doe"
	}
}

4、更新文档

POST更新文档,带有_update会对比原来的数据,和原来的相同,则不执行任何操作(version和_seq_no)都不变。

注:在请求地址后拼接"if_seq_no=1&if_primary_term=1"可进行乐观锁操作

:POST http://127.0.0.1:9200/customer/external/1/_update
{
 "doc":{
  "name":"John"
 }
}
返回示例:
{
	"_index": "customer",
	"_type": "external",
	"_id": "1",
	"_version": 2,
	"result": "updated",
	"_shards": {
		"total": 2,
		"successful": 1,
		"failed": 0
	},
	"_seq_no": 9,
	"_primary_term": 1
}

如果请求数据和原有数据一样,则不执行任何操作,序列号也不发生变化:
{
	"_index": "customer",
	"_type": "external",
	"_id": "1",
	"_version": 2,
	"result": "noop",
	"_shards": {
		"total": 2,
		"successful": 1,
		"failed": 0
	},
	"_seq_no": 9,
	"_primary_term": 1
}

POST更新文档,不带_update会在更新过程中,重复执行更新操作,数据也能够更新成功,不会和原来的数据进行对比。

5、删除文档或索引

注:elasticsearch并没有提供删除类型的操作,只提供了删除索引和文档的操作。

:DELETE http://127.0.0.1:9200/customer/external/1 (删除文档)
返回示例:
{
	"_index": "customer",
	"_type": "external",
	"_id": "1",
	"_version": 2,
	"result": "deleted",
	"_shards": {
		"total": 2,
		"successful": 1,
		"failed": 0
	},
	"_seq_no": 9,
	"_primary_term": 1
}

再次查询返回示例:
{
	"_index": "customer",
	"_type": "external",
	"_id": "1",
	"found": false
}:DELETE http://127.0.0.1:9200/customer (删除索引)
返回示例:
{
	"acknowledged": true
}

6、eleasticsearch的批量操作_bulk

语法格式:
{action:{metadata}}\n
{request body}\n
{action:{metadata}}\n
{request body}\n

这里的批量操作,当发生某一条执行发生失败时,其他的数据仍然能够接着执行,也就是说彼此之间是独立的。

bulk api以此按顺序执行所有的action(动作)。如果一个单个的动作因任何原因失败,它将继续处理它后面剩余的动作。当bulk api返回时,它将提供每个动作的状态(与发送的顺序相同),所以您可以检查是否一个指定的动作是否失败了。

示例1_执行多条数据::POST http://127.0.0.1:9200/customer/external/_bulk
{"index":{"_id":"1"}}
{"name":"John Doe"}
{"index":{"_id":"2"}}
{"name":"John Doe"}

示例1_返回示例:
#! Deprecation: [types removal] Specifying types in bulk requests is deprecated.
{
  "took" : 491,
  "errors" : false,
  "items" : [
	{
	  "index" : {
		"_index" : "customer",
		"_type" : "external",
		"_id" : "1",
		"_version" : 1,
		"result" : "created",
		"_shards" : {
		  "total" : 2,
		  "successful" : 1,
		  "failed" : 0
		},
		"_seq_no" : 0,
		"_primary_term" : 1,
		"status" : 201
	  }
	},
	{
	  "index" : {
		"_index" : "customer",
		"_type" : "external",
		"_id" : "2",
		"_version" : 1,
		"result" : "created",
		"_shards" : {
		  "total" : 2,
		  "successful" : 1,
		  "failed" : 0
		},
		"_seq_no" : 1,
		"_primary_term" : 1,
		"status" : 201
	  }
	}
  ]
}

示例2_对于整个索引执行批量操作::POST http://127.0.0.1:9200/_bulk
{"delete":{"_index":"website","_type":"blog","_id":"123"}}
{"create":{"_index":"website","_type":"blog","_id":"123"}}
{"title":"my first blog post"}
{"index":{"_index":"website","_type":"blog"}}
{"title":"my second blog post"}
{"update":{"_index":"website","_type":"blog","_id":"123"}}
{"doc":{"title":"my updated blog post"}}

示例2_返回示例:
#! Deprecation: [types removal] Specifying types in bulk requests is deprecated.
{
  "took" : 608,
  "errors" : false,
  "items" : [
	{
	  "delete" : {
		"_index" : "website",
		"_type" : "blog",
		"_id" : "123",
		"_version" : 1,
		"result" : "not_found",
		"_shards" : {
		  "total" : 2,
		  "successful" : 1,
		  "failed" : 0
		},
		"_seq_no" : 0,
		"_primary_term" : 1,
		"status" : 404
	  }
	},
	{
	  "create" : {
		"_index" : "website",
		"_type" : "blog",
		"_id" : "123",
		"_version" : 2,
		"result" : "created",
		"_shards" : {
		  "total" : 2,
		  "successful" : 1,
		  "failed" : 0
		},
		"_seq_no" : 1,
		"_primary_term" : 1,
		"status" : 201
	  }
	},
	{
	  "index" : {
		"_index" : "website",
		"_type" : "blog",
		"_id" : "MCOs0HEBHYK_MJXUyYIz",
		"_version" : 1,
		"result" : "created",
		"_shards" : {
		  "total" : 2,
		  "successful" : 1,
		  "failed" : 0
		},
		"_seq_no" : 2,
		"_primary_term" : 1,
		"status" : 201
	  }
	},
	{
	  "update" : {
		"_index" : "website",
		"_type" : "blog",
		"_id" : "123",
		"_version" : 3,
		"result" : "updated",
		"_shards" : {
		  "total" : 2,
		  "successful" : 1,
		  "failed" : 0
		},
		"_seq_no" : 3,
		"_primary_term" : 1,
		"status" : 200
	  }
	}
  ]
}

7、样本测试数据

https://github.com/elastic/elasticsearch/blob/master/docs/src/test/resources/accounts.json

8、进阶检索

Search Api

ES支持两种基本方式检索;
通过REST request uri 发送搜索参数(uri+检索参数)
如:GET bank/_search?q=*&sort=account_number:asc

通过REST request body 来发送它们(uri+请求体)
如:GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": [
	{ "account_number": "asc" },
	{ "balance":"desc"}
  ]
}

返回参数:
took - 搜索耗时(毫秒)
timed_out - 搜索请求是否超时
_shards - 搜索了多少个碎片,以及有多少碎片成功、失败或跳过的细分
max_score - 找到的最相关文件的分数
hits.total.value - 找到了多少匹配的文件
hits.sort - 文档的排序位置(当不按相关性评分排序时)
hits._score - 文档的相关性得分(在使用match_all时不适用)

详细的字段信息,参照:https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started-search.html

Query DSL

基本语法格式:
QUERY_NAME:{
   ARGUMENT:VALUE,
   ARGUMENT:VALUE,...
}

如果针对于某个字段,那么它的结构如下:
{
  QUERY_NAME:{
	 FIELD_NAME:{
	   ARGUMENT:VALUE,
	   ARGUMENT:VALUE,...
	  }   
   }
}

如:GET bank/_search
{
  "query": {
	"match_all": {}
  },
  "from": 0,
  "size": 5,
  "sort": [
	{
	  "account_number": {
		"order": "desc"
	  }
	}
  ]
}

query定义如何查询:

match_all查询类型【代表查询所有的所有】,es中可以在query中组合非常多的查询类型完成复杂查询;
除了query参数之外,我们可也传递其他的参数以改变查询结果,如sort,size;
from+size限定,完成分页功能;
sort排序,多字段排序,会在前序字段相等时后续字段内部排序,否则以前序为准;

match匹配查询

如:GET bank/_search
{
  "query": {
	"match": {
	  "address": "kings"
	}
  }
}
全文检索,最终会按照评分进行排序,会对检索条件进行分词匹配

match_phrase [短句匹配],将需要匹配的值当成一整个单词(不分词)进行检索

如:GET bank/_search
{
  "query": {
	"match_phrase": {
	  "address": "mill road"
	}
  }
}
查处address中包含mill road的所有记录,并给出相关性得分

文本字段的匹配,使用keyword,匹配的条件就是要显示字段的全部值,要进行精确匹配的。
match_phrase是做短语匹配,只要文本中包含匹配条件,就能匹配到。

multi_math【多字段匹配】

如:GET bank/_search
{
  "query": {
	"multi_match": {
	  "query": "mill",
	  "fields": [
		"state",
		"address"
	  ]
	}
  }
}
state或者address中包含mill,并且在查询过程中,会对于查询条件进行分词。

bool用来做复合查询

复合语句可以合并,任何其他查询语句,包括符合语句。这也就意味着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑。

如:GET bank/_search
{
   "query":{
		"bool":{
			 "must":[
			  {"match":{"address":"mill"}},
			  {"match":{"gender":"M"}}
			 ]
		 }
	}
}
  • must:必须达到must所列举的所有条件
  • must_not,必须不匹配must_not所列举的所有条件
  • should,应该达到should列举的条件,如果到达会增加相关文档的评分,并不会改变查询的结果。如果query中只有should且只有一种匹配规则,那么should的条件就会被作为默认匹配条件而且改变查询结果。
如:GET bank/_search
{
  "query": {
	"bool": {
	  "must": [
		{
		  "match": {
			"gender": "M"
		  }
		},
		{
		  "match": {
			"address": "mill"
		  }
		}
	  ],
	  "must_not": [
		{
		  "match": {
			"age": "18"
		  }
		}
	  ],
	  "should": [
		{
		  "match": {
			"lastname": "Wallace"
		  }
		}
	  ]
	}
  }
}

Filter【结果过滤】,filter在使用过程中,并不会计算相关性得分:

如:GET bank/_search
{
  "query": {
	"bool": {
	  "must": [
		{
		  "match": {
			"address": "mill"
		  }
		}
	  ],
	  "filter": {
		"range": {
		  "balance": {
			"gte": "10000",
			"lte": "20000"
		  }
		}
	  }
	}
  }
}

term,代表完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇

如:GET bank/_search
{
  "query": {
	"term": {
	  "address": "mill Road"
	}
  }
}

Aggregation(执行聚合)

aggs:执行聚合。聚合语法如下:
"aggs":{
	"aggs_name这次聚合的名字,方便展示在结果集中":{
		"AGG_TYPE聚合的类型(avg,term,terms)":{}
	 }
}
如:查出所有年龄分布,并且这些年龄段中M的平均薪资和F的平均薪资以及这个年龄段的总体平均薪资
GET bank/_search
{
  "query": {
	"match_all": {}
  },
  "aggs": {
	"ageAgg": {
	  "terms": {
		"field": "age",
		"size": 100
	  },
	  "aggs": {
		"genderAgg": {
		  "terms": {
			"field": "gender.keyword"
		  },
		  "aggs": {
			"balanceAvg": {
			  "avg": {
				"field": "balance"
			  }
			}
		  }
		},
		"ageBalanceAvg": {
		  "avg": {
			"field": "balance"
		  }
		}
	  }
	}
  },
  "size": 0
}

Mapping(映射)

Maping是用来定义一个文档(document),以及它所包含的属性(field)是如何存储和索引的。比如:使用mapping来定义:

  • 哪些字符串属性应该被看做全文本属性(full text fields);
  • 哪些属性包含数字,日期或地理位置;
  • 文档中的所有属性是否都能被索引(all 配置);
  • 日期的格式;
  • 自定义映射规则来执行动态添加属性;
查看mapping信息:
GET bank/_mapping
新版本改变

ElasticSearch7-去掉type概念

  1. 关系型数据库中两个数据表示是独立的,即使他们里面有相同名称的列也不影响使用,但ES中不是这样的。elasticsearch是基于Lucene开发的搜索引擎,而ES中不同type下名称相同的filed最终在Lucene中的处理方式是一样的。
     1)、两个不同type下的两个user_name,在ES同一个索引下其实被认为是同一个filed,你必须在两个不同的type中定义相同的filed映射。否则,不同type中的相同字段名称就会在处理中出现冲突的情况,导致Lucene处理效率下降。
     2)、去掉type就是为了提高ES处理数据的效率。
  2. Elasticsearch 7.x URL中的type参数为可选。比如,索引一个文档不再要求提供文档类型。
  3. Elasticsearch 8.x 不再支持URL中的type参数。
  4. 解决:
    将索引从多类型迁移到单类型,每种类型文档一个独立索引
    将已存在的索引下的类型数据,全部迁移到指定位置即可。
创建映射
创建索引并指定映射
如:PUT /my_index
{
  "mappings": {
	"properties": {
	  "age": {
		"type": "integer"
	  },
	  "email": {
		"type": "keyword"
	  },
	  "name": {
		"type": "text"
	  }
	}
  }
}
查看映射
如:GET /my_index
添加新的字段映射
如:PUT /my_index/_mapping
{
  "properties": {
	"employee-id": {
	  "type": "keyword",
	  "index": false
	}
  }
}

这里的 "index": false,表明新增的字段不能被检索,只是一个冗余字段。
更新映射

对于已经存在的字段映射,我们不能更新。更新必须创建新的索引,进行数据迁移。

数据迁移

先创建new_twitter的正确映射。然后使用如下方式进行数据迁移。

如:POST _reindex [固定写法]
{
  "source":{
	  "index":"twitter"
   },
  "dest":{
	  "index":"new_twitter"
   }
}

将旧索引的type下的数据进行迁移

如:POST _reindex [固定写法]
{
  "source":{
	  "index":"twitter",
	  "type":"twitter"
   },
  "dest":{
	  "index":"new_twitters"
   }
}

9、分词

一个tokenizer(分词器)接收一个字符流,将之分割为独立的tokens(词元,通常是独立的单词),然后输出tokens流。
例如:whitespace tokenizer遇到空白字符时分割文本。它会将文本"Quick brown fox!"分割为[Quick,brown,fox!]。
该tokenizer(分词器)还负责记录各个terms(词条)的顺序或position位置(用于phrase短语和word proximity词近邻查询),以及term(词条)所代表的原始word(单词)的start(起始)和end(结束)的character offsets(字符串偏移量)(用于高亮显示搜索的内容)。
elasticsearch提供了很多内置的分词器,可以用来构建custom analyzers(自定义分词器)。

所有的语言分词,默认使用的都是"Standard Analyzer",但是这些分词器针对于中文的分词,并不友好。为此需要安装中文的分词器。

安装ik分词器

https://github.com/medcl/elasticsearch-analysis-ik/releases 对应es版本安装

  1. 进入/mydata/elasticsearch/plugins目录
  2. 下载ik分词器:wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.9.3/elasticsearch-analysis-ik-7.9.3.zip
  3. unzip elasticsearch-analysis-ik-7.9.3.zip -d /mydata/elasticsearch/plugins/ik
  4. rm -rf *.zip
  5. docker restart elasticsearch
测试分词器
1、使用默认分词器
GET my_index/_analyze
{
   "text":"我是中国人"
}

2、使用ik_smart
GET my_index/_analyze
{
   "analyzer": "ik_smart", 
   "text":"我是中国人"
}

3、使用ik_max_word
GET my_index/_analyze
{
   "analyzer": "ik_max_word", 
   "text":"我是中国人"
}

观察结果能够看出不同的分词器,分词有明显的区别,所以定义索引不能再使用默认的mapping,要手动建立mapping,因为需要选择分词器

自定义词库

修改/usr/share/elasticsearch/plugins/ik/config中的IKAnalyzer.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
	<entry key="ext_dict"></entry>
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords"></entry>
	<!--用户可以在这里配置远程扩展字典 -->
	<entry key="remote_ext_dict">http://127.0.0.1/fenci.txt</entry> 
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

修改完成后,需要重启elasticsearch容器,否则修改不生效。
更新完成后,es只会对于新增的数据用更新分词。历史数据是不会重新分词的。如果想要历史数据重新分词,需要执行:

POST my_index/_update_by_query?conflicts=proceed

10、elasticsearch-Rest-Client

9300: TCP

spring-data-elasticsearch:transport-api.jar
  • springboot版本不同,ransport-api.jar不同,不能适配es版本
  • 7.x已经不建议使用,8以后就要废弃
9200: HTTP
  • jestClient: 非官方,更新慢;
  • RestTemplate:模拟HTTP请求,ES很多操作需要自己封装,麻烦;
  • HttpClient:同上;
Elasticsearch-Rest-Client
  • 官方RestClient,封装了ES操作,API层次分明,上手简单;

最终选择Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client);
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

11、SpringBoot整合ElasticSearch

导入依赖

<-- 这里的版本要和所按照的ELK版本匹配 -->
<dependency>
	<groupId>org.elasticsearch.client</groupId>
	<artifactId>elasticsearch-rest-high-level-client</artifactId>
	<version>7.9.3</version>
</dependency>

修改spring-boot-dependencies中所依赖的ELK版本

<properties>
	...
	<elasticsearch.version>7.9.3</elasticsearch.version>
</properties>

测试数据

1、初始化

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-getting-started-initialization.html

2、配置RequestOptions

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-low-usage-requests.html#java-rest-low-usage-request-options

3、测试保存数据

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-document-index.html

@Test
public void indexData() throws IOException {
 IndexRequest indexRequest = new IndexRequest("users");

 User user = new User();
 user.setUserName("张三");
 user.setAge(20);
 user.setGender("男");
 String jsonString = JSON.toJSONString(user);
 //设置要保存的内容
 indexRequest.source(jsonString, XContentType.JSON);
 //执行创建索引和保存数据
 IndexResponse index = client.index(indexRequest, RequestOptions.COMMON_OPTIONS);

 System.out.println(index);

}
4、测试复杂检索

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-search.html

/**
 * 复杂检索:在bank中搜索address中包含mill的所有人的年龄分布以及平均年龄,平均薪资
 * @throws IOException
 */
@Test
public void searchData() throws IOException {
    //1. 创建检索请求
    SearchRequest searchRequest = new SearchRequest();
    //1.1)指定索引
    searchRequest.indices("bank");
    //1.2)构造检索条件
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(QueryBuilders.matchQuery("address","Mill"));
    //1.2.1)按照年龄分布进行聚合
    TermsAggregationBuilder ageAgg=AggregationBuilders.terms("ageAgg").field("age").size(10);
    sourceBuilder.aggregation(ageAgg);
    //1.2.2)计算平均年龄
    AvgAggregationBuilder ageAvg = AggregationBuilders.avg("ageAvg").field("age");
    sourceBuilder.aggregation(ageAvg);
    //1.2.3)计算平均薪资
    AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
    sourceBuilder.aggregation(balanceAvg);
    System.out.println("检索条件:"+sourceBuilder);
    searchRequest.source(sourceBuilder);
    //2. 执行检索
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    System.out.println("检索结果:"+searchResponse);
    //3. 将检索结果封装为Bean
    SearchHits hits = searchResponse.getHits();
    SearchHit[] searchHits = hits.getHits();
    for (SearchHit searchHit : searchHits) {
        String sourceAsString = searchHit.getSourceAsString();
        Account account = JSON.parseObject(sourceAsString, Account.class);
        System.out.println(account);
    }
    //4. 获取聚合信息
    Aggregations aggregations = searchResponse.getAggregations();
    Terms ageAgg1 = aggregations.get("ageAgg");
    for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
        String keyAsString = bucket.getKeyAsString();
        System.out.println("年龄:"+keyAsString+" ==> "+bucket.getDocCount());
    }
    Avg ageAvg1 = aggregations.get("ageAvg");
    System.out.println("平均年龄:"+ageAvg1.getValue());
    Avg balanceAvg1 = aggregations.get("balanceAvg");
    System.out.println("平均薪资:"+balanceAvg1.getValue());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值