【SpringCloud学习笔记】Elasticsearch

1. Elasticsearch

1.1 安装ES

  1. 启动Docker:service docker restart / systemctl restart docker
  2. 基于Docker创建网络docker network create hm-net
  3. 向云服务器上传elasticsearch以及kibana的tar包,并使用docker load -i xxx.tar进行加载
  4. 使用如下命令启动es:
docker run -d \
  --name es \
  -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  -e "discovery.type=single-node" \
  -v es-data:/usr/share/elasticsearch/data \
  -v es-plugins:/usr/share/elasticsearch/plugins \
  --privileged \
  --network hm-net \
  -p 9200:9200 \
  -p 9300:9300 \
  elasticsearch:7.12.1
  1. 使用如下命令启动kibana:
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=hm-net \
-p 5601:5601  \
kibana:7.12.1
  1. 使用docker logs -f es / kibana可查看运行日志:

image.png

  1. 验证在浏览器中访问:9200以及5601端口能观察到如下现象证明启动成功:

image.pngimage.png

1.2 初识Elasticsearch

1.2.1 简介

官方网站:https://www.elastic.co/cn/elasticsearch

**Elasticsearch:**是一个高性能的搜索引擎,它是ELK技术栈的一部分:

  • Logstash / Beats:用于数据收集
  • Elasticsearch:用于数据的存储、搜索、计算
  • Kibana:用于数据可视化(内置Devtools等高效工具)

**应用场景:**搜索引擎技术在日常生活中非常常见,例如:

  1. github等网站的高亮搜索关键词
  2. 百度/搜狗等搜索引擎
  3. 各大网站支持的模糊查询(关键字匹配)
  4. 打车软件地理位置搜索

1.2.2 倒排索引

ES的高性能得益于其底层实现的倒排索引
倒排索引中有两个重要概念:

  • 文档(Document):一条数据就是一个文档,例如商品记录、用户记录都是一个文档
  • 词条(Item):对于一个文档中的内容或者用户搜索句使用某种算法,基于语义划分得到的一组词就是词条,例如我喜欢学习Java,就可以分成:“我”、“喜欢”、“学习”、"Java"这些词条

倒排索引构建流程:
假设此时正向索引为:

idtitleprice
1小米手机4999
2华为手机3999
3华为手表2999
4小米汽车199999

此时每一条数据就是一个文档,我们可以对文档中的title内容构建倒排索引:

  1. 使用某种分词算法将title进行分词,例如"小米手机"就可以得到"小米"、"手机"两个词条
  2. 以词条作为键,文档id列表作为值
  3. 如果词条已经出现过,那么就在文档列表末尾追加当前文档,如果该词条没有出现过,则构建一个新的键值对,键为当前词条,值为当前文档id

当对上述四条数据分词结束后倒排索引为:

词条文档id列表
小米[1, 4]
手机[1, 2]
华为[2, 3]
手表[3]
汽车[4]

倒排索引工作流程:

  1. 此时当用户搜索关键词为"小米手表",使用相同的分词算法得出词条"小米"、“手表”
  2. 然后就会查询倒排索引,小米对应的文档id有1、4, 手表对应的文档有3
  3. 然后再根据正排索引查询id对应的具体文档内容

1.2.3 IK分词器

1.2.3.1 基本使用

我们已经了解到ES工作原理需要借助一定的分词算法进行分词匹配,那么就需要一个字典(dict)来保存一些常用的词语,分词算法才可以判断某一个序列是否可以作为一个词条。
而ES标准的分词器对于中文分词支持力度不大!例如我们尝试在devtools发起如下分词请求:

POST /_analyze
{
  "analyzer": "standard",
  "text": "我喜欢学习Java"
}

image.png
可以观察到"standard"模式下的响应结果并非是我们想得到的!这个时候我们就需要使用到IK分词器了
安装步骤:

  1. 方式一:使用在线安装的方式,输入以下命令:
# 在线安装
docker exec -it es ./bin/elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
# 重启docker es服务
docker restart es
  1. 方式二:离线安装,将下载好的ik分词压缩包挂载在数据卷中
# 查看所有的数据卷位置
docker volume ls 
# 查看docker es-plugins数据卷位置
docker volume inspect es-plugins
# cd xxx
# 上传ik压缩包
# 重启docker es服务
docker restart es

此时再次发起请求,结果如下:
image.png

1.2.3.2 设置扩展词以及停用词

**扩展词:**我们需要自定义一些单词放在字典当中,例如"白嫖"、“米饭论坛”
**停用词:**在中文中一些语气词比如啊、哦、了是不需要进行分词的,可以剔除
前面我们提到IK分词器需要依赖字典,该配置文件就可以在ik文件夹目录中的/config/IKAnalyzer.cfg.xml进行配置扩展词以及暂停词的文件image.png
我们进行上述配置:然后重启es: docker restart es此后es就会读取同级目录下的ext.dic获取扩展字典当中的内容,读取stopwords.dic获取停用词列表
image.png
证明拓展词以及停用词配置成功!

1.2.4 索引库操作

  1. 创建索引库和映射:

请求方式:PUT
请求路径:/索引库名(自己定义)
请求参数:mapping映射

PUT /索引库名
{
  "mappings": {
    "properties": {
      "字段名": {
        "type": "text",
        "index": true,
        "analyzer": "ik_smart"
      },
      "字段名": {
        "type": object,
        "properties": {
          "字段名": {
            "type": boolean
          }
        }
      }
    }
  }
}
  1. 查询索引库:

请求方式:GET
请求路径:/索引库名
请求参数:无

GET /索引库名
  1. 删除索引库:

请求方式:DELETE
请求路径:/索引库名
请求参数:无

DELETE /索引库名
  1. 修改索引库

修改索引结构在es中是不被允许的,因此如果修改了字段结构会导致倒排索引重建,开销巨大,但是我们可以新增字段
请求方式:PUT
请求路径:/索引库名/_mapping

PUT /索引库名/_mapping
{
  "properties": {
    "新字段名": {}
  }
}

索引库操作总结:

  1. 新增:PUT /索引库名
  2. 查询:GET /索引库名
  3. 修改 PUT /索引库名/_mapping
  4. 删除:DELETE /索引库名

1.2.5 文档操作

1.3 Java客户端

1.3.1 Java客户端初始化

上述我们实践了借助Kibana发送请求,但是我们还是需要学习使用Java客户端编程的方式实现:

  1. 在pom文件中引入RestHignLevelClient依赖
<!-- 引入es依赖 -->
<dependency>
  <groupId>org.elasticsearch.client</groupId>
  <artifactId>elasticsearch-rest-high-level-client</artifactId>
  <version>7.12.1</version>
</dependency>
  1. 初始化RestHignLevelClient对象
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class EsDemoApplicationTests {

	private RestHighLevelClient client;

	@BeforeEach
	public void init() {
		client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://129.211.6.176:9200")));
	}

	@Test
	public void testClient() {
		System.out.println(client);
	}
}

1.3.2 创建映射以及索引库

原先我们在Kibana中使用如下HTTP请求配置映射信息以及索引库:

PUT /user
{
  "mappings": {
    "properties": {
      "desc": {
        "type": "text",
        "index": "true",
        "analyzer": "ik_smart"
      },
      "name": {
        "type": "text",
        "index": "false"
      },
      "age": {
        "type": "integer",
        "index": "false"
      }
    }
  }
}

而在Java代码中我们需要通过以下的方式来创建:

/**
 * 测试创建索引库
 */
@Test
public void testCreateIndex() throws IOException {
    // 1. 创建Request对象
    CreateIndexRequest request = new CreateIndexRequest("user");
    // 2. 配置携带参数
    request.source(MAPPING_TEMPLATE, XContentType.JSON);
    // 3. 发起请求
    client.indices().create(request, RequestOptions.DEFAULT);
}

1.3.3 查看索引库

原先我们在Kibana中使用如下HTTP请求查看索引库:

GET /user
/**
 * 测试获取索引库信息
 */
@Test
public void testGetIndex() throws IOException {
    // 1. 创建request对象
    GetIndexRequest request = new GetIndexRequest("user");
    // 2. 发起请求
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    System.out.println("exists: " + exists);
}

1.3.4 删除索引库

原先我们在Kibana中使用如下HTTP请求删除索引库:

DELETE /user
/**
 * 测试删除索引库
 */
@Test
public void testDeleteIndex() throws IOException {
    // 1. 创建request对象
    DeleteIndexRequest request = new DeleteIndexRequest("user");
    // 2. 发起请求
    client.indices().delete(request, RequestOptions.DEFAULT);
}

1.3.5 新增文档

原先我们在Kibana中使用如下HTTP请求增加文档:

# 插入文档
POST /user/_doc/1
{
  "desc": "测试描述",
  "name": "ricejson",
  "age": 20
}
/**
 * 新增文档
 */
@Test
public void testCreateDocument() throws IOException {
    // 1. 创建request
    IndexRequest request = new IndexRequest("user").id("1");
    // 创建用户对象
    User user1 = new User("测试描述", "ricejson", 20);
    Gson gson = new Gson();
    String userStr = gson.toJson(user1);
    // 2. 填写内容
    request.source(userStr, XContentType.JSON);
    // 3. 发起请求
    client.index(request, RequestOptions.DEFAULT);
}

1.3.6 查看文档

原先我们在Kibana中使用如下HTTP请求查看文档:

GET /user/_doc/1
/**
 * 获取文档
 */
@Test
public void testGetDocument() throws IOException {
    // 1. 创建request
    GetRequest request = new GetRequest("user", "1");
    // 2. 发起请求
    GetResponse resp = client.get(request, RequestOptions.DEFAULT);
    // 3. 获取返回内容
    String source = resp.getSourceAsString();
    System.out.println(source);
}

1.3.7 删除文档

原先我们在Kibana中使用如下HTTP请求删除文档:

DELETE /user/_doc/1
/**
 * 删除文档
 */
@Test
public void deleteDocument() throws IOException {
    // 1. 创建request对象
    DeleteRequest request = new DeleteRequest("user", "1");
    // 2. 发起删除文档请求
    client.delete(request, RequestOptions.DEFAULT);
}

1.3.8 修改文档

全量修改:与新增文档一致
局部修改:

POST /user/_update/1
{
  "doc": {
    "name": "米饭好好吃"
  }
}
/**
 * 局部更新文档
 */
@Test
public void updateDocument() throws IOException {
    // 1. 创建request对象
    UpdateRequest request = new UpdateRequest("user", "1");
    // 2. 设置更新内容
    request.doc("name", "米饭好好吃");
    // 3. 发起请求
    client.update(request, RequestOptions.DEFAULT);
}

1.3.9 批处理

/**
 * 测试批处理操作
 */
@Test
public void testBatch() throws IOException {
    // 1. 创建request对象
    BulkRequest bulkRequest = new BulkRequest();
    // 2. 添加多个request操作
    // 创建用户对象
    User user = new User("测试描述", "ricejson", 20);
    Gson gson = new Gson();
    String userStr = gson.toJson(user);
    bulkRequest.add(new IndexRequest("user").id("1").source(userStr, XContentType.JSON));
    bulkRequest.add(new IndexRequest("user").id("2").source(userStr, XContentType.JSON));
    // 3. 发起批处理请求
    client.bulk(bulkRequest, RequestOptions.DEFAULT);
}

1.4 DSL语句

1.4.1 快速入门

现在我们需要设计一个商品表,其中MySQL存储如下字段内容:
id: int 编号
name: varchar(20) 名称
brand:varchar(20) 品牌
price: int 价格
desc: varchar(100) 描述内容

# 定义以及新增商品索引库
PUT /goods
{
  "mappings": {
    "properties": {
      "id": {
        "type": "integer"
      },
      "name": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "brand": {
        "type": "keyword"
      },
      "price": {
        "type": "integer"
      },
      "desc": {
        "type": "text",
        "analyzer": "ik_smart"
      }
    }
  }
}

现在插入几条模拟数据:

POST /goods/_doc/1
{
  "id": 1,
  "name": "超级智能手表",
  "brand": "FutureTech",
  "price": 1299,
  "desc": "这款超级智能手表拥有最新的健康监测功能,能够实时追踪您的心率和睡眠质量。"
}

POST /goods/_doc/2
{
  "id": 2,
  "name": "无线蓝牙耳机",
  "brand": "AudioMaster",
  "price": 499,
  "desc": "享受无线自由,这款无线蓝牙耳机提供卓越的音质和长达24小时的续航能力。"
}

POST /goods/_doc/3
{
  "id": 3,
  "name": "便携式咖啡机",
  "brand": "CoffeeCraft",
  "price": 699,
  "desc": "随时随地享受新鲜咖啡,这款便携式咖啡机设计精巧,操作简便,是咖啡爱好者的理想选择。"
}

POST /goods/_doc/4
{
  "id": 4,
  "name": "智能扫地机器人",
  "brand": "CleanRobotics",
  "price": 2999,
  "desc": "智能扫地机器人,自动导航,高效清洁,为您节省宝贵的时间。"
}

POST /goods/_doc/5
{
  "id": 5,
  "name": "多功能运动相机",
  "brand": "ActionCam",
  "price": 1499,
  "desc": "这款多功能运动相机防水防尘,适合各种极限运动场景,记录您的每一个精彩瞬间。"
}

搜索索引库中全部的文档内容语法如下:

GET /goods/_search
{
  "query": {
    "match_all": {}
  }
}

1.4.2 叶子查询

常见的叶子查询主要有如下两类:

  • 全量查询:要求查询条件必须是可以分词的
  • 精确查询
1.4.2.1 全量查询

其中全量查询语法格式如下:

# 全量查询
GET /goods/_search
{
  "query": {
    "match": {
      "name": "智能"
    }
  }
}

除此以外我们还可以指定查询多个字段:

# 全量查询(查询多个字段)
GET /goods/_search
{
  "query": {
    "multi_match": {
      "query": "您",
      "fields": ["name", "desc"]
    }
  }
}
1.4.2.2 精确查询

精确查询有如下两种常见方式:

  1. term:词条完全匹配
  2. range:范围查询,比如price >= 1000 以及 price <= 1999
# 精确查询(词条查询)
GET /goods/_search
{
  "query": {
    "term": {
      "brand": "ActionCam"
    }
  }
}
# 精确查询(范围查询)
GET /goods/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 1499,
        "lte": 2999
      }
    }
  }
}

1.4.3 复合查询

bool查询:
将多个叶子查询经过mustshouldmust_notfilter等与或非操作合并成一个复杂查询:

# 复合查询(查询name中包含机器人并且价格在1499-2999之间)
GET /goods/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "机器人"
          }
        }
      ],
      "filter": [
        {
          "range": {
            "price": {
              "gte": 1499,
              "lte": 2999
            }
          }
        }
      ]
    }
  } 
}

1.4.4 排序+分页

排序:
我们还可以在query的同级目录下加入order: {"排序字段": "asc|desc"}

  • ASC:表示正序
  • DESC:表示倒序
# 排序
GET /goods/_search
{
  "query": {
    "match_all": {}
  },
  "sort": {
    "price": "asc"
  }
}

分页:
我们还可以在query的同级目录下加入"from": x, "size": x

  • from:表示从哪一条数据开始
  • size:表示单页的数量
# 分页
GET /goods/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 3
}

1.4.5 高亮显示

想必大家都遇到过以下场景:
image.png
即百度搜索返回的结果中,与关键字匹配的结果都会高亮(红色)显示,打开开发者工具能够发现被高亮的部分由标签进行包括并使用CSS语法标红醒目。
那么问题就出现了,这个标签到底是由前端分析生成的还是由后端返回的?

  • 前端:如果让前端进行处理,都需要对查询内容进行分词,然后在内容中进行匹配,但是这样一来性能开销就非常大,前端一般只做数据展示,数据处理一般让后端处理
  • 后端:事实上ES在进行倒排索引的构建过程中,会保存查询分词在文档中的位置,然后在查询词前后加上等标签返回给前端。✔

DSL添加高亮显示:

# 高亮显示
GET /goods/_search
{
  "query": {
    "match": {
      "name": "智能"
    }
  },
  "highlight": {
    "fields": {
      "name": {
        "pre_tags": "<em>",
        "post_tags": "</em>"
      }
    }
  }
}

1.5 Java客户端操作DSL

1.5.1 快速入门

我们现在想要查询出索引库为goods下全部的文档内容,与之匹配的DSL语句如下:

GET /goods/_search
{
  "query": {
    "match_all": {}
  }
}

相对应的java代码如下:

/**
 * 测试查询全部文档
 */
@Test
public void testMatchAll() throws IOException {
    // 1. 创建request
    SearchRequest request = new SearchRequest("goods");
    // 2. 添加query查询条件
    request.source()
            .query(QueryBuilders.matchAllQuery());
    // 3. 发起请求获取响应
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    // 4. 解析响应
    // 4.1 获取到hits
    SearchHits searchHits = resp.getHits();
    // 4.2 获取到数据条数
    TotalHits hitsCount = searchHits.getTotalHits();
    // 4.3 获取到数据数组
    SearchHit[] hitsHits = searchHits.getHits();
    // 4.3 逐个解析
    for (SearchHit searchHit : hitsHits) {
        // 4.4 获取source数据
        String source = searchHit.getSourceAsString();
        System.out.println(source);
    }
}

1.5.2 叶子查询

前面我们已经介绍过叶子查询主要有如下几类:

  1. 全量查询
    1. match:单字段匹配
    2. match_all:多字段匹配
  2. 精确查询
    1. term:根据词条内容匹配
    2. range:根据范围匹配

需求1:现在我们需要找出desc中包含"这款"的文档
对应的DSL语句如下:

GET /goods/_search
{
  "query": {
    "match": {
      "desc": "这款"
    }
  }
}

对应的Java代码如下:

    /**
     * 查询desc中包含"这款"的内容
     */
    @Test
    public void testMatch() throws IOException {
        // 1. 创建request对象
        SearchRequest request = new SearchRequest("goods");
        // 2. 设置查询条件
        request.source()
                .query(QueryBuilders.matchQuery("desc", "这款"));
        // 3. 发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.1 获取到hits
        SearchHits hits = response.getHits();
        // 4.2 获取到数据总条数
        TotalHits totalHits = hits.getTotalHits();
        // 4.3 获取到数据集合
        SearchHit[] searchHits = hits.getHits();
        // 4.4 逐条解析
        for (SearchHit searchHit : searchHits) {
            String source = searchHit.getSourceAsString();
            System.out.println(source);
        }
    }

需求2:现在我们需要查询desc中包含"相机"并且name中包含"相机"的文档
对应的DSL语句如下:

GET /goods/_search
{
  "query": {
    "multi_match": {
      "query": "相机",
      "fields": ["name", "desc"]
    }
  }
}

对应的Java代码如下:

/**
 * 查询desc并且name中都包含"相机"的文档
 */
@Test
public void testMultiMatch() throws IOException {
    // 1. 创建request
    SearchRequest request = new SearchRequest("goods");
    // 2. 添加查询条件
    request.source()
            .query(QueryBuilders.multiMatchQuery("相机", "name", "desc"));
    // 3. 发起请求获取响应
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.1 获取到hits
    SearchHits hits = response.getHits();
    // 4.2 获取到数据总数
    TotalHits totalHits = hits.getTotalHits();
    // 4.3 获取全部查询文档
    SearchHit[] searchHits = hits.getHits();
    // 4.4 解析每条数据
    for (SearchHit searchHit : searchHits) {
        // 4.5 获取source数据
        String source = searchHit.getSourceAsString();
        System.out.println(source);
    }

需求3:现在我们需要精确查找手机品牌为"ActionCam"的文档
对应的DSL语句如下:

GET /goods/_search
{
  "query": {
    "term": {
      "brand": "ActionCam"
    }
  }
}

对应的Java代码如下:

/**
 * 查询手机品牌为"ActionCam"的文档
 */
@Test
public void testTerm() throws IOException {
    // 1. 创建request对象
    SearchRequest request = new SearchRequest("goods");
    // 2. 设置查询条件
    request.source()
            .query(QueryBuilders.termQuery("brand", "ActionCam"));
    // 3. 发起请求获取响应
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.1 获取hits
    SearchHits hits = response.getHits();
    // 4.2 获取数据总数
    TotalHits totalHits = hits.getTotalHits();
    // 4.3 获取全部匹配文档
    SearchHit[] searchHits = hits.getHits();
    // 4.4 解析出每一条数据
    for (SearchHit searchHit : searchHits) {
        // 4.5 获取source
        String source = searchHit.getSourceAsString();
        System.out.println(source);
    }

需求4:现在我们需要查询价格高于1999的文档
对应的DSL语句如下:

GET /goods/_search
{
  "query": {
    "range": {
      "price": {
        "gt": 1999
      }
    }
  }
}

对应的Java代码如下:

/**
 * 查询价格高于1499的文档
 */
@Test
public void testRange() throws IOException {
    // 1. 创建request
    SearchRequest request = new SearchRequest("goods");
    // 2. 设置查询条件
    request.source()
            .query(QueryBuilders.rangeQuery("price").gt(1499));
    // 3. 发起请求,获取响应
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.1 获取hits
    SearchHits hits = response.getHits();
    // 4.2 获取数据总数
    TotalHits totalHits = hits.getTotalHits();
    // 4.3 获取数据全集
    SearchHit[] searchHits = hits.getHits();
    // 4.4 解析每条数据
    for (SearchHit searchHit : searchHits) {
        // 4.5 获取source
        String source = searchHit.getSourceAsString();
        System.out.println(source);
    }
}

1.5.3 复合查询

需求:查询价格低于2999并且name中包含"耳机"并且品牌为"AudioMaster"的文档

GET /goods/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "耳机"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "brand": "AudioMaster"
          }
        },
        {
          "range": {
            "price": {
              "lt": 2999
            }
          }
        }
      ]
    }
  }
}

对应的Java代码如下:

/**
 * 查询价格低于2999并且name中包含"耳机"并且品牌为"AudioMaster"的文档
 */
@Test
public void testBool() throws IOException {
    // 1. 创建request
    SearchRequest request = new SearchRequest("goods");
    // 2. 设置查询条件
    request.source()
            .query(QueryBuilders.boolQuery()
                    .must(QueryBuilders.matchQuery("name", "耳机"))
                    .filter(QueryBuilders.termQuery("brand", "AudioMaster"))
                    .filter(QueryBuilders.rangeQuery("price").lt(2999)));
    // 3. 发起请求,获取响应
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    process(response);
}

1.5.4 分页+排序查询

需求:查询全部文档,按照价格从低到高,分页为第一页,三条数据

GET /goods/_search
{
  "query": {
    "match_all": {}
  },
  "sort": {
    "price": "ASC"
  },
  "from": 1,
  "size": 3
}

对应的Java代码如下:

/**
 * 查询全部文档,按照价格从低到高,分页为第一页,三条数据
 */
@Test
public void testPageAndSort() throws IOException {
    // 1. 创建request
    SearchRequest request = new SearchRequest("goods");
    // 2. 设置查询条件
    request.source()
            .query(QueryBuilders.matchAllQuery())
            .sort("price", SortOrder.ASC)
            .from(0)
            .size(3);
    // 3. 发起请求,获取响应
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    process(response);
}

1.5.5 高亮显示

DSL高亮语法:

GET /goods/_search
{
  "query": {
    "match": {
      "name": "相机"
    }
  },
  "highlight": {
    "fields": {
      "name": {
        "pre_tags": "<em>",
        "post_tags": "</em>"
      }
    }
  }
}

Java客户端实现:

/**
 * 测试高亮
 */
@Test
public void testHighlight() throws IOException {
    // 1. 创建request
    SearchRequest request = new SearchRequest("goods");
    // 2. 设置查询条件
    request.source()
            .query(QueryBuilders.matchQuery("name", "相机"));
    // 3. 设置高亮条件
    request.source().highlighter(new HighlightBuilder()
            .field("name")
            .preTags("<em>")
            .postTags("</em>"));
    // 4. 进行查询
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 5. 进行解析
    SearchHits hits = response.getHits();
    SearchHit[] searchHits = hits.getHits();
    for (SearchHit searchHit : searchHits) {
        Map<String, HighlightField> hlfs = searchHit.getHighlightFields();
        HighlightField hlf = hlfs.get("name");
        String value = hlf.getFragments()[0].string();
        System.out.println(value);
    }
}

1.5.6 聚合

1.5.6.1 聚合分类

在DSL中,聚合可以分为如下几类:

  1. 桶(Buckets)聚合
    1. Terms Aggregation: 词条聚合
    2. Date 日期时间聚合
  2. 度量(Metric)聚合
    1. min:最小值
    2. max:最大值
    3. avg:平均值
    4. stats:统计最小值、最大值、平均值
  3. 管道(Pipeline)聚合

基于别的聚合再次聚合

1.5.6.2 聚合DSL语句

聚合三要素:

  1. 聚合名称
  2. 聚合类型
  3. 聚合字段

聚合DSL语法:

GET /goods/_search
{
  "size": 0,
  "aggs": {
    "brand_agg": {
      "terms": {
        "field": "brand",
        "size": 5
      }
    }
  }
}
1.5.6.3 Java客户端操作聚合DSL语句
/**
 * 测试聚合DSL
 */
@Test
public void testAggregation() throws IOException {
    // 1. 创建request对象
    SearchRequest request = new SearchRequest("goods");
    // 2. 编写查询条件
    request.source().query(QueryBuilders.matchAllQuery());
    // 2.1 设置分页
    request.source().size(0);
    // 2.2 设置聚合三要素
    request.source()
            .aggregation(AggregationBuilders
                    .terms("brand_agg")
                    .field("brand")
                    .size(5));
    // 3. 查询结果
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4. 解析结果
    Aggregations aggregations = response.getAggregations();
    Terms terms = aggregations.get("brand_agg");
    List<? extends Terms.Bucket> buckets = terms.getBuckets();
    for (Terms.Bucket bucket : buckets) {
        System.out.println("key: " + bucket.getKeyAsString());
        System.out.println("count: " + bucket.getDocCount());
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/678964.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Qt实现文本文件的读写操作

文章目录 文本读写简介QFileDialog简介常用方法示例代码 QFile简介常用方法示例代码 QTextStream简介常用方法示例代码 结合使用示例完整示例代码(读写操作&#xff0c;可直接复制运行我使用的Qt版本为QT5.14)mainwindow.hmainwindow.cppmain.cpp代码解释 文本读写简介 在现代…

k8s Pods漂移时间配置

默认为300秒 apiVersion: apps/v1 kind: Deployment metadata:name: my-test spec:replicas: 1selector:matchLabels:app: my-apptemplate:metadata:labels:app: my-appspec:containers:- name: my-containerimage: nginx:latestports:- containerPort: 80tolerations:- key: &…

MySQL之查询性能优化(四)

查询性能优化 MySQL客户端/服务器通信协议 一般来说&#xff0c;不需要去理解MySQL通信协议的内部实现细节&#xff0c;只需要大致理解通信协议是如何工作的。MySQL客户端和服务器之间的通信协议是"半双工"的&#xff0c;这意味着&#xff0c;在任何一个时刻&#…

History 模式和 Hash 模式路由的区别、优缺点及在开发生产环境中的注意事项

在现代单页应用&#xff08;SPA&#xff09;开发中&#xff0c;前端路由是至关重要的一部分。常见的路由模式有两种&#xff1a;History 模式和 Hash 模式。本文将详细探讨这两种模式的区别、优缺点&#xff0c;并在开发和生产环境中的注意事项。 路由模式简介 Hash 模式 H…

.Net 基于.Net8开发的一个Asp.Net Core Webapi后端框架

1.项目结构 该项目是基于.net8开发的Asp.Net Core WebApi后端服务,集成了Efcore,Autofac,Jwt,AutoMapper,Serilog,Quartz,MiniExcel等组件。该框架简单易上手&#xff0c;没有额外的学习成本; 该项目采用了多层结构设计&#xff0c;有利于解耦&#xff0c;包含公共层&#xff0…

机器视觉——硬件常用基础知识

光源 机器视觉中光源的作用 1&#xff09;强化特征&#xff0c;弱化背景 2&#xff09;光源打得好&#xff0c;图好了&#xff0c;后期算法更简化 3&#xff09;图好了&#xff0c;测试速度更高 各种光源的综合性能对比及为啥使用LED灯 光的颜色的选择 白色光&#xff1a;通常用…

【分享】两种方法禁止修改Word文档

对于比较重要的Word文件&#xff0c;不想被随意编辑修改&#xff0c;可以试试以下两个方法&#xff0c;不清楚的小伙伴&#xff0c;一起来看看吧&#xff01; 方法1&#xff1a;设置“只读方式” 我们可以给Word文档设置以“只读方式”打开&#xff0c;这样就算编辑修改了文档…

企业内部聊天软件Riot部署

ubuntu docker 简介 Riot(原Vector)是使用Matrix React SDK构建的Matrix网络聊天客户端,开源免费,功能丰富,支持私人对话,团队对话,语言视频对话,上传文件,社区互动。支持在聊天界面添加各种有趣的插件,比如RSS等各种机器人、虚拟币实时监控等。并且所有通过Riot传…

63. UE5 RPG 兼容没有武器的普通攻击

前面&#xff0c;我们实现了近战攻击技能&#xff0c;敌人通过AI靠近玩家&#xff0c;并且通过AI还能够触发近战攻击的释放。现在我们思考一个问题&#xff0c;如果敌人没有武器&#xff0c;攻击的手段是用的双手&#xff0c;我们该如何去获取它的攻击范围。 现在实现的一套是获…

什么情况!伯克希尔哈撒韦股票跌近100%

KlipC报道&#xff1a;当地时间6月3日开盘后&#xff0c;纽约证劵交易所的一个技术问题导致沃伦巴菲特(Warren Buffett)旗下伯克希尔哈撒韦公司(Berkshire Hathaway)A类股票似乎下跌了近100%&#xff0c;达到185.1美元/股。目前&#xff0c;所有受影响的股票已经恢复交易。 除了…

OLED_讲解

OLED&#xff08;有机发光二极管 Organic Light-Emitting Diode&#xff09;是一种显示技术&#xff0c;它使用有机化合物材料作为发光层&#xff0c;当通过电流时&#xff0c;这些材料会发光。OLED显示器由多个单独可控制的OLED像素组成&#xff0c;每个像素都能够发出红、绿、…

【异常分析:四分位距与3σ原则】

文章目录 前言四分位距&#xff08;IQR&#xff09;3σ原则使用步骤计算四分位距应用3σ原则 代码 前言 异常分析的目标是识别数据中的异常值&#xff0c;这些异常值可能是由于错误的记录、设备故障或者其他未知原因导致的。四分位距&#xff08;interquartile range, IQR&…

目标检测数据集 - 海洋垃圾检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;海洋垃圾检测数据集&#xff0c;真实拍摄海洋海底场景高质量垃圾检测图片数据&#xff0c;涉及场景丰富&#xff0c;比如海底塑料垃圾数据、海底铁制品罐状垃圾数据、海底纸张垃圾数据、海洋生物和海底垃圾同框数据、海底探索仪器和海底垃圾同框数据、海…

前端localForage存储数据使用教程

前言 前端本地化存储算是一个老生常谈的话题了&#xff0c;我们对于 cookies、Web Storage&#xff08;sessionStorage、localStorage&#xff09;的使用已经非常熟悉&#xff0c;在面试与实际操作之中也会经常遇到相关的问题&#xff0c;但这些本地化存储的方式还存在一些缺陷…

iPhone录音的m4a格式音频文件怎么转成MP3格式?

在日常工作生活中&#xff0c;我们有时会用电脑或手机录音&#xff0c;比如iPhone录音的M4A格式音频&#xff0c;要上传至某些软件或者平台使用&#xff0c;但是有时这些平台或者软件对音频格式有要求&#xff0c;比如有的就只能上传mp3格式的文件。 这个时候我们就需要先将音频…

在Three.js中实现模型点击高亮:整合EffectComposer与OutlinePass的终极指南

效果【后期实现鼠标点击选中轮廓后给出一个弹窗显示相应的模型信息】 标签指示线参考我的上一篇文章 引言 Three.js不仅让WebGL的3D图形编程变得简单易懂&#xff0c;还通过其强大的扩展库支持丰富的后期处理效果&#xff0c;为3D场景增添无限魅力。本篇文章将引导您深入了…

客户案例|Zilliz Cloud 助力点石科技转型 AI 智能服务商

福建点石科技网络科技有限公司成立于2010年&#xff0c;是国家高新技术企业&#xff0c;阿里云、蚂蚁金服等大厂海内外生态合作伙伴ISV。在餐饮、零售、酒店、旅游、商圈的行业定制化服务化上有深厚积累&#xff0c;在境内外做了大量标杆性软件项目&#xff0c;如东南亚RWS圣淘…

详解智慧互联网医院系统源码:开发医院小程序教学

本篇文章&#xff0c;笔者将详细介绍智慧互联网医院系统的源码结构&#xff0c;并提供开发医院小程序的详细教学。 一、智慧互联网医院系统概述 智慧互联网医院系统涵盖了预约挂号、在线咨询、电子病历、药品管理等多个模块。 二、系统源码结构解析 智慧互联网医院系统的源码…

【软件测试】自动化测试如何管理测试数据

前言 在之前的自动化测试框架相关文章中&#xff0c;无论是接口自动化还是UI自动化&#xff0c;都谈及data模块和config模块&#xff0c;也就是测试数据和配置文件。 随着自动化用例的不断增加&#xff0c;需要维护的测试数据也会越来越多&#xff0c;维护成本越来越高&#…

MySQL Hints:控制查询优化器的选择

码到三十五 &#xff1a; 个人主页 MySQL Hints是优化数据库查询性能的一种强大工具。它们允许开发者在SQL查询中嵌入指令&#xff0c;以影响MySQL优化器的决策过程。在某些情况下&#xff0c;优化器可能无法选择最佳的查询执行计划&#xff0c;这时我们可以使用Hints来引导优化…