Elasticsearch

1.Elasticsearch

1.1 介绍

ELK(Elastic Stack)是以Elastic为核心的技术栈,如下图所示:

这个Lucene使用java写成的,其实就是个jar包,我们引入之后就可以使用这个Lucene的API。而ES就是基于Lucene的二次开发,对其API进行进一步封装:

1.1.1 正排索引

基于文档id创建索引。查询词条时必须先找到文档,而后判断是否包含词条

就是传统的关系型数据中的表,通过id或通过某个值来查询记录。

1.1.2 倒排索引

对文档内容分词,对词条创建索引,并记录词条所在文档的信息。查询时先根据词条查询到文档id,而后获取到文档

  • 概念
    • 文档:每一条数据就是一个文档(每一条记录就是一个文档)
    • 词条:文档按照语义分成词语

示例如下:

这个倒排索引其实和生活中字典相当像,你拿到一本字典的目录,肯定不会傻到先找页码,你肯定是先大略看一眼目录的关键字,然后找到关键字之后,去看关键字旁边的页码,最后再根据页码翻到书对应的那一页。

搜索过程图

先倒排索引查出文档的ID值,然后根据ID使用正排索引查出结果。

1.1.3 相关概念

  • Index(索引)
    其实就是相同数据类型的数据集合,长这样。
    即同类型的文档集合。
  • 文档(Document)
    • 之前说elasticsearch是面向文档的,那么就意味着索引和搜索数据的最小单位是文档,elasticsearch中,文档有几个重要属性:
      • 自我包含,一篇文档同时包含字段和对应的值,也就是同时包含key:value !
      • 可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这么来的! {就是一个json对象 ! fastjson进行自动转换 !}
      • 灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段。

尽管我们可以随意的新增或者忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符串也可以是整形。因为elasticsearch会保存字段和类型之间的映射及其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在elasticsearch中,类型有时候也称为映射类型。

  • 就是索引中的其中一个记录,一条数据
  • 约束(Mapping)
    • 就是数据类型,表示文档中属性的数据类型是什么
  • DSL
    • 用来访问Elasticsearch的语言,JSON风格的请求语句,支持Resful风格,使用的是http协议通信
  • 分片
    • 因为es使用的是集群,所以对于一个索引不可能全部都存放在同一个节点上,因此要分片,即划分索引。默认是5个主分片,5个对应主分片的副本

1.1.4 数据库与elasticsearch使用场景

1.2 安装

1.2.1 安装es

创建网络

因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络:

docker network create es-net

加载es镜像

直接拉去镜像即可

docker pull elasticsearch:7.12.1

运行es

docker run -d \
	--name es \
    -e "ES_JAVA_OPTS=-Xms256m -Xmx256m" \ # 建议512m++ 这里因为阿里云内存有限
    -e "discovery.type=single-node" \
    -v es-data:/usr/share/elasticsearch/data \
    -v es-plugins:/usr/share/elasticsearch/plugins \
    -v es-logs:/usr/share/elasticsearch/logs \
    --privileged \
    --network es-net \
    -p 9200:9200 \
    -p 9300:9300 \
elasticsearch:7.12.1

命令解释:

  • -e "cluster.name=es-docker-cluster":设置集群名称
  • -e "http.host=0.0.0.0":监听的地址,可以外网访问
  • -e "ES_JAVA_OPTS=-Xms512m -Xmx512m":内存大小
  • -e "discovery.type=single-node":非集群模式
  • -v es-data:/usr/share/elasticsearch/data:挂载逻辑卷,绑定es的数据目录
  • -v es-logs:/usr/share/elasticsearch/logs:挂载逻辑卷,绑定es的日志目录
  • -v es-plugins:/usr/share/elasticsearch/plugins:挂载逻辑卷,绑定es的插件目录
  • --privileged:授予逻辑卷访问权
  • --network es-net :加入一个名为es-net的网络中
  • -p 9200:9200:端口映射配置

1.2.2 安装kibana

拉镜像

docker pull kibana:7.12.1

运行

docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601  \
kibana:7.12.1
  • --network es-net :加入一个名为es-net的网络中,与elasticsearch在同一个网络中
  • -e ELASTICSEARCH_HOSTS=http://es:9200":设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch
  • -p 5601:5601:端口映射配置

kibana启动一般比较慢,需要多等待一会,可以通过命令:

docker logs -f kibana

打开看看

测试

1.2.3 安装IK分词器

分词:即把一段中文或者别的划分成一个个的关键字,我们在搜索时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一一个匹配操作,默认的中文分词是将每个字看成一个词(不使用用IK分词器的情况下)

es在创建倒排索引时需要对文档分词;在搜索时,需要对用户输入内容分词。但默认的分词规则对中文处理并不友好。

我们在kibana的DevTools中测试:

可以看到每一词都被分出来了,不符合预期,我们需要使用其他分词软件

1)查看数据卷目录

安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看:

docker volume inspect es-plugins

显示结果:

[
    {
        "CreatedAt": "2022-05-06T10:06:34+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
        "Name": "es-plugins",
        "Options": null,
        "Scope": "local"
    }
]

说明plugins目录被挂载到了:/var/lib/docker/volumes/es-plugins/_data这个目录中。

2)解压分词器安装包

下面我们需要把课前资料中的ik分词器解压缩,重命名为ik

链接:https://pan.baidu.com/s/1oS-Hf5vWFAi5mH76ehDcrg?pwd=rki9
提取码:rki9

3)上传到es容器的插件数据卷中

也就是/var/lib/docker/volumes/es-plugins/_data

4)重启容器

# 4、重启容器
docker restart es

# 查看es日志
docker logs -f es

5)测试

IK分词器包含两种模式:

  • ik_smart:最少切分
  • ik_max_word:最细切分,分得词更多,空间占用率高
GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "阿果程序员学习java太棒了"
}
GET /_analyze
{
  "analyzer": "ik_smart",
  "text": "阿果程序员学习java太棒了"
}

1.3 自定义分词

docker目录下:/var/lib/docker/volumes/es-plugins/_data/ik/config/

中打开IKAnalyzer.cfg.xml文件

aguo.dic aguo.dic就可以存放在当前文件,作为自定义词汇

<?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">aguo.dic</entry>
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords"></entry>
	<!--用户可以在这里配置远程扩展字典 -->
	<!-- <entry key="remote_ext_dict">words_location</entry> -->
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

aguo.dic文件示例

奥利给
阿果

更新这个词典文件后,需要docker restart es重启es服务

1.4 Rest风格操作

一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁更有层次更易于实现缓存等机制。

1.4.1 基本命令

methodurl地址描述
PUT(创建,修改)localhost:9200/索引名称/类型名称/文档id创建文档(指定文档id)
POST(创建)localhost:9200/索引名称/类型名称创建文档(随机文档id)
POST(修改)localhost:9200/索引名称/类型名称/文档id/_update修改文档
DELETE(删除)localhost:9200/索引名称/类型名称/文档id删除文档
GET(查询)localhost:9200/索引名称/类型名称/文档id查询文档通过文档ID
POST(查询)localhost:9200/索引名称/类型名称/文档id/_search查询所有数据

1.4.2 索引操作

1.4.2.1 创建索引

PUT /索引名/类型名/ID

{

DSL语句

}

//新建索引
PUT /aguoindex/1
{
    "name":"阿果爱Java",
    "age":3
    
}
  • 数据类型
    • 字符串类型
      • text、
        keyword
        • text:支持分词,全文检索,支持模糊、精确查询,不支持聚合,排序操作;text类型的最大支持的字符长度无限制,适合大字段存储;
        • keyword:不进行分词,直接索引、支持模糊、支持精确匹配,支持聚合、排序操作。keyword类型的最大支持的长度为——32766个UTF-8类型的字符,可以通过设置ignore_above指定自持字符长度,超过给定长度后的数据将不被索引,无法通过term精确匹配检索返回结果。
    • 数值型
      • long、Integer、short、byte、double、float、half floatscaled float
    • 日期类型
      • date
    • te布尔类型
      • boolean
    • 二进制类型
      • binary
    • 等等…

指定类型时创建规则

PUT /aguoindex
{
  "mappings":{
    "properties":{
      "info":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "name":{ #对象类型
        "type":"object",
        "properties": {
          "firstname":{
            "type":"keyword"
          },
          "lastname":{
            "type":"keyword"
          }
        }
      },
      "age":{ #不参与倒排索引
        "type":"long",
        "index": false
      },
      "birthday":{
        "type":"date"
      }
    }
  }
}
1.4.2.2 修改索引

禁止修改索引库,允许添加新的字段。

POST /aguoindex/_mapping
{
  "properties":{
    "sex":{
      "type":"keyword"
    }
  } 
}
1.4.2.4 删除索引
DElETE /aguoindex

1.4.3 文档语法

新增

POST /aguoindex/_doc/1
{
  "info":"阿果爱学Java",
  "age":21,
  "brithday":"2000-08-31",
  "name":{
    "firstname":"阿",
    "lastname":"果"
  }
}

查询

GET /aguoindex/_doc/1

删除

DELETE /aguoindex/_doc/1

修改

#存在则修改,不存在则新增,全量修改
PUT /aguoindex/_doc/1
{
  "info":"阿果爱学Java",
  "age":21,
  "brithday":"2000-08-31",
  "name":{
    "firstname":"阿",
    "lastname":"果"
  }
}
#局部修改
POST /aguoindex/_update/1
{
  "doc": {
    "age":22
  }
}

1.5 RestClient

ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http即请求发送给ES。

链接:https://pan.baidu.com/s/1xkAW_U7_rwAhkSeuMC8BHQ?pwd=bg9q
提取码:bg9q

准备好数据

创建索引

PUT /hotel 
{
  "mappings":{
    "properties": {
      "id":{
        "type": "keyword" 
      },
      "name":{
        "type": "text",
        "analyzer": "ik_max_word",
        "copy_to": "all"
      },
      "address":{
        "type": "keyword",
        "index": false
      },
      "price":{
        "type": "integer"
      },
      "score":{
        "type": "integer"
      },
      "brand":{
        "type": "keyword",
        "copy_to": "all"
      },
      "city":{
        "type": "keyword",
        "copy_to": "all"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword"
      },
      "location":{
        "type": "geo_point"
      },
      "pic":{
        "type": "keyword",
        "index": false
      },
      "all":{
        "type": "text",
        "analyzer": "ik_max_word"
      }
      
    }
  }
}

“all” 字段是为了方便搜索而存在,聚集了 酒店名字、品牌、城市等信息的集合体

1.5.1 Springboot操作

导入依赖

<dependency>
  <groupId>org.elasticsearch.client</groupId>
  <artifactId>elasticsearch-rest-high-level-client</artifactId>
  <version>7.17.4</version>
</dependency>
<dependency>
  <groupId>org.elasticsearch</groupId>
  <artifactId>elasticsearch</artifactId>
  <version>7.17.4</version>
</dependency>

创建es连接

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://120.76.137.145:9200")
        ));
1.5.1.1 索引库操作

创建索引库

//1.创建Request对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
//2、准备请求的参数:DSL语句
request.source(HoTelConstant.createIndexDSL, XContentType.JSON);
//3.发起请求
client.indices().create(request, RequestOptions.DEFAULT);

删除索引库

@Test
void deleteHotelIndex() throws IOException {
    //1.创建Request对象
    DeleteIndexRequest request = new  DeleteIndexRequest("hotel");
//2.发起请求
client.indices().delete(request, RequestOptions.DEFAULT);
}

判断索引库

@Test
void isExitsHotelIndex() throws IOException {
    //1.创建Request对象
    GetIndexRequest request = new GetIndexRequest("hotel");
    //2.发起请求
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    System.out.println(exists);
}
1.5.1.2 文档操作

插入文档

//获取request对象
IndexRequest request = new IndexRequest("hotel").id("123456");
//设置请求的body
request.source(JSON.toJSONString(hotelDoc),XContentType.JSON);
//发起请求
client.index(request, RequestOptions.DEFAULT);

查询文档

//获取request对象,索引与id
GetRequest request = new GetRequest("hotel","36934");
//得到response
GetResponse response = client.get(request, RequestOptions.DEFAULT);
//从response得到对象的JSON字符串形式
String hotelJson = response.getSourceAsString();
//反序列化为对象
HotelDoc hotelDoc = JSON.parseObject(hotelJson, HotelDoc.class);
System.out.println(hotelDoc);

更新文档

//获取request
UpdateRequest request = new UpdateRequest("hotel", "36934");
//封装body
request.doc(
    "price","999888"
);
//发起更新
client.update(request, RequestOptions.DEFAULT);

删除文档

//获取request
DeleteRequest request = new DeleteRequest("hotel","36934");
//发起删除
client.delete(request, RequestOptions.DEFAULT);

批量导入

List<Hotel> list = iHotelService.list();
LinkedList<HotelDoc> link = new LinkedList<>();
for (Hotel hotel : list) {
    link.add(new HotelDoc(hotel));
}
//以上是封装数据
//获取request对象
BulkRequest request = new BulkRequest();
//遍历对象,全部放到request中
for (HotelDoc hotelDoc : link) {
    request.add(new IndexRequest("hotel").id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc),XContentType.JSON));
}
//提交
client.bulk(request, RequestOptions.DEFAULT);

1.6 DSL 语法

1.6.1 Query分类

Elasticsearch:提供了基于SON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括:

  • 查询所有:查询出所有数据,一般测试用。例如:match_all
  • 全文检案(full text)查询:利用分词器对用户输入内容分词,然后去倒排案引库中匹配。例如:
    • match_quey
    • multi_match_query
  • 精确查询=:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如: 与上面互斥
    • ids
    • range
    • term
  • 地理(geo)查询:根据经纬度查询。例如:
    • geo_distance
    • geo_bounding_box【不常用】
  • 复合(compound)查询:复合查询可以将上述各种查询景件组合起来,合并查询景件。例如:
    • bool
    • function_score

1.6.2 全文检索

match查询

全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索,语法:

GET /hotel/_search
{
    "query":{
        "match":{
            "name":"李四"
        }
    }
}

multi_match 查询

GET /hotel/_search
{
    "query":{
        "multi_match":{
            "query":"外滩如家",
            "fields":["brand","name","business"]
        }
    }
}

1.6.3 精确查询

term查询:

根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段

GET /hotel/_search
{
  "query": {
    "term": {
      "city": {
        "value": "上海"
      }
    }
  }
}

1.6.4 模糊查询

range查询:

根据数值范围查询,可以是数值、日期的范围

GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 10, # 大于等于,去掉e就只是大于
        "lte": 2000
      }
    }
  }
}

1.6.5 地理查询

distance 查询

指定圆心与半径,检索出圆形中所有的点

GET /hotel/_search
{
  "query": {
    "geo_distance": {
      "distance":"15km",
      "location":"31.21,121.5"
    }
  }
}

1.6.6 Function Score Query

根据结果集的命中词频进行排序

DSL的模板如下,加权模式可以忽略

示例如下:

让“如家”这个品牌更加靠前。

GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": { #query_socre
        "match": {
          "all": "外滩"
        }
      },
      "functions": [
        {
          "filter": { #对“如家”的品牌结果施加权重
            "term": {
              "brand": "如家"
            }
          },
          "weight": 10
        }
      ]
    }
  }
}

1.6.7 Boolean Query

逻辑运算

  • bool查询有几种逻辑关系?
    • must:必须匹配的条件,可以理解为“与”
    • should:选择性匹配的条件,可以理解为"或”
    • must not:必须不匹配的条件,不参与打分
    • filter:必须匹配的条件,不参与打分

模板如下

GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {}
      ],
      "should": [
        {}
      ],
      "must_not": [
        {}
      ],
      "filter": [
        {}
      ]
    }    
  }
}

示例

GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "brand": {
              "value": "如家"
            }
          }
        }
      ],
      "should": [
        {
          "match": {
            "city": "北京"
          }
        },
        {
          "match": {
            "city": "上海"
            
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "price": {
              "gte": 3000
            }
          }
        }
      ],
      "filter": [
        {
          "geo_distance": {
            "distance": "15km",
            "location": {
              "lat": 31.21,
              "lon": 121.5
            }
          }
          
        }
      ]
    }    
  }
}

1.7 结果集处理

1.7.1 排序

对结果集更加keyword、数值、地理坐标、日期排序

数值排序

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "FIELD": {
        "order": "asc/desc"
      },
      #...
    }
  ]
}

地理排序

距离据点的距离排序

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance": {
        "location": {
          "lat": 40,
          "lon": -70
        },
        "order": "desc",
        "unit": "km"
      }
    }
  ]
}

1.7.2 分页

官方限制1w条分页数据,需要在应用层保证,超出的部分需要通过其他更多条件来筛选结果

模板

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "FIELD": {
        "order": "asc"
      }
    }
  ],
  "from": 10,
  "size": 100
}

因为es是把10+100=110的结果全部检索出来,最后截取10条,所以对于大数据量的情况下,需要更多条件来筛选。

1.7.3 高亮

就是在搜索结果中把搜索关键字突出显示

原理:

  • 将搜索结果中的关键字用标签标记出来
  • 在页面中给标签添加css样式
  1. query域不能使用match_all
  2. 如果query的待查字段与高亮字段不一致,需要加"require_field_match": “false”

模板

GET /hotel/_search
{
  "query": {
    "match": {
      "all": "如家"
    }
  },
  "highlight": {
    "fields": {#fields关键字
      "name": {
        "require_field_match": "false",
          #默认是<em></em>
        "pre_tags": "<aguo>"
        , "post_tags": "</aguo>"
      }
    }
  }
}

1.8 RestCilent使用DSL语法

核心就是SearchRequest

解析数据的通用方法

private void analyticalData(SearchResponse search) {
    //获取一个列表数据
    SearchHits hitList = search.getHits();
    long total = hitList.getTotalHits().value;
    System.out.printf("共找到"+total+"条数据");
    //获取一个hit数组
    SearchHit[] hits = hitList.getHits();
    //反序列化
    for (SearchHit hit : hits) {
        String sourceAsString = hit.getSourceAsString();
        HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
        System.out.println(hotelDoc);
    }
}

1.8.1 match_all

示例

//获取request对象
SearchRequest request = new SearchRequest("hotel");
//封装body
request.source()
    .query(QueryBuilders.matchAllQuery());
//发起请求
SearchResponse search = this.client.search(request, RequestOptions.DEFAULT);
//解析
analyticalData(search);
  • request.source()
    • 与DSL的一级菜单一致,其同等级的含有query、sort、highlighter
  • QueryBuilders
    • 含有与query相关的查询,如term、match、bool等操作
  • cilent
    • 在1.5.1有

1.8.2 multi_match

//request
SearchRequest request = new SearchRequest("hotel");
//封装body
request.source()
    .query(QueryBuilders.multiMatchQuery("如家","brand","all"));
SearchResponse search = this.client.search(request, RequestOptions.DEFAULT);
//解析
analyticalData(search);

1.8.3 bool查询

@Test
void booleanQuery() throws IOException {
    //获取request
    SearchRequest request = new SearchRequest("hotel");
    //使用Bool查询
    BoolQueryBuilder builder = new BoolQueryBuilder();
    //封装Bool
    builder.must(QueryBuilders.termQuery("city","北京"));
    builder.mustNot(QueryBuilders.rangeQuery("price").gt(1000));
    builder.filter(QueryBuilders.termQuery("brand","如家"));
    //封装body
    request.source().query(builder);
    //发起请求
    SearchResponse search = this.client.search(request, RequestOptions.DEFAULT);
    //解析请求
    analyticalData(search);
}

与以下完全对应

GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "city": {
              "value": "北京"
            }
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "price": {
              "gt": 1000
            }
          }
        }
      ],
      "filter": [
        {
          "term": {
            "brand": "如家"
          }
        }
      ]
    }
  }
}

1.8.4 排序与分页

source()含有query、sort、highlighter,支持链式编程,很舒服

int page = 1;
int pageSize = 10;
//获取request
SearchRequest request = new SearchRequest("hotel");
request.source()
.query(QueryBuilders.termQuery("city","北京"))
//排序
.sort("price", SortOrder.ASC)
//分页
.from((page-1)*pageSize).size(10);
//发起请求
SearchResponse search = this.client.search(request, RequestOptions.DEFAULT);
//解析请求
analyticalData(search);

1.8.5 高亮

与query同级别,所以直接在query后面链式

//获取request
SearchRequest request = new SearchRequest("hotel");
request.source()
    .query(QueryBuilders.matchQuery("all","二钻"))
    .highlighter(new HighlightBuilder()
                 .field("name")
                 .requireFieldMatch(false)
                 .field("city")
                 .requireFieldMatch(false)
                );
//发起请求
SearchResponse search = this.client.search(request, RequestOptions.DEFAULT);
//解析请求
analyticalData(search);

自己写了个工具类

/**
     * 
     * @param search response结果
     * @param type 反序列化的对象类型
     * @return 高亮标签插入后对象
     * @param <T> 
     * @throws Exception 可能的反射异常,包括反射方法失败、调用失败
     */
private <T> Collection<T> analyticalData(SearchResponse search, Class<T>  type) throws Exception {
    //获取一个列表数据
    SearchHits hitList = search.getHits();
    long total = hitList.getTotalHits().value;
    System.out.println("共找到"+total+"条数据");
    //获取一个hit数组
    SearchHit[] hits = hitList.getHits();
    //开始反序列化
    Collection<T> collection = new LinkedList<>();
    for (SearchHit hit : hits) {
        String sourceAsString = hit.getSourceAsString();
        //反序列化
        T bean = JSON.parseObject(sourceAsString, type);
        //获取高亮map
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        //遍历高亮field,如name,city要求的高亮
        for (String key : highlightFields.keySet()) {
            //根据field获取高亮的对象,text为,如<em>北京</em>
            HighlightField highlightField = highlightFields.get(key);
            Text[] fragments = highlightField.fragments();
            //经过不断测试,这里Text.length===1,时间复杂度:O(1)
            for (Text fragment : fragments) {
                StringBuilder sb = new StringBuilder();
                sb.append("set");
                sb.append(key.substring(0,1).toUpperCase());
                sb.append(key.substring(1));
                Method setMethod = bean.getClass().getMethod(sb.toString(), String.class);
                setMethod.invoke(bean,fragment.string());
            }
        }
        collection.add(bean);
    }
    return collection;
}

1.9 聚合

关于聚合的详细操作博客地址

聚合(aggregations)可以实现对文档数据的统计、分析、运算。

聚合有三类:

  • 桶(Bucket)聚合:用来对文档做分组
    • TermAggregation:按照文档字段值分组
    • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
    • Histogram Aggregation:根据数值阶梯分组,与日期类似
    • Range Aggregation:数值和日期的范围分组,指定开始和结束,然后按段分组
  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求max、min、avg、sum等
  • 管道(pipeline)聚合:其它聚合的结果为基础做聚合【少用】

聚合三要素

  • 聚合名称
  • 聚合类型
  • 聚合字段

聚合可配置属性有:

  • size:指定聚合结果数量
  • order:指定聚合结果排序方式
  • field:指定聚合字段

1.9.1 桶聚合

aggsquery同级别!

参与聚合的字段必须是keyword类型

默认聚合所有数据,因此大数据时候需要使用query过滤下

语法

GET /hotel/_search
{
  "size": 0,#表示不显示文档
  "aggs": {
    "brandeggs": {#自定义聚合名
      "terms": {
        "field": "name",
        "size": 10,#聚合结果数量
        "order": {#自定义排序规则
          "_count": "asc"
        }
      }
    }
  }
}

1.9.2 Metric聚合

对桶聚合的结果再次聚合

语法

GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandeggs": {
      "terms": {
        "field": "brand",
        "size": 20,
        "order": {
          "scoreAgg.avg": "desc"
        }
      },
      "aggs": {
        "scoreAgg": {#自定义聚合名
          "stats": {#stats是max,min全计算
            "field": "score"
          }
          
        }
      }
    }
  }
}

1.9.3 概念区分

"a":{
  "terms:{..}//关键字分桶
   "aggs":{
      "b":{}
      "c":{}
    }
}
  • a为顶级分桶,a-terms是a分桶的依据
  • a-aggs中,表示在a的基础上再次分桶
  • b,c是同级关系,相互独立 互不干扰,都是在a分桶的基础上分组

1.10 自动补全

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值