一、引言
1.1 海量数据
在海量数据中执行搜索功能时,如果使用MySQL,效率太低。
1.2 全文检索
在海量数据中执行搜索功能时,如果使用MySQL,效率太低。
1.3 高亮显示
将搜索关键字,以红色的字体展示。
二、ES概述
2.1 ES的介绍
ES是一个使用Java语言并且基于Lucene编写的搜索引擎框架,他提供了分布式的全文搜索功能,提供了一个统一的基于RESTful风格的WEB接口,官方客户端也对多种语言都提供了相应的API。
Lucene:Lucene本身就是一个搜索引擎的底层。
分布式:ES主要是为了突出他的横向扩展能力。
全文检索:将一段词语进行分词,并且将分出的单个词语统一的放到一个分词库中,在搜索时,根据关键字去分词库中检索,找到匹配的内容。(倒排索引)
RESTful风格的WEB接口:操作ES很简单,只需要发送一个HTTP请求,并且根据请求方式的不同,携带参数的同,执行相应的功能。
应用广泛:Github.com,WIKI,Gold Man用ES每天维护将近10TB的数据。
2.2 ES的由来
ES回忆时光 |
---|
2.3 ES和Solr
Solr在查询死数据时,速度相对ES更快一些。但是数据如果是实时改变的,Solr的查询速度会降低很多,ES的查询的效率基本没有变化。
Solr搭建基于需要依赖Zookeeper来帮助管理。ES本身就支持集群的搭建,不需要第三方的介入。
最开始Solr的社区可以说是非常火爆,针对国内的文档并不是很多。在ES出现之后,ES的社区火爆程度直线上升,ES的文档非常健全。
ES对现在云计算和大数据支持的特别好。
2.4 倒排索引
将存放的数据,以一定的方式进行分词,并且将分词的内容存放到一个单独的分词库中。
当用户去查询数据时,会将用户的查询关键字进行分词。
然后去分词库中匹配内容,最终得到数据的id标识。
根据id标识去存放数据的位置拉取到指定的数据。
倒排索引 |
---|
三、 ElasticSearch安装
3.1 安装ES&Kibana
前提条件
1.如果防火墙开启则关闭,并且重启docker服务
2.将虚拟内存保持4g以上
3.设置sysctl参数,检查sysctl参数若没有则设置
echo "vm.max_map_count=655360">>/etc/sysctl.conf
sysctl -p #执行当前命令
yml文件
version: "3.1"
services:
elasticsearch:
image: daocloud.io/library/elasticsearch:6.5.4
restart: always
container_name: elasticsearch
ports:
- 9200:9200
- 9300:9300
kibana:
image: daocloud.io/library/kibana:6.5.4
restart: always
container_name: kibana
ports:
- 5601:5601
environment:
- elasticsearch_url=http://192.168.199.109:9200
depends_on:
- elasticsearch
解决kiban 无法访问问题
1.进入kibana,修改config/kibana.yml
docker exec -it kibana /bin/bash
kibana.example.org.crt kibana.example.org.key kibana.yml
bash-4.2$ vi config/kibana.yml
2.退出重启kibana
docker restart kibana
说明
9200用于外部通讯,基于http协议,程序与es的通信使用9200端口。
9300jar之间就是通过tcp协议通信,遵循tcp协议,es集群中的节点之间也通过9300端口进行通信。
3.2 安装IK分词器
由于网络问题,采用国内的路径去下载:http://tomcat01.qfjava.cn:81/elasticsearch-analysis-ik-6.5.4.zip
进去到ES容器内部,跳转到bin目录下,执行bin目录下的脚本文件:
./elasticsearch-plugin install http://tomcat01.qfjava.cn:81/elasticsearch-analysis-ik-6.5.4.zip
重启ES的容器,让IK分词器生效。
PUT book_v6
POST /book_v6/_analyze
{
"analyzer": "ik_max_word",
"text": "我是中国人"
}
四、 ElasticSearch基本操作
4.1 ES的结构
4.1.1 索引Index,分片和备份
ES的服务中,可以创建多个索引。
每一个索引默认被分成5片存储。
每一个分片都会存在至少一个备份分片。
备份分片默认不会帮助检索数据,当ES检索压力特别大的时候,备份分片才会帮助检索数据。
备份的分片必须放在不同的服务器中。
索引分片备份 |
---|
4.1.2 类型 Type
一个索引下,可以创建多个类型。
类型 |
---|
4.1.3 文档 Doc
一个类型下,可以有多个文档。这个文档就类似于MySQL表中的多行数据。
文档 |
---|
4.1.4 属性 Field
一个文档中,可以包含多个属性。类似于MySQL表中的一行数据存在多个列。
属性 |
---|
4.2 操作ES的RESTful语法
GET请求:
http://ip:port/index:查询索引信息
http://ip:port/index/type/doc_id:查询指定的文档信息
POST请求:
http://ip:port/index/type/_search:查询文档,可以在请求体中添加json字符串来代表查询条件
http://ip:port/index/type/doc_id/_update:修改文档,在请求体中指定json字符串代表修改的具体信息
PUT请求:
http://ip:port/index:创建一个索引,需要在请求体中指定索引的信息,类型,结构
http://ip:port/index/type/_mappings:代表创建索引时,指定索引文档存储的属性的信息
DELETE请求:
http://ip:port/index:删除索引
http://ip:port/index/type/doc_id:删除指定的文档
4.3 索引的操作
4.3.1 创建一个索引
语法如下
# 创建一个索引
PUT /person
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
}
}
4.3.2 查看索引信息
语法如下
# 查看索引信息
GET /person
查看信息 |
---|
4.3.3 删除索引
语法如下
# 删除索引
DELETE /person
4.4 ES中Field可以指定的类型
字符串类型:
text:一把被用于全文检索。 将当前Field进行分词。
keyword:当前Field不会被分词。
数值类型:
long:取值范围为-9223372036854774808~922337203685477480(-2的63次方到2的63次方-1),占用8个字节
integer:取值范围为-2147483648~2147483647(-2的31次方到2的31次方-1),占用4个字节
short:取值范围为-32768~32767(-2的15次方到2的15次方-1),占用2个字节
byte:取值范围为-128~127(-2的7次方到2的7次方-1),占用1个字节
double:1.797693e+308~ 4.9000000e-324 (e+308表示是乘以10的308次方,e-324表示乘以10的负324次方)占用8个字节
float:3.402823e+38 ~ 1.401298e-45(e+38表示是乘以10的38次方,e-45表示乘以10的负45次方),占用4个字节
half_float:精度比float小一半。
scaled_float:根据一个long和scaled来表达一个浮点型,long-345,scaled-100 -> 3.45
时间类型:
date类型,针对时间类型指定具体的格式
布尔类型:
boolean类型,表达true和false
二进制类型:
binary类型暂时支持Base64 encode string
范围类型:
long_range:赋值时,无需指定具体的内容,只需要存储一个范围即可,指定gt,lt,gte,lte
integer_range:同上
double_range:同上
float_range:同上
date_range:同上
ip_range:同上
经纬度类型:
geo_point:用来存储经纬度的
ip类型:
ip:可以存储IPV4或者IPV6
其他的数据类型参考官网:Field datatypes | Elasticsearch Guide [6.5] | Elastic
4.5 创建索引并指定数据结构
语法如下
# 创建索引,指定数据结构
PUT /book
{
"settings": {
# 分片数
"number_of_shards": 5,
# 备份数
"number_of_replicas": 1
},
# 指定数据结构
"mappings": {
# 类型 Type
"novel": {
# 文档存储的Field
"properties": {
# Field属性名
"name": {
# 类型
"type": "text",
# 指定分词器
"analyzer": "ik_max_word",
# 指定当前Field可以被作为查询的条件
"index": true ,
# 是否需要额外存储
"store": false
},
"author": {
"type": "keyword"
},
"count": {
"type": "long"
},
"on_sale": {
"type": "date",
# 时间类型的格式化方式
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"descr": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
}
4.6 文档的操作
文档在ES服务中的唯一标识,
_index
,_type
,_id
三个内容为组合,锁定一个文档,操作是添加还是修改。
4.6.1 新建文档
自动生成_id
# 添加文档,自动生成id
POST /book/novel
{
"name": "盘龙",
"author": "我吃西红柿",
"count": 100000,
"on_sale": "2000-01-01",
"descr": "山重水复疑无路,柳暗花明又一村"
}
手动指定_id
# 添加文档,手动指定id
PUT /book/novel/1
{
"name": "红楼梦",
"author": "曹雪芹",
"count": 10000000,
"on_sale": "1985-01-01",
"descr": "一个是阆苑仙葩,一个是美玉无瑕"
}
4.6.2 修改文档
覆盖式修改
# 添加文档,手动指定id
PUT /book/novel/1
{
"name": "红楼梦",
"author": "曹雪芹",
"count": 4353453,
"on_sale": "1985-01-01",
"descr": "一个是阆苑仙葩,一个是美玉无瑕"
}
doc修改方式
修改时只修改部分内容,不会修改版本
# 修改文档,基于doc方式
POST /book/novel/1/_update
{
"doc": {
# 指定上需要修改的field和对应的值
"count": "1234565"
}
}
4.6.3 删除文档
根据id删除
# 根据id删除文档
DELETE /book/novel/_id
查看索引结构
GET /book7
{
"book7" : {
"aliases" : { },
"mappings" : {
"novel" : {
"properties" : {
"author" : {
"type" : "keyword"
},
"count" : {
"type" : "integer"
},
"descr" : {
"type" : "text",
"store" : true,
"analyzer" : "ik_max_word",
"search_analyzer" : "ik_smart"
},
"id" : {
"type" : "text",
"fields" : { #不但可以文本,还可以是关键字
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"name" : {
"type" : "text",
"fielddata" : true
},
"on_sale" : {
"type" : "date",
"format" : "year_month_day"
}
}
}
},
"settings" : {
"index" : {
"refresh_interval" : "1s",
"number_of_shards" : "5",
"provided_name" : "book7",
"creation_date" : "1615809289668",
"store" : {
"type" : "fs"
},
"number_of_replicas" : "1",
"uuid" : "UtBQle9ATNu6J_HqPBkepQ",
"version" : {
"created" : "6050499"
}
}
}
}
}
elasticsearch入门使用(二) Mapping + field type字段类型 - nickchou - 博客园
五、Java操作ElasticSearch【重点
】
5.1 Springboot连接ES
创建Maven工程
导入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
创建配置文件application.properties
# ELASTICSEARCH (ElasticsearchProperties)
# Elasticsearch cluster name. 必须写正确
spring.data.elasticsearch.cluster-name=docker-cluster
# Comma-separated list of cluster node addresses. 必须是 9300
spring.data.elasticsearch.cluster-nodes=192.168.12.130:9300
# Whether to enable Elasticsearch repositories.
spring.data.elasticsearch.repositories.enabled=true
server.port=8888
创建启动类
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class,args);
}
}
创建实体类
/**
* "name": "红楼梦",
* "author": "曹雪芹1",
* "count": 10000000,
* "on_sale": "1985-01-01",
* "descr": "一个是阆苑仙葩,一个是美玉无瑕"
*/
@Document(indexName = "book7",replicas = 1,shards = 5,type = "novel")
public class Book {
@Id
private String id;
// fielddata 用于sort
@Field(type = FieldType.Text,fielddata = true)
private String name;
@Field(type = FieldType.Keyword)
private String author;
@Field(type = FieldType.Integer)
private int count;
@Field(type = FieldType.Date,format = DateFormat.year_month_day)
private String on_sale;
@Field(analyzer = "ik_max_word", searchAnalyzer = "ik_smart", type = FieldType.Text, store = true)
private String descr;
。。。。
}
5.2 Java操作索引
5.2.1 创建索引
代码如下
@SpringBootTest
@RunWith(SpringRunner.class)
public class MyApplicationTest {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Test
public void createIndex(){
// 创建索引
elasticsearchTemplate.createIndex(Book.class);
// 设置mapping
elasticsearchTemplate.putMapping(Book.class);
}
}
5.2.2 增加/覆盖数据
代码如下
@Test
public void addBook(){
Book book = new Book();
book.setAuthor("张三");
book.setCount(1000555);
book.setDescr("一个是阆苑仙葩,一个是美玉无瑕");
book.setId("2");
book.setName("盘龙");
book.setOn_sale("2020-01-12");
IndexQuery indexQuery = new IndexQueryBuilder()
.withId(book.getId())
.withObject(book)
.build();
// String id = elasticsearchTemplate.index(indexQuery);
// System.out.println("id:"+id);
List<IndexQuery> queries = new ArrayList<>();
queries.add(indexQuery);
elasticsearchTemplate.bulkIndex(queries);
}
5.2.3 更新数据
代码如下
/**
* 更新数据
*/
@Test
public void updateBook(){
Map<String, Object> params = new HashMap<>();
// 其中某一个属性
params.put("count", 999);
UpdateRequest updateRequest = new UpdateRequest();
updateRequest.doc(params);
UpdateQueryBuilder updateQueryBuilder = new UpdateQueryBuilder();
updateQueryBuilder.withId("1");
updateQueryBuilder.withUpdateRequest(updateRequest);
updateQueryBuilder.withClass(Book .class);
UpdateQuery build = updateQueryBuilder.build();
UpdateResponse update1 = elasticsearchTemplate.update(build);
System.out.println(update1.getResult().getLowercase());
}
5.2.3 删除数据
/**
* 删除
*/
@Test
public void delete(){
// 根据id删除
// elasticsearchTemplate.delete(Book.class, "2");
// 删除索引
elasticsearchTemplate.deleteIndex(Book.class);
}
5.2.4 查选
/**
* 根据id 查选
*/
@Test
public void queryById(){
GetQuery query = new GetQuery();
query.setId("1");
Book book = elasticsearchTemplate.queryForObject(query, Book.class);
System.out.println("book:"+book);
}
/**
* 模糊查询
*/
@Test
public void query(){
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.queryStringQuery("descr:美玉"))
.withPageable(new PageRequest(0, 20))
.build();
List<Book> list = elasticsearchTemplate.queryForList(searchQuery, Book.class);
System.out.println("list:"+list);
}
六、 ElasticSearch的各种查询
1.下载数据
webget https://raw.githubusercontent.com/elastic/elasticsearch/master/docs/src/test/resources/accounts.json
2.加载 json数据 注意es连接地址
curl -H "Content-Type: application/json" -XPOST "192.168.13.100:9200/bank/_doc/_bulk?pretty&refresh" --data-binary "@accounts.json"
3.数据分析
{
"account_number": 1, #账号
"balance": 39225, #账户余额
"firstname": "Amber", #名子
"lastname": "Duke", #姓氏
"age": 32, #年龄
"gender": "M", #性别
"address": "880 Holmes Lane", #地址
"employer": "Pyrami", #雇佣者 老板 公司
"email": "amberduke@pyrami.com", # 邮箱
"city": "Brogan", #所在城市
"state": "IL" #国家简称
}
6.1 term&terms查询【重点
】
6.1.1 term查询
term的查询是代表完全匹配,搜索之前不会对你搜索的关键字进行分词,对你的关键字去文档分词库中去匹配内容。
它被用作精确查询,比如数字,时间,布尔,和字段属性为keyword类型的关键字,后面讲的聚合也只支持精确字段,而
分词字段text是不被支持
的。字符串只能匹配keyword
# term查询
POST /bank/_doc/_search
{
"from": 0,
"size": 5,
"query": {
"term": {
"state.keyword": {
"value": "DE"
}
}
}
}
6.1.2 terms查询
terms和term的查询机制是一样,都不会将指定的查询关键字进行分词,直接去分词库中匹配,找到相应文档内容。
terms是在针对一个字段包含多个值的时候使用。
term:where province = 北京;
terms:where province = 北京 or province = ?or province = ?
# terms查询
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"terms": {
"province": [
"北京",
"山西",
"武汉"
]
}
}
}
POST /bank/_doc/_search
{
"from": 0,
"size": 20
, "query": {
"terms": {
"state.keyword": [
"DE",
"PA"
]
}
}
}
6.2 match查询【重点
】
match查询属于高层查询,他会根据你查询的字段类型不一样,采用不同的查询方式。
查询的是日期或者是数值的话,他会将你基于的字符串查询内容转换为日期或者数值对待。
如果查询的内容是一个不能被分词的内容(keyword),match查询不会对你指定的查询关键字进行分词。
如果查询的内容时一个可以被分词的内容(text),match会将你指定的查询内容根据一定的方式去分词,去分词库中匹配指定的内容。
match查询,实际底层就是多个term查询,将多个term查询的结果给你封装到了一起。
6.2.1 match_all查询
查询全部内容,不指定任何查询条件。
# match_all查询
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"match_all": {}
}
}
POST /bank/_doc/_search
{
"from": 0,
"size": 2,
"query": {
"match_all": {}
}
}
6.2.2 match查询
指定一个Field作为筛选的条件
# match查询
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"match": {
"smsContent": "收货安装"
}
}
}
#只要含有关键词 就查询出来
POST /bank/_doc/_search
{
"from":0,
"size":20,
"query":{
"match":{
"address":"Avenue Baycliff"
}
}
}
6.2.3 布尔match查询
基于一个Field匹配的内容,采用and或者or的方式连接
# 布尔match查询
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"match": {
"smsContent": {
"query": "中国 健康",
"operator": "and" # 内容既包含中国也包含健康
}
}
}
}
# 布尔match查询
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"match": {
"smsContent": {
"query": "中国 健康",
"operator": "or" # 内容包括健康或者包括中国
}
}
}
}
#或者关系
POST /bank/_doc/_search
{
"from":0,
"size":20,
"query":{
"match":{
"address":{
"query":"Avenue Baycliff",
"operator":"or"
}
}
}
}
6.2.4 multi_match查询
match针对一个field做检索,multi_match针对多个field进行检索,多个field对应一个text。
# multi_match 查询
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"multi_match": {
"query": "北京", # 指定text
"fields": ["province","smsContent"] # 指定field们
}
}
}
# 两个字段含有又给就可以
POST /bank/_doc/_search
{
"from":0,
"size":20,
"query":{
"multi_match": {
"query": "Hondah",
"fields": ["city","address"]
}
}
}
6.3 其他查询
6.3.1 id查询
根据id查询 where id = ?
# id查询
GET /sms-logs-index/sms-logs-type/1
GET /bank/_doc/1
6.3.2 ids查询
根据多个id查询,类似MySQL中的where id in(id1,id2,id2...)
# ids查询
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"ids": {
"values": ["1","2","3"]
}
}
}
POST /bank/_doc/_search
{
"query":{
"ids":{
"values":["1","2","3"]
}
}
}
6.3.5 wildcard查询
通配查询,和MySQL中的like是一个套路,可以在查询时,在字符串中指定通配符*和占位符?
只能匹配部分字,不可以匹配一个词
# 卡方搜索
# 注意是搜索词是关键词 关键词 *爱*
# 我们搜索的 文本 如果是 中文必须配置分词器 "analyzer" : "ik_max_word",否则默认的分词器 会将 文档中的属性切分为每一个字 无法切分
POST /book/novel/_search
{
"from":0,
"size":5,
"query":{
"wildcard":{
"decr":{
"value":"*爱情*"
}
}
}
}
#也可 去查询keyword 此时会将 目标属性作为整体去查询
POST /book/novel/_search
{
"from":0,
"size":5,
"query":{
"wildcard":{
"author":{
"value":"*雪*"
}
}
}
}
GET /book
6.3.6 range查询
范围查询,只针对数值类型,对某一个Field进行大于或者小于的范围指定
# range 查询
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"range": {
"fee": {
"gt": 5,
"lte": 10
# 可以使用 gt:> gte:>= lt:< lte:<=
}
}
}
}
POST /bank/_doc/_search
{
"query": {
"range": {
"age": {
"gt": 24,
"lte": 100
}
}
}
}
6.6 复合查询
6.6.1 bool查询
复合过滤器,将你的多个查询条件,以一定的逻辑组合在一起。
must: 所有的条件,用must组合在一起,表示And的意思
must_not:将must_not中的条件,全部都不能匹配,标识Not的意思
should:所有的条件,用should组合在一起,表示Or的意思
# 查询省份为武汉或者北京
# 运营商不是联通
# smsContent中包含中国和平安
# bool查询
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"province": {
"value": "北京"
}
}
},
{
"term": {
"province": {
"value": "武汉"
}
}
}
],
"must_not": [
{
"term": {
"operatorId": {
"value": "2"
}
}
}
],
"must": [
{
"match": {
"smsContent": "中国"
}
},
{
"match": {
"smsContent": "平安"
}
}
]
}
}
}
POST /bank/_doc/_search
{
"query": {
"bool": {
"should": [ #shoud 与 must 并存时不起作用
{
"term": {
"city": {
"value": "Yardville"
}
}
},
{
"term": {
"city": {
"value": "Shaft"
}
}
}
],
"must_not": [
{
"term": {
"age": {
"value": "39"
}
}
}
],
"must": [
{
"match": {
"state": "DE"
}
},
{
"match": {
"lastname": "Bartlett"
}
}
]
}
}
}
注意:
must 与should 是否可以联合使用?
a.默认情况下must和should 不能混用,若使用should不生效
b.若要强行使用,则需要在should配置minimum_should_match =1
# must should 并存时 minimum_should_match必须配置为1
GET /bank/_search
{
"size": 30,
"query":{
"bool":{
"must":{
"match":{"gender":"M"}
},
"should":[
{"term":{"age": 30}}
],
"minimum_should_match": 1
}
}
}
6.7 filter查询
query,根据你的查询条件,去计算文档的匹配度得到一个分数,并且根据分数进行排序,不会做缓存的。
filter,根据你的查询条件去查询文档,不去计算分数,而且filter会对经常被过滤的数据进行缓存。
# filter查询
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"corpName": "盒马鲜生"
}
},
{
"range": {
"fee": {
"lte": 4
}
}
}
]
}
}
}
POST /bank/_doc/_search
{
"query": {
"bool": {
"filter": [
{
"match": {
"address": "731"
}
},
{
"range": {
"age": {
"lte": 40
}
}
}
]
}
}
}
6.8 高亮查询【重点
】
高亮查询就是你用户输入的关键字,以一定的特殊样式展示给用户,让用户知道为什么这个结果被检索出来。
高亮展示的数据,本身就是文档中的一个Field,单独将Field以highlight的形式返回给你。
ES提供了一个highlight属性,和query同级别的。
fragment_size:指定高亮数据展示多少个字符回来。
pre_tags:指定前缀标签,举个栗子< font color="red" >
post_tags:指定后缀标签,举个栗子< /font >
fields:指定哪几个Field以高亮形式返回
效果图 |
---|
RESTful实现
# highlight查询
POST /sms-logs-index/sms-logs-type/_search
{
"query": {
"match": {
"smsContent": "盒马"
}
},
"highlight": {
"fields": {
"smsContent": {}
},
"pre_tags": "<font color='red'>",
"post_tags": "</font>",
"fragment_size": 10
}
}
POST /bank/_doc/_search
{
"query": {
"match": {
"address": "Street"
}
},
"highlight": {
"fields": {
"address": {}
},
"pre_tags": "<font color='red'>",
"post_tags": "</font>",
"fragment_size": 10
}
}
6.9 聚合查询【重点
】
ES的聚合查询和MySQL的聚合查询类似,ES的聚合查询相比MySQL要强大的多,ES提供的统计数据的方式多种多样。
聚合操作,我们可以对数据进行分组的求和,求数,最大值,最小值,或者其它的自定义的统计功能,es对聚合有着不错的支持,需要注意的是,在对某字段进行聚合之后,需要开启这个字段的fielddata我,
匹配:开启fielddata 字段 或者 keyword
# ES聚合查询的RESTful语法
POST /index/type/_search
{
"aggs": {
"名字(agg)": {
"agg_type": {
"属性": "值"
}
}
}
}
6.9.1 去重计数查询
去重计数,即Cardinality,第一步先将返回的文档中的一个指定的field进行去重,统计一共有多少条
field 对应的列必须是关键字,如果不是需要province.keyword
# 去重计数查询 北京 上海 武汉 山西
POST /sms-logs-index/sms-logs-type/_search
{
"aggs": {
"agg": {
"cardinality": {
"field": "province"
}
}
}
}
POST /bank/_doc/_search
{
"aggs": {
"agg": {
"cardinality": {
"field": "state.keyword"
}
}
}
}
6.9.2 范围统计
统计一定范围内出现的文档个数,比如,针对某一个Field的值在 0~100,100~200,200~300之间文档出现的个数分别是多少。
范围统计可以针对普通的数值,针对时间类型,针对ip类型都可以做相应的统计。
range,date_range,ip_range
数值统计
# 数值方式范围统计
POST /sms-logs-index/sms-logs-type/_search
{
"aggs": {
"agg": {
"range": {
"field": "fee",
"ranges": [
{
"to": 5
},
{
"from": 5, # from有包含当前值的意思
"to": 10
},
{
"from": 10
}
]
}
}
}
}
POST /bank/_doc/_search
{
"aggs": {
"agg": {
"range": {
"field": "age",
"ranges": [
{
"to": 15
},
{
"from": 15,
"to": 30
},
{
"from": 50
}
]
}
}
}
}
时间范围统计
# 时间方式范围统计
POST /sms-logs-index/sms-logs-type/_search
{
"aggs": {
"agg": {
"date_range": {
"field": "createDate",
"format": "yyyy",
"ranges": [
{
"to": 2000
},
{
"from": 2000
}
]
}
}
}
}
6.9.3 统计聚合查询
他可以帮你查询指定Field的最大值,最小值,平均值,平方和等
使用:extended_stats
# 统计聚合查询
POST /sms-logs-index/sms-logs-type/_search
{
"aggs": {
"agg": {
"extended_stats": {
"field": "fee"
}
}
}
}
#平均年龄
POST /bank/_doc/_search
{
"aggs": {
"agg": {
"extended_stats": {
"field": "age"
}
}
}
}
其他的聚合查询方式查看官方文档:Elasticsearch Guide [6.5] | Elastic
总结:
fielddata默认为false,使用场景为 文本的 聚合,排序或在脚本中使用,但是会占用大量的堆内存,一般来说可以使用.keyword 来替代使用
更新fielddata为true_es fielddata理解_大宝碎碎念的博客-CSDN博客
Elasticsearch:fielddata 介绍 - 三度 - 博客园
6.10 java查询
查询公式
NativeSearchQueryBuilder.withQuery(QueryBuilder1).withFilter(QueryBuilder2).withSort(SortBuilder1).withXXXX().build();
/**
* 根据具属性名排序
*/
@Test // 报错 对应得列必须配置 @Field(type = FieldType.Text,fielddata = true)
public void sort(){
Pageable pageable= new PageRequest(0, 20);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.queryStringQuery("id:2"))
.withSort(SortBuilders.fieldSort("name"))
.withPageable(pageable)
.build();
Page<Book> list = elasticsearchTemplate.queryForPage(searchQuery, Book.class);
System.out.println("list:"+list);
}
/**
* 模糊查询
*
* 此模糊查询与mysql中的模糊查询不太一样,此模糊查询类似分词匹配。
* 比如有两条数据:1、我今天非常高兴 2、他摔倒很高兴
* 输入:今天高兴
* 这两条数据都能匹配上。
*
*/
@Test
public void search(){
Pageable pageable = new PageRequest(0, 10);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("descr", "无路美玉"))
.withPageable(pageable)
.build();
List<Book> list = elasticsearchTemplate.queryForList(searchQuery, Book.class);
System.out.println("list:"+list);
}
/**
*其余匹配类似mysql中like "%word%"的模糊匹配
*/
@Test
public void like(){
Pageable pageable = new PageRequest(0, 10);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchPhraseQuery("descr", "美玉"))
.withPageable(pageable)
.build();
List<Book> list = elasticsearchTemplate.queryForList(searchQuery, Book.class);
System.out.println("list:"+list);
}
/**
* 全匹配 查询不到 只有类型 是关键字时 才可以匹配 test 无法匹配
*/
@Test
public void term(){
Pageable pageable = new PageRequest(0, 10);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
// .withQuery(QueryBuilders.termQuery("author", "张三"))
.withQuery(QueryBuilders.termQuery("name", "盘龙")) // 非关键字无法查询
.withPageable(pageable)
.build();
List<Book> list = elasticsearchTemplate.queryForList(searchQuery, Book.class);
System.out.println("list:"+list);
}
/**
* 即boolQuery,可以设置多个条件的查询方式。它的作用是用来组合多个Query,有四种方式来组合,must,mustnot,filter,should。
* must代表返回的文档必须满足must子句的条件,会参与计算分值;
* filter代表返回的文档必须满足filter子句的条件,但不会参与计算分值;
* should代表返回的文档可能满足should子句的条件,也可能不满足,有多个should时满足任何一个就可以,通过minimum_should_match设置至少满足几个。
* mustnot代表必须不满足子句的条件。
*/
@Test
public void boolQuery(){
QueryBuilder filterQuery = QueryBuilders
.boolQuery()
// .filter(QueryBuilders.termQuery("name", "菜鸟"))
.filter(QueryBuilders.matchPhraseQuery("descr", "美玉"));
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(filterQuery).build();
List<Book> list = elasticsearchTemplate.queryForList(searchQuery, Book.class);
System.out.println("list:"+list);
}
/**
* 高亮显示
*/
@Test
public void HeightLight(){
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//在tag,description域中搜索匹配的内容
//HighlightFields表示高亮设置
//new HighlightBuilder.Field("tag").preTags("<span style=\"color:red\">").postTags("</span>")表示设置高亮域为tag,并加上前置与后置标签
NativeSearchQuery query =
queryBuilder.withQuery(QueryBuilders.multiMatchQuery("美玉","name","descr"))
.withHighlightFields(new HighlightBuilder.Field("descr").preTags("<span style=\"color:red\">").postTags("</span>")).build();
//通过elasticsearchTemplate并重写SearchResultMapper方法来实现高亮搜索
AggregatedPage<Book> bookAggregatedPage = elasticsearchTemplate.queryForPage(query, Book.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
List<Book> books = new ArrayList<>();
//得到匹配的所有选项
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
if (hits.getHits().length <= 0) {
return null;
}
Book book = new Book();
book.setId(hit.getId());
book.setName((String) hit.getSourceAsMap().get("name"));
//扩展性不强,使用反射更好
//防止标签不存在,描述存在的问题
if (hit.getHighlightFields().size()!=0) {
String highlightString = hit.getHighlightFields().get("descr").fragments()[0].toString();
book.setDescr(highlightString);
}
books.add(book);
}
return new AggregatedPageImpl<>((List<T>)books);
}
});
bookAggregatedPage.getContent();
}
/**
* 高亮显示
*/
@Test
public void HeightLight2(){
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//在tag,description域中搜索匹配的内容
//HighlightFields表示高亮设置
//new HighlightBuilder.Field("tag").preTags("<span style=\"color:red\">").postTags("</span>")表示设置高亮域为tag,并加上前置与后置标签
NativeSearchQuery query =
queryBuilder.withQuery(QueryBuilders.multiMatchQuery("美玉","name","descr"))
.withHighlightFields(new HighlightBuilder.Field("descr").preTags("<span style=\"color:red\">").postTags("</span>")).build();
//通过elasticsearchTemplate并重写SearchResultMapper方法来实现高亮搜索
AggregatedPage<Book> bookAggregatedPage = elasticsearchTemplate.queryForPage(query, Book.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
List<Book> books = new ArrayList<>();
//得到匹配的所有选项
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
if (hits.getHits().length <= 0) {
return null;
}
Book book = new Book();
book.setId(hit.getId());
book.setName((String) hit.getSourceAsMap().get("name"));
// book.setDescr((String) hit.getSourceAsMap().get("name"));
setHighLight(hit,"descr",book);
books.add(book);
}
return new AggregatedPageImpl<>((List<T>)books);
}
});
System.out.println("list:"+bookAggregatedPage.getContent());
}
public void setHighLight(SearchHit searchHit, String field, Object object) {
Map<String, HighlightField> highlightFieldMap = searchHit.getHighlightFields();
HighlightField highlightField = highlightFieldMap.get(field);
if (highlightField != null) {
String highLightMessage = highlightField.fragments()[0].toString();
String capitalize = StringUtils.capitalize(field);
String methodName = "set"+capitalize;
Class<?> clazz = object.getClass();
try {
Method setMethod = clazz.getMethod(methodName, String.class);
setMethod.invoke(object, highLightMessage);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 聚合查询
* 根据字段 author 聚合查选
*/
@Test
public void aggregation1(){
// 创建一个查询条件对象
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
// 拼接查询条件
queryBuilder.should(QueryBuilders.multiMatchQuery("美玉","name","descr"));
// 创建聚合查询条件
TermsAggregationBuilder agg = AggregationBuilders.terms("author_count").field("author");//keyword表示不使用分词进行聚合
// 创建查询对象
SearchQuery build = new NativeSearchQueryBuilder()
.withQuery(queryBuilder) //添加查询条件
.addAggregation(agg) // 添加聚合条件
.withPageable(PageRequest.of(0, 10)) //符合查询条件的文档分页(不是聚合的分页)
.build();
// 执行查询
AggregatedPage<Book> testEntities = elasticsearchTemplate.queryForPage(build, Book.class);
// 取出聚合结果
Aggregations entitiesAggregations = testEntities.getAggregations();
Terms terms = (Terms) entitiesAggregations.asMap().get("author_count");
// 遍历取出聚合字段列的值,与对应的数量
for (Terms.Bucket bucket : terms.getBuckets()) {
String keyAsString = bucket.getKeyAsString(); // 聚合字段列的值
long docCount = bucket.getDocCount();// 聚合字段对应的数量
System.out.println("keyAsString={},value={}"+ keyAsString+ docCount);
}
}
/**
* 求平局数量
*/
@Test
public void aggregation2(){
// 创建一个查询条件对象
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
// 拼接查询条件
queryBuilder.should(QueryBuilders.multiMatchQuery("美玉","name","descr"));
// 创建聚合查询条件
AvgAggregationBuilder ab= AggregationBuilders.avg("avg_count").field("count");
// 创建查询对象
SearchQuery build = new NativeSearchQueryBuilder()
.withQuery(queryBuilder) //添加查询条件
.addAggregation(ab) // 添加聚合条件
.withPageable(PageRequest.of(0, 10)) //符合查询条件的文档分页(不是聚合的分页)
.build();
// 执行查询
AggregatedPage<Book> testEntities = elasticsearchTemplate.queryForPage(build, Book.class);
// 取出聚合结果
Aggregations entitiesAggregations = testEntities.getAggregations();
InternalAvg avg = (InternalAvg) entitiesAggregations.asMap().get("avg_count");
System.out.println(avg.getValueAsString());
}
聚合工具类
//(1)统计某个字段的数量
ValueCountBuilder vcb= AggregationBuilders.count("count_uid").field("uid");
//(2)去重统计某个字段的数量(有少量误差)
CardinalityBuilder cb= AggregationBuilders.cardinality("distinct_count_uid").field("uid");
//(3)聚合过滤
FilterAggregationBuilder fab= AggregationBuilders.filter("uid_filter").filter(QueryBuilders.queryStringQuery("uid:001"));
//(4)按某个字段分组
TermsBuilder tb= AggregationBuilders.terms("group_name").field("name");
//(5)求和
SumBuilder sumBuilder= AggregationBuilders.sum("sum_price").field("price");
//(6)求平均
AvgBuilder ab= AggregationBuilders.avg("avg_price").field("price");
//(7)求最大值
MaxBuilder mb= AggregationBuilders.max("max_price").field("price");
//(8)求最小值
MinBuilder min= AggregationBuilders.min("min_price").field("price");
//(9)按日期间隔分组
DateHistogramBuilder dhb= AggregationBuilders.dateHistogram("dh").field("date");
//(10)获取聚合里面的结果
TopHitsBuilder thb= AggregationBuilders.topHits("top_result");
//(11)嵌套的聚合
NestedBuilder nb= AggregationBuilders.nested("negsted_path").path("quests");
//(12)反转嵌套
AggregationBuilders.reverseNested("res_negsted").path("kps ");
参考:
ElasticSearch(四)spring-data-elasticsearch @Field注解无效,最完美解决方案_晴天小哥哥的博客-CSDN博客
elasticsearch时间格式DateFormat的含义 - ☞书香门第☜ - 博客园
聚合
https://blog.csdn.net/wenwen513/article/details/85163168
/**
* java操作查询api https://www.cnblogs.com/fclbky/p/7124469.html
* @author 231
* 参考文档:https://blog.csdn.net/qq_42383787/article/details/89476236
* https://blog.csdn.net/textboy/article/details/51006970
* https://www.cnblogs.com/sbj-dawn/p/8891419.html
* https://www.jb51.net/article/166763.htm
* http://www.mamicode.com/info-detail-2731123.html
* https://www.cnblogs.com/yanliang12138/p/12081379.html *************重点
* https://blog.csdn.net/mingyonghu/article/details/109758236 重点*****
*
* ElasticSearchTemplate
* https://www.cnblogs.com/powerwu/articles/12060916.html
* https://blog.csdn.net/tianyaleixiaowu/article/details/77965257
* https://blog.csdn.net/tianyaleixiaowu/article/details/76149547/
* https://blog.csdn.net/qq_16436555/article/details/94398049?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control
*//*
相似度查询