1url
1 .1elasticsearch-head访问地址
1.2kibana 访问地址
http://192.168.124.129:5601/app/home#/
进入容器,开一个伪端口
docker exec -u 0 -it 1057344e383e /bin/bash
执行
apt-get update
apt-get install vim -y
#使用vi 修改文件内容
vi /usr/share/kibana/config/kibana.yml
server.host: "0.0.0.0"
server.shutdownTimeout: "5s"
elasticsearch.hosts: [ "http://192.168.124.129:9200" ]
monitoring.ui.container.elasticsearch.enabled: true
i18n.locale: zh-CN
2
3酒店搜索
3.1创建酒店索引
PUT /hotel
{
"mappings": {
"properties": {
"title": {
"type": "text"
},
"city": {
"type": "keyword"
},
"price": {
"type": "double"
}
}
}
}
3.2写入文档
POST /hotel/_doc/001
{
"title":"好再来酒店",
"city":"青岛",
"price":"578.23"
}
3.3根据id搜索文档
GET /hotel/_doc/001
3.4根据一般字段搜索文档
使用term进行精确查询
GET /hotel/_search
{
"query": {
"term": {
"price": {
"value": "578.23"
}
}
}
}
3.5根据文本字段搜索文档
使用match搜索对某个字段进行模糊匹配
GET /hotel/_search
{
"query": {
"match": {
"title": "再来"
}
}
}
4SpringBoot整合Spring Data Elasticsearch
4.1pom引入
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>myes</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>myes</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<elasticsearch>7.17.1</elasticsearch>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.2修改 application.yml
spring:
elasticsearch:
rest:
uris: http://192.168.124.129:9200
4.3代码
Hotel类
package com.example.myes.dto;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import java.io.Serializable;
/**
* @author: liuy
* @date: 2022/11/9 20:40
* @description:
* @version: 1.0
*/
@Document(indexName = "hotel")
@Data
public class Hotel implements Serializable {
private static final long serialVersionUID = 5649160852330501329L;
/**
* es的_id
*/
@Id
private String id;
/**
* 对应索引的title
*/
private String title;
/**
* 对应索引的city
*/
private String city;
/**
* 对应索引的price
*/
private String price;
}
HotelEsDao类
package com.example.myes.dao;
import com.example.myes.dto.Hotel;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
/**
* @author: liuy
* @date: 2022/11/9 20:45
* @description:
* @version: 1.0
*/
public interface HotelEsDao extends CrudRepository<Hotel, String> {
List<Hotel> findByTitleLike(String title);
}
定义HotelEsService类
package com.example.myes.service;
import com.example.myes.dao.HotelEsDao;
import com.example.myes.dto.Hotel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author: liuy
* @date: 2022/11/9 20:48
* @description:
* @version: 1.0
*/
@Service
public class HotelEsService {
@Autowired
private HotelEsDao hotelEsDao;
public List<Hotel> getHotelFromTitle(String keyword) {
return hotelEsDao.findByTitleLike(keyword);
}
}
定义TestController类
package com.example.myes.controller;
import com.example.myes.dto.Hotel;
import com.example.myes.service.HotelEsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author: liuy
* @date: 2022/11/9 20:14
* @description:
* @version: 1.0
*/
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private HotelEsService hotelEsService;
@RequestMapping("/test1")
public String test1(String title) {
List<Hotel> hotelList = hotelEsService.getHotelFromTitle(title);
if(CollectionUtils.isEmpty(hotelList)){
return "no data";
}
return hotelList.toString();
}
}
5索引的操作
5.1创建索引
PUT /hotel
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
},
"mappings": {
"properties": {
"title": {
"type": "text"
},
"city": {
"type": "keyword"
},
"price": {
"type": "double"
}
}
}
}
5.2删除索引
DELETE /hotel
5.3关闭索引
由于上一步删除了,先添加索引再关闭
PUT /hotel
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
},
"mappings": {
"properties": {
"title": {
"type": "text"
},
"city": {
"type": "keyword"
},
"price": {
"type": "double"
}
}
}
}
POST /hotel/_close
写入数据
POST /hotel/_doc/002
{
"title":"阳光夏日酒店",
"city":"北京",
"price":788.00
}
搜索数据
GET /hotel/_search
{
"query": {
"match": {
"title": "再来"
}
}
}
5.4打开索引
POST /hotel/_open
5.5索引别名
创建三个索引
PUT /january_log
{
"mappings":{
"properties":{
"uid":{
"type":"keyword"
},
"hotel_id":{
"type":"keyword"
},
"check_in_date":{
"type":"keyword"
}
}
}
}
PUT /february_log
{
"mappings":{
"properties":{
"uid":{
"type":"keyword"
},
"hotel_id":{
"type":"keyword"
},
"check_in_date":{
"type":"keyword"
}
}
}
}
PUT /march_log
{
"mappings":{
"properties":{
"uid":{
"type":"keyword"
},
"hotel_id":{
"type":"keyword"
},
"check_in_date":{
"type":"keyword"
}
}
}
}
插入数据,同一用户在不同月份的入住记录
POST /january_log/_doc/001
{
"uid":"001",
"hotel_id":"92772",
"check_in_date":"2021-01-05"
}
POST /february_log/_doc/001
{
"uid":"001",
"hotel_id":"33224",
"check_in_date":"2021-02-23"
}
POST /march_log/_doc/001
{
"uid":"001",
"hotel_id":"92772",
"check_in_date":"2021-03-28"
}
创建别名
POST /_aliases
{
"actions": [
{
"add": {
"index": "january_log",
"alias": "last_three_month"
}
},
{
"add": {
"index": "february_log",
"alias": "last_three_month"
}
},
{
"add": {
"index": "march_log",
"alias": "last_three_month"
}
}
]
}
请求在索引last_three_month中搜索uid为001的用户的入住记录
GET /last_three_month/_search
{
"query": {
"term": {
"uid": "001"
}
}
}
需要指出的是,在默认情况下,当一个别名只指向一个索引时,写入数据的请求可以指向这个别名,如果这个别名指向多个索引(就像上面的例子),则写入数据的请求是不可以指向这个别名的
6映射
6.1查看映射
GET /hotel/_mapping
6.2扩展映射
映射中的字段不能修改,可以扩展,新增字段tag
POST /hotel/_mapping
{
"properties": {
"tag": {
"type": "keyword"
}
}
}
查看 索引
GET /hotel/_mapping
6.3基本的数据类型
keyword类型
keyword类型是不进行切分的字符串类型。这里的“不进行切分”指的是:在索引时,对keyword类型的数据不进行切分,直接构建倒排索引;在搜索时,对该类型的查询字符串不进行切分后的部分匹配。keyword类型数据一般用于对文档的过滤、排序和聚合
在现实场景中,keyword经常用于描述姓名、产品类型、用户ID、URL和状态码等。keyword类型数据一般用于比较字符串是否相等,不对数据进行部分匹配,因此一般查询这种类型的数据时使用term查询。
text类型
text类型是可进行切分的字符串类型。这里的“可切分”指的是:在索引时,可按照相应的切词算法对文本内容进行切分,然后构建倒排索引;在搜索时,对该类型的查询字符串按照用户的切词算法进行切分,然后对切分后的部分匹配打分
数字 ,日期等类型
6.4多字段
针对同一个字段,有时需要不同的数据类型,这通常表现在为了不同的目的以不同的方式索引相同的字段。例如,在订单搜索系统中,既希望能够按照用户姓名进行搜索,又希望按照姓氏进行排列,可以在mapping定义中将姓名字段先后定义为text类型和keyword类型,其中,keyword类型的字段叫作子字段,这样ES在建立索引时会将姓名字段建立两份索引,即text类型的索引和keyword类型的索引。订单搜索索引的定义如下
PUT /hotel_order
{
"mappings": {
"properties": {
"order_id": {
"type": "keyword"
},
"user_id": {
"type": "keyword"
},
"user_name": {
"type": "text",
"fields": {
"user_name_keyword": {
"type": "keyword"
}
}
},
"hotel_id": {
"type": "keyword"
}
}
}
}
添加数据
如果报错:Malformed action/metadata line [3], expected START_OBJECT but found [VALUE_STRING] 注意不能换行
POST /_bulk
{"index":{"_index":"hotel_order","_id":"001"}}
{"order_id": "001","user_id":"user_00x","user_name":"Michael Jordan", "hotel_id":"h001"}
{"index":{"_index":"hotel_order","_id":"002"}}
{"order_id": "002","user_id":"user_00a","user_name":"Stephen Jordan", "hotel_id":"h0500"}
{"index":{"_index":"hotel_order","_id":"003"}}
{"order_id": "003","user_id":"user_30e","user_name":"Tim Jordan", "hotel_id":"h0520"}
{"index":{"_index":"hotel_order","_id":"004"}}
{"order_id": "004","user_id":"user_430","user_name":"Kobe Jordan", "hotel_id":"h0600"}
查询数据
GET /hotel_order/_search
{
"query": {
"match": {
"user_name": "Jordan"
}
},
"sort": {
"user_name.user_name_keyword": "asc"
}
}
6.5文档操作
单条写入文档
POST /hotel/_doc/001
{
"title":"好再来酒店",
"city":"青岛",
"price":578.23
}
批量写入文档
不指定_id
POST /_bulk
{"index":{"_index":"hotel"}}
{"title": "文雅酒店","city": "北京","price": 556.00}
{"index":{"_index":"hotel"}}
{"title": "嘉怡假日酒店","city": "北京","price": 337.00}
指定_id
POST /_bulk
{"index":{"_index":"hotel","_id":"001"}}
{"title": "文雅酒店","city": "北京","price": 556.00}
{"index":{"_index":"hotel","_id":"002"}}
{"title": "嘉怡假日酒店","city": "北京","price": 337.00}
更新单条文档
POST /hotel/_update/001
{
"doc": {
"title": "好再来酒店",
"city": "北京",
"price": 659.45
}
}
查看更新后的结果
GET /hotel/_doc/001
upsert 存在更新,不存在新增
POST /hotel/_update/001
{
"doc": {
"title": "好再来酒店",
"city": "北京",
"price": 659.45
},
"upsert": {
"title": "好再来酒店",
"city": "北京",
"price": 659.45
}
}
批量更新文档
POST /_bulk
{"update":{"_index":"hotel","_id":"001"}}
{"doc":{"title": "文雅豪情酒店","city": "北京","price": 556.00}}
{"update":{"_index":"hotel","_id":"002"}}
{"doc":{"title": "嘉怡七天酒店","city": "北京","price": 337.00}}
根据条件更新文档
query用于指定更新数据的匹配条件,相当于SQL中的where语句;script用于指定具体的更新操作,相当于SQL的set内容
POST /hotel/_update_by_query
{
"query": {
"term": {
"city": {
"value": "北京"
}
}
},
"script": {
"source": "ctx._source['city']='上海'",
"lang": "painless"
}
}
执行以上DSL后,ES将先搜索城市为“北京”的酒店,然后把这些酒店的城市字段的值改为“上海”。
删除单条文档
DELETE /hotel/_doc/001
批量删除文档
POST /_bulk
{"delete":{"_index":"hotel","_id":"001"}}
{" delete ":{"_index":"hotel","_id":"002"}}
根据条件删除文档
条件删除文档的查询条件:城市为“北京”的文档
POST /hotel/_delete_by_query
{
"query": {
"term": {
"city": {
"value": "北京"
}
}
}
}
7丰富的搜索功能
7.1搜索辅助功能
指定返回的字段
在ES中,通过_source子句可以设定返回结果的字段。_source指向一个JSON数组,数组中的元素是希望返回的字段名称。
因为已存在,所以先删后创建索引
DELETE /hotel
PUT /hotel
{
"mappings": {
"properties": {
"title": {
"type": "text"
},
"city": {
"type": "keyword"
},
"price": {
"type": "double"
},
"create_time": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"amenities": {
"type": "text"
},
"full_room": {
"type": "boolean"
},
"location": {
"type": "geo_point"
},
"praise": {
"type": "integer"
}
}
}
}
造数据
POST /_bulk
{"index":{"_index":"hotel","_id":"001"}}
{"title": "文雅酒店","city": "青岛","price": 556.00,"create_time":"2020-04-18 12:00:00", "amenities":"浴池,普通停车场/充电停车场","full_room":false,"location":{"lat":36.083078,"lon": 120.37566},"praise":10}
{"index":{"_index":"hotel","_id":"002"}}
{"title": "金都嘉怡假日酒店","city": "北京","price": 337.00,"create_time": "2021-03-15 20:00:00","amenities":"wifi,充电停车场/可升降停车场","full_room":false, "location":{"lat": 39.915153,"lon": 116.4030},"praise":60}
{"index":{"_index":"hotel","_id":"003"}}
{"title": "金都欣欣酒店","city": "天津","price": 200.00,"create_time":"2021-05-09 16:00:00","amenities":"提供假日party,免费早餐,可充电停车场","full_room": true,"location":{"lat": 39.186555,"lon": 117.162007},"praise":30}
{"index":{"_index":"hotel","_id":"004"}}
{"title": "金都酒店","city": "北京","price": 500.00,"create_time":"2021-02-18 08:00:00", "amenities":"浴池(假日需预定),室内游泳池,普通停车场","full_room":true,"location": {"lat": 39.915343,"lon": 116.4239},"praise":20}
{"index":{"_index":"hotel","_id":"005"}}
{"title": "文雅精选酒店","city": "北京","price": 800.00,"create_time": "2021-01-01 08:00:00","amenities":"浴池(假日需预定),wifi,室内游泳池,普通停车场", "full_room":true,"location":{"lat": 39.918229,"lon": 116.422011},"praise":20}
指定搜索结果只返回title和city字段
GET /hotel/_search
{
"_source": ["title","city"],
"query": {
"term": {
"city": {
"value": "北京"
}
}
}
}
结果计数
返回城市为“北京”的酒店个数
GET /hotel/_count
{
"query": {
"term": {
"city": {
"value": "北京"
}
}
}
}
结果分页
用户可以通过设置from和size来定义搜索位置和每页显示的文档数量,from表示查询结果的起始下标,默认值为0,size表示从起始下标开始返回的文档个数,默认值为10
GET /hotel/_search
{
"from": 0,
"size": 20,
"query": {
"term": {
"city": {
"value": "北京"
}
}
}
}
在默认情况下,用户最多可以取得10 000个文档,即from为0时,size参数最大为10 000
对于普通的搜索应用来说,size设为10 000已经足够用了。如果确实需要返回多于10 000条的数据,可以适当修改max_result_window的值。以下示例将hotel索引的最大窗口值修改为了20 000。
PUT /hotel/_settings
{
"index": {
"max_result_window": 20000
}
}
注意,如果将配置修改得很大,一定要有足够强大的硬件作为支撑。
性能分析
"profile": "true", //打开性能剖析开关
GET /hotel/_search
{
"profile": "true",
"query": {
"match": {
"title": "金都"
}
}
}
{
"query":{
"match":{
"title":"金都"
}
}
}
评分分析
GET /hotel/_explain/002
{
"query": {
"match": {
"title": "金都"
}
}
}
7.2丰富的搜索匹配功能
查询所有文档
当需要查询所有文档的数据时,对应的SQL语句为select*form table_name
使用ES的match_all查询可以完成类似的功能
默认每个文档分值1.0,可以通过boost参数设定该分值
GET /hotel/_search
{
"_source": [
"title",
"city"
],
"query": {
"match_all": {
"boost": 2
}
}
}
term级别查询
搜索住宿价格为500元的酒店,price字段为数值型数据
GET /hotel/_search
{
"query": {
"term": {
"price": {
"value": "500"
}
}
}
}
以下示例是搜索城市为北京的酒店,city字段为关键字类型数据keyword
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "北京"
}
}
}
}
搜索没有满房的酒店,full_room(满房状态)字段为布尔型数据
POST hotel/_search
{
"query":{
"term":{
"full_room":false
}
}
}
搜索北京不满的
POST hotel/_search
{
"query":{
"bool":{
"filter":[
{
"term":{
"city":{
"value":"北京"
}
}
},
{
"term":{
"full_room":false
}
}
]
}
}
}
对于日期型的字段查询,需要按照该字段在mappings中定义的格式进行查询
GET /hotel/_search
{
"query": {
"term": {
"create_time": {
"value": "2021-03-15 20:00:00"
}
}
}
}
terms查询用于查询一个或多个值与待查字段是否完全匹配
搜索城市为“北京”或者“天津”的酒店示例
GET /hotel/_search
{
"query": {
"terms": {
"city": [
"北京",
"天津"
]
}
}
}
range查询
range查询用于范围查询,一般是对数值型和日期型数据的查询。使用range进行范围查询时,用户可以按照需求中是否包含边界数值进行选项设置,可供组合的选项如下:·gt:大于;·lt:小于;·gte:大于或等于;·lte:小于或等于。
查询住宿价格在300~500(包含边界值)元的酒店
查询住宿价格大于300(不包含边界值)元的酒店
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 300,
"lte": 500
}
}
}
}
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gt": 300
}
}
}
}
exists查询
在某些场景下,我们希望找到某个字段不为空的文档,则可以用exists搜索。字段不为空的条件有:·值存在且不是null;·值不是空数组;·值是数组,但不是[null]。
DELETE /hotel_1
PUT /hotel_1
{
"mappings": {
"properties": {
"title": {
"type": "text"
},
"tag": {
"type": "keyword"
}
}
}
}
POST /hotel_1/_doc/006
{
"title": "环球酒店",
"tag": null
}
POST /hotel_1/_doc/007
{
"title": "环球酒店",
"tag": []
}
POST /hotel_1/_doc/008
{
"title": "环球酒店",
"tag": [null]
}
都不命中
GET /hotel_1/_search
{
"query": {
"exists": {
"field": "tag"
}
}
}
布尔查询
must查询,计算得分
使用must查询城市为北京并且价格在350~400元的酒店
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"city": {
"value": "北京"
}
}
},
{
"range": {
"price": {
"gte": 350,
"lte": 400
}
}
}
]
}
}
}
should查询 计算得分
使用should查询城市为北京或者天津的酒店
GET /hotel/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"city": {
"value": "北京"
}
}
},
{
"term": {
"city": {
"value": "天津"
}
}
}
]
}
}
}
must not查询 计算得分
must not查询城市不是北京也不是天津的酒店
GET /hotel/_search
{
"query": {
"bool": {
"must_not": [
{
"term": {
"city": {
"value": "北京"
}
}
},
{
"term": {
"city": {
"value": "天津"
}
}
}
]
}
}
}
filter查询关注的是查询条件和文档是否匹配,不进行相关的打分计算,但是会对部分匹配结果进行缓存
请求城市为北京并且未满房的酒店的查询结果
GET /hotel/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"city": "北京"
}
},
{
"term": {
"full_room": false
}
}
]
}
}
}
如果不想让检索词频率TF(Term Frequency)对搜索结果排序有影响,只想过滤某个文本字段是否包含某个词,可以使用Constant Score将查询语句包装起来
查询amenities字段包含关键词“停车场”的酒店,满足条件打1分
GET /hotel/_search
{
"_source": ["amenities"],
"query": {
"constant_score": {
"filter": {
"match": {
"amenities": "停车场"
}
}
}
}
}
参数boost可以控制命中文档的得分,默认值为1.0
GET /hotel/_search
{
"_source": ["amenities"],
"query": {
"constant_score": {
"boost": 2.0,
"filter": {
"match": {
"amenities": "停车场"
}
}
}
}
}
当使用ES进行搜索时,命中的文档默认按照相关度进行排序。有些场景下用户需要干预该“相关度”,此时就可以使用Function Score查询。使用时,用户必须定义一个查询以及一个或多个函数,这些函数为每个文档计算一个新分数
GET /hotel/_search
{
"_source": ["title","city"],
"query": {
"function_score": {
"query": { //查询符合条件的文档
"term": {
"city": {
"value": "北京"
}
}
},
"functions": [ //定义函数
{ //此处只定义了一个函数:随机数函数
"random_score": {}
}
],
"score_mode": "sum" //最终分数是各个函数的加和值
}
}
}
全文搜索(数据类型是text)
match查询
match查询是全文搜索的主要代表。对于最基本的math搜索来说,只要分词中的一个或者多个在文档中存在即可。例如搜索“金都酒店”,查询词先被分词器切分为“金”“都”“酒”“店”,因此,只要文档中包含这4个字中的任何一个字,都会被搜索到,默认使用的是标准分词器,不适合中文搜索。
按照标题搜索“金都酒店”
GET /hotel/_search
{
"_source": ["title"], //只返回title字段
"query": {
"match": { //匹配title字段为“金都酒店”的文档
"title": "金都酒店"
}
}
}
GET /hotel/_search
{
"_source": ["title"], //只返回title字段
"query": {
"match": { //匹配title字段为“金都酒店”的文档
"title": {
"query": "金都酒店"
}
}
}
}
match搜索可以设置operator参数,该参数决定文档按照分词后的词集合进行“与”还是“或”匹配。在默认情况下,该参数的值为“或”关系,即operator的值为or,这也解释了搜索结果中包含部分匹配的文档。如果希望各个词之间的匹配结果是“与”关系,则可以设置operator参数的值为and
查询同时包含 金 和都的文档
GET /hotel/_search
{
"_source": ["title"],
"query": {
"match": {
"title":{
"query": "金都",
"operator":"and" //查询词之间的匹配结果为“与”关系
}
}
}
}
有时搜索多个关键字,关键词和文档在某一个比例上匹配即可,如果使用“与”操作过于严苛,如果使用“或”操作又过于宽松。这时可以采用minimum_should_match参数,该参数叫作最小匹配参数,其值为一个数值,意义为可以匹配上的词的个数。在一般情况下将其设置为一个百分数,因为在真实场景中并不能精确控制具体的匹配数量。以下示例设置最小匹配为80%的文档:
GET /hotel/_search
{
"_source": ["title"],
"query": {
"match": {
"title": { //match搜索条件
"query": "金都",
"operator": "or",
"minimum_should_match": "80%" //设置最小匹配度为80%
}
}
}
}
multi_match查询
有时用户需要在多个字段中查询关键词,除了使用布尔查询封装多个match查询之外,可替代的方案是使用multi_match。可以在multi_match的query子句中组织数据匹配规则,并在fields子句中指定需要搜索的字段列表。
在title和amenities两个字段中同时搜索“假日”关键词
GET /hotel/_search
{
"_source": ["title","amenities"],
"query": {
"multi_match": {
"query": "假日", //匹配关键字为“假日”
"fields": [ //设置匹配的字段为title和amenities
"title",
"amenities"
]
}
}
}
match_phrase查询
match_phrase用于匹配短语,与match查询不同的是,match_phrase用于搜索确切的短语或邻近的词语。假设在酒店标题中搜索“文雅酒店”,希望酒店标题中的“文雅”与“酒店”紧邻并且“文雅”在“酒店”前面,则使用match_phrase查询的DSL如下:
GET /hotel/_search
{
"query": {
"match_phrase": {
"title": "文雅酒店"
}
}
}
使用match_phrase进行查询时,ES将查询文本“精选酒店”切分为“精选”“酒店”,“文雅”匹配时命中了文档001和文档005,但是“酒店”匹配时要求“酒店”必须在“文雅”之后并且索引位置和“文雅”之差为1,而文档001符合匹配要求但是文档005不符合要求。如果需要文档005也命中上述查询,则可以设置match_phrase查询的slop参数,它用来调节匹配词之间的距离阈值。下面的DSL将slop设置为2
GET /hotel/_search
{
"query": {
"match_phrase": {
"title": {
"query": "文雅酒店",
"slop": 2 //将“文雅”和“酒店”之间的最大匹配距离设置为2
}
}
}
}
基于地理位置查询
支持两种类型的地理查询:一种是地理点(geo_point),即经纬度查询,另一种是地理形状查询(geo_shape),即支持点、线、圆形和多边形查询等。
对应于geo_point字段类型的查询方式有3种,分别为geo_distance查询、geo_bounding_box查询和geo_polygon。
geo_distance查询方式需要用户指定一个坐标点,在指定距离该点的范围后,ES即可查询到相应的文档。假设北京天安门的经纬度为[116.4039,39.915143],以下为使用geo_distance查询所找到的天安门5km范围内的酒店:
GET /hotel/_search
{
"_source": [ //只返回部分字段
"title",
"city",
"location"
],
"query": {
"geo_distance": {
"distance": "5km", //设置距离范围为5km
"location": { //设置中心点经纬度
"lat": "39.915143", //设置纬度
"lon": "116.4039" //设置经度
}
}
}
}
geo_bounding_box查询提供的是矩形内的搜索,需要用户给出左上角的顶点地理坐标和右下角的顶点地理坐标。假设定义国贸商圈为一个矩形,其左上角顶点的经纬度为[116.457044,39.922821],右下角顶点的经纬度为[116.479466,39.907104]
GET /hotel/_search
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": { //设置左上角的顶点坐标
"lat": "39.922821",
"lon": "116.457044"
},
"bottom_right": { //设置右下角的顶点坐标
"lat": "39.907104",
"lon": "116.479466"
}
}
}
}
}
geo_polygon比geo_bounding_box提供的地理范围功能更加灵活,它支持多边形内的文档搜索,使用该查询需要提供多边形所有顶点的地理坐标。假设北京地坛公园商圈的地形为三角形,该三角形的三个顶点的经纬度分别为[116.417088,39.959829]、[116.432035,39.960272]和[116.421399,39.965802]
GET /hotel/_search
{
"query": {
"geo_polygon": {
"location": {
"points": [
{ //设置三角形的第1个顶点坐标
"lat": "39.959829",
"lon": "116.417088"
},
{ //设置三角形的第2个顶点坐标
"lat": "39.960272",
"lon": "116.432035"
},
{ //设置三角形的第3个顶点坐标
"lat": "39.965802",
"lon": "116.421399"
}
]
}
}
}
}
搜索建议
ES中的Completion Suggester是比较合适的。为了使用Completion Suggester,其对应的字段类型需要定义为completion类型。在以下示例中定义了一个酒店搜索建议的索引:
PUT /hotel_sug
{
"mappings": {
"properties": {
"query_word": { //定义query_word字段,类型为completion
"type": "completion"
}
}
}
}
POST /_bulk
{"index":{"_index":"hotel_sug","_id":"001"}}
{"query_word": "如家酒店"}
{"index":{"_index":"hotel_sug","_id":"002"}}
{"query_word": "如家快捷酒店"}
{"index":{"_index":"hotel_sug","_id":"003"}}
{"query_word": "如家精选酒店"}
{"index":{"_index":"hotel_sug","_id":"004"}}
{"query_word": "汉庭假日酒店"}
假设用户输入“如家”关键词,需要ES给出前缀为该词的酒店查询词
GET /hotel_sug/_search
{
"suggest": {
"hotel_zh_sug": { //定义搜索建议名称
"prefix": "如家", //设置搜索建议的前缀
"completion": { //设置搜索建议对应的字段名称
"field": "query_word"
}
}
}
}
在上述查询中,hotel_zh_sug定义的是搜索建议的名称,prefix定义的是用户输入的关键词,completion.field定义的是搜索建议的候选集对应的字段名称。
7.3按字段值排序
在默认情况下,ES对搜索结果是按照相关性降序排序的
ES提供了sort子句可以对数据进行排序。使用sort子句一般是按照字段信息进行排序,不受相关性影响,而且打分步骤需要耗费一定的硬件资源和时间,因此默认情况下,不对文档进行打分。使用sort排序分为两种类别,一种是按照字段值的大小进行排序,另一种是按照给定地理坐标的距离远近进行排序。
按普通字段值排序
使用sort子句对字段值进行排序时需要指定排序的字段。ES默认是按照字段值进行升序排序,可以设置order参数为asc或desc,指定按照字段值进行升序或者降序排序。以下示例为搜索名称包含“金都”的酒店,并对酒店按照价格进行降序排列。
GET /hotel/_search
{
"_source": [ //只返回部分字段
"title",
"price"
],
"query": { //搜索条件
"match": {
"title": "金都"
}
},
"sort": [
{ //按照价格降序排列
"price": {
"order": "desc"
}
}
]
}
默认情况下ES查询时使用sort对结果排序是不计算分数的。也可以使用sort对搜索结果按照多个字段进行排序。例如,用户可以按照价格进行降序排列,然后再按照口碑值进行降序排列
GET /hotel/_search
{
"_source": [ //返回部分字段
"title",
"price",
"praise"
],
"query": { //查询条件
"match": {
"title": "金都"
}
},
"sort": [
{
"price": { //按照价格进行降序排列
"order": "desc"
},
"praise": { //按照口碑进行降序排列
"order": "desc"
}
}
]
}
按地理距离排序
前面我们介绍了ES提供的基于地理位置的查询功能,使用geo_distance查询,配合sort可以指定另一种排序规则,即按照文档坐标与指定坐标的距离对结果进行排序。使用时,需要在sort内部指定排序名称为geo_distanc,并指定目的地坐标。除了可以指定升序或者降序排列外,还可以指定排序结果中sort子句中的距离的计量单位,默认值为km即千米。在进行距离计算时,系统默认使用的算法为arc,该算法的特点是计算精准但是耗费时间较长,用户可以使用distance_type参数选择另一种计算速度快但经度略差的算法,名称为plane。如下示例使用geo_distance查询天安门5km范围内的酒店,并按照距离由近及远进行排序
GET /hotel/_search
{
"_source": [ //返回部分字段
"title",
"city",
"location"
],
"query": {
"geo_distance": {
"distance": "5km", //设置地理范围为5km
"location": { //设置中心点坐标
"lat": "39.915143",
"lon": "116.4039"
}
}
},
"sort": [ //设置排序逻辑
{
"_geo_distance": {
"location": { //设置排序的中心点坐标
"lat": "39.915143",
"lon": "116.4039"
},
"order": "asc", //按距离由近到远进行排序
"unit": "km", //排序所使用的距离的计量单位
"distance_type": " plane " //排序所使用的距离计算算法
}
}
]
}
8聚合
定义酒店索引,存在的话先删除DELETE /hotel
PUT /hotel
{
"settings": {
"number_of_shards": 1 //指定主分片个数为1
},
"mappings": {
"properties": {
"title": { //定义title字段的类型为text
"type": "text"
},
"city": { //定义city字段的类型为keyword
"type": "keyword"
},
"price": { //定义price字段的类型为double
"type": "double"
},
"create_time": { //定义create_time字段的类型为date
"type": "date"
},
"full_room": { //定义full_room字段的类型为boolean
"type": "boolean"
},
"location": { //定义location字段的类型为geo_point
"type": "geo_point"
},
"tags": { //定义tags字段的类型为keyword
"type": "keyword"
},
"comment_info": { //定义comment_info字段的类型为object
"properties": {
"favourable_comment": { //定义favourable_comment字段的类型为integer
"type": "integer"
},
"negative_comment": { //定义negative_comment字段的类型为integer
"type": "integer"
}
}
}
}
}
}
添加数据
POST /_bulk
{"index":{"_index":"hotel","_id":"001"}}
{"title": "文雅酒假日酒店","city":"北京","price": 556.00,"create_time":"20200418120000","full_room":true,"location":{"lat": 39.938838,"lon": 116.449112}, "tags":["wifi","小型电影院"],"comment_info":{"favourable_comment":20,"negative_comment":10}}
{"index":{"_index":"hotel","_id":"002"}}
{"title": "金都嘉怡假日酒店","city":"北京","create_time":"20210315200000","full_room":false,"location":{"lat": 39.915153,"lon": 116.4030},"tags": ["wifi","免费早餐"],"comment_info":{"favourable_comment":20,"negative_ comment":10}}
{"index":{"_index":"hotel","_id":"003"}}
{"title": "金都假日酒店","city":"北京","price": 200.00,"create_time":"20210509160000","full_room":true,"location":{"lat": 40.002096,"lon": 116.376673},"comment_info":{"favourable_comment":20,"negative_comment":10}}
{"index":{"_index":"hotel","_id":"004"}}
{"title": "金都假日酒店","city":"天津","price": 500.00,"create_time":"20210218080000","full_room":false,"location":{"lat":39.155004,"lon": 117.203976},"tags":["wifi","免费车位"]}
{"index":{"_index":"hotel","_id":"005"}}
{"title": "文雅精选酒店","city":"天津","price": 800.00,"create_time":"20210101080000","full_room":true,"location":{"lat": 39.178447,"lon": 117.219999},"tags":["wifi","充电车位"],"comment_info":{"favourable_comment" :20,"negative_comment":10}}
8.1聚合指标
常见的统计指标
计算平均值
当使用avg子句进行平均值的聚合时,可以在avg子句中指定聚合的字段。在默认情况下,查询将匹配所有文档,如果不需要返回匹配的文档信息,最好将返回的文档个数设置为0。这样既可以让结果看起来更整洁,又可以提高查询速度
GET /hotel/_search
{
"size": 0,
"aggs": {
"my_agg": { //聚合名称
"avg": {
"field": "price" //计算文档的平均价格
}
}
}
}
如果聚合的指标字段不是ES的基本类型,例如object类型,则可以使用点运算符进行引用
GET /hotel/_search
{
"size": 0,
"aggs": {
"my_agg": {
"avg": {
//使用点运算符引用object类型字段的数据
"field": "comment_info.favourable_comment"
}
}
}
}
与平均值类似,最大值、最小值及加和值分别使用max、min和sum子句进行聚合
为了避免多次请求,ES还提供了stats聚合。stats聚合可以将对应字段的最大值、最小值、平均值及加和值一起计算并返回计算结果
GET /hotel/_search
{
"size": 0,
"aggs": {
"my_agg": {
"stats": { //使用stats运算符计算多个指标
"field": "price"
}
}
}
}
空值处理
使用value_count聚合统计了price字段中非空值的个数
GET /hotel/_search
{
"size": 0,
"aggs": {
"my_agg": {
"value_count": { //统计price字段中非空值的个数
"field": "price"
}
}
}
}
如果判断的字段是数组类型,则value_count统计的是符合条件的所有文档中该字段数组中非空元素个数的总和,而不是数组的个数总和
GET /hotel/_search
{
"size": 0,
"aggs": {
"my_agg": {
"value_count": { //统计tags字段数组中非空元素的个数
"field": "tags"
}
}
}
}
如果需要以空值字段的数据作为聚合指标对其进行聚合,可以在指标统计中通过missing参数指定填充值对空值进行填充。以下示例演示了对price字段进行聚合,并设定了当字段值为空值时使用100进行替代的查询请求
GET /hotel/_search
{
"size": 0,
"aggs": {
"my_agg": {
"sum": {
"field": "price",
"missing":100 //计算加和值时将price字段中的空值用100代替
}
}
}
}
8.2桶聚合
单维度桶聚合
匹配的方式有terms、filter和ranges等。本节只介绍比较有代表性的terms查询和ranges查询,对其他匹配方式感兴趣读者可以阅读相关文档进行学习,这里不再赘述。
terms聚合是按照字段的实际完整值进行匹配和分组的,它使用的维度字段必须是keyword、bool、keyword数组等适合精确匹配的数据类型,因此不能对text字段直接使用terms聚合,如果对text字段有terms聚合的需求,则需要在创建索引时为该字段增加多字段功能
因为ES支持多桶聚合,所以每个桶聚合需要定义一个名字,此处定义了一个桶聚合,名字为my_agg。在这个桶聚合中使用了一个terms聚合,聚合字段选择了城市,目的是统计各个城市的酒店的文档个数。在聚合外面,因为不希望返回任何文档,所以指定查询返回的文档为0
在默认情况下,进行桶聚合时如果不指定指标,则ES默认聚合的是文档计数
GET /hotel/_search
{
"size": 0,
"aggs": {
"my_agg": {
"terms": { //按照城市进行聚合
"field": "city"
}
}
}
}
GET /hotel/_search
{
"size": 0,
"aggs": {
"my_agg": {
"terms": {
"field": "full_room" //按照满房状态进行聚合
}
}
}
}
ranges聚合也是经常使用的一种聚合。它匹配的是数值字段,表示按照数值范围进行分组。用户可以在ranges中添加分组,每个分组用from和to表示分组的起止数值。注意该分组包含起始数值,不包含终止数值
GET /hotel/_search
{
"size": 0,
"aggs": {
"my_agg": {
"range": {
"field": "price",
"ranges": [ //多个范围桶
{
"to": 200 //不指定from,默认from为0
},
{
"from": 200,
"to": 500
},
{
"from": 500 //不指定to,默认to为该字段最大值
}
]
}
}
}
}
有时还需要对单维度桶指定聚合指标,聚合指标单独使用子aggs进行封装,该aggs子句的使用方式和上一节介绍的聚合指标相同。以下请求表示按照城市维度进行聚合,统计各个城市的平均酒店价格
GET /hotel/_search
{
"size": 0,
"aggs": {
"my_agg": { //单维度聚合名称
"terms": { //定义单维度桶
"field": "city"
},
"aggs": { //用于封装单维度桶下的聚合指标
"my_sum": { //聚合指标名称
"sum": { //对price字段进行加和
"field": "price",
"missing": 200
}
}
}
}
}
}
多维度桶嵌套聚合
统计各个城市的满房和非满房状态下的酒店平均价格。ES支持嵌套桶聚合,进行嵌套时,可以使用aggs子句进行子桶的继续嵌套,指标放在最里面的子桶内
GET /hotel/_search
{
"size": 0,
"aggs": {
"group_city": { //多维度桶名称
"terms": {
"field": "city"
},
"aggs": { //单维度桶
"group_full_room": {
"terms": {
"field": "full_room"
},
"aggs": { //聚合指标
"my_sum": {
"avg": {
"field": "price",
"missing": 200
}
}
}
}
}
}
}
}
地理距离聚合
给定了一个地理位置,此处使用ranges聚合对距离该位置的酒店划分了3个分组的桶:第1个桶为3km范围内;第2个桶为3~10km;第3个桶为大于等于10km
GET /hotel/_sear
{
"size": 0,
"aggs": {
"my_agg": {
"geo_distance": {
"field": "location",
"origin": { //指定聚合的中心点经纬度
"lat": 39.915143,
"lon": 116.4039
},
"unit": "km", //指定聚合时的距离计量单位
"ranges": [ //指定每一个聚合桶的距离范围
{
"to": 3
},
{
"from": 3,
"to":10
},
{
"from": 10
}
]
}
}
}
}
可以指定聚合指标进行地理距离聚合,下面的DSL将按照bucket分桶聚合酒店的最低价格
GET /hotel/_search
{
"size": 0,
"aggs": {
"my_agg": {
"geo_distance": { //按地理距离聚合
"field": "location",
"origin": { //指定中心点经纬度坐标
"lat": 39.915143,
"lon": 116.4039
},
"unit": "km",
"ranges": [ //指定距离范围桶
{
"to": 3
},
{
"from": 3,
"to": 10
},
{
"from": 10
}
]
},
"aggs": { //指定聚合指标
"my_min": { //聚合指标名称
"min": { //计算每个桶内price字段的最小值
"field": "price",
"missing": 100
}
}
}
}
}
}
8.3聚合方式
ES支持灵活的聚合方式,它不仅支持聚合和查询相结合,而且还可以使聚合的过滤条件不影响搜索条件,并且还支持在聚合后的结果中进行过滤筛选。本节将介绍这些聚合方式。
直接聚合
直接聚合指的是聚合时的DSL没有query子句,是直接对索引内的所有文档进行聚合。8.2介绍的示例都属于直接聚合
先查询再聚合
与直接聚合相对应,这种查询方式需要增加query子句,query子句和普通的query查询没有区别,参加聚合的文档必须匹配query查询
GET /hotel/_search
{
"size": 0,
"query": { //指定查询query逻辑
"term": {
"city": {
"value": "北京"
}
}
},
"aggs": { //指定聚合逻辑
"my_agg": {
"avg": {
"field": "price"
}
}
}
}
前过滤器
有时需要对聚合条件进一步地过滤,但是又不能影响当前的查询条件。例如用户进行酒店搜索时的搜索条件是天津的酒店,但是聚合时需要将非满房的酒店平均价格进行聚合并展示给用户。此时不能变更用户的查询条件,需要在聚合子句中添加过滤条件
GET /hotel/_search
{
"size": 0,
"query": { //指定查询的query逻辑
"term": {
"city": {
"value": "天津"
}
}
},
"aggs": {
"my_agg": {
"filter": { //指定过滤器逻辑
"term": {
"full_room": false
}
},
"aggs": { //指定聚合逻辑
"my_avg": {
"avg": {
"field": "price"
}
}
}
}
}
}
后过滤器
使用match匹配title中包含“假日”的酒店,并且查询出这些酒店的平均价格,最后使用post_filter设置后过滤器的条件,将酒店的城市锁定为“北京”
GET /hotel/_search
{
"size": 0,
"query": { //指定查询的query逻辑
"match": {
"title": "假日"
}
},
"post_filter": { //指定后过滤器逻辑
"term": {
"city": "北京"
}
},
"aggs": { //指定聚合逻辑
"my_agg": {
"avg": {
"field": "price",
"missing":200
}
}
}
}
8.4聚合排序
根据前面的介绍可知,ES对于聚合结果的默认排序规则有时并非是我们期望的。可以使用ES提供的sort子句进行自定义排序,有多种排序方式供用户选择:可以按照聚合后的文档计数的大小进行排序;可以按照聚合后的某个指标进行排序;还可以按照每个组的名称进行排序
按文档计数排序
按照城市的酒店平均价格进行聚合,并按照聚合后的文档计数进行升序排列的请求
GET /hotel/_search
{
"size": 0,
"aggs": {
"group_city": {
"terms": {
"field": "city",
"order": { //按照文档计数进行升序排列
"_count": "asc"
}
},
"aggs": {
"my_avg": {
"avg": { //使用价格平均值作为聚合指标
"field": "price",
"missing": 200
}
}
}
}
}
}
按聚合指标排序
按照城市的酒店平均价格进行聚合,并按照聚合后的平均价格进行升序排列
GET /hotel/_search
{
"size": 0,
"aggs": {
"group_city": {
"terms": {
"field": "city",
"order": { //按照聚合指标进行升序排列
"my_avg": "asc"
}
},
"aggs": {
"my_avg": { //定义聚合指标
"avg": {
"field": "price",
"missing": 200
}
}
}
}
}
}
按照城市的酒店平均价格进行聚合,并按照聚合后的分组名称进行升序排列
GET /hotel/_search
{
"size": 0,
"aggs": {
"group_city": {
"terms": {
"field": "city",
"order": { //按照分组key的自然顺序升序排列
"_key": "asc"
}
},
"aggs": {
"my_avg": { //定义聚合指标
"avg": {
"field": "price",
"missing": 200
}
}
}
}
}
}
8.5聚合分页
Top hits聚合
搜索“金都”时,如果希望按照城市分组,每组按照匹配分数降序展示3条文档数据
GET /hotel/_search
{
"size": 0,
"query": {
"match": {
"title": "金都"
}
},
"aggs": {
"group_city": { //按照城市进行桶聚合
"terms": {
"field": "city"
},
"aggs": {
"my_avg": {
"top_hits": { //指定返回每个桶的前3个文档
"size": 3
}
}
}
}
}
}
Collapse聚合
当在索引中有大量数据命中时,Top hits聚合存在效率问题,并且需要用户自行排序。针对上述问题,ES推出了Collapse聚合,即用户可以在collapse子句中指定分组字段,匹配query的结果按照该字段进行分组,并在每个分组中按照得分高低展示组内的文档。当用户在query子句外指定from和size时,将作用在Collapse聚合之后,即此时的分页是作用在分组之后的
GET /hotel/_search
{
"from": 0, //指定分页的起始位置
"size": 5, //指定每页返回的数量
"query": { //指定查询的query逻辑
"match": {
"title": "金都"
}
},
"collapse": { //指定按照城市进行Collapse聚合
"field": "city"
}
}
Linux相关命令
查看各个目录下的大小
du -sh /opt/liuy/*
Linux后台启动Kibana
nohup ./bin/kibana --allow-root &
参考文献:
Elasticsearch搜索引擎构建入门与实战---高印会
Elasticsearch: 权威指南 | Elastichttps://www.elastic.co/guide/cn/elasticsearch/guide/current/index.htmlData in: documents and indices | Elasticsearch Guide [7.17] | Elastichttps://www.elastic.co/guide/en/elasticsearch/reference/7.17/documents-indices.html