Elasticsearch快速入门
Elasticsearch是由elastic公司开发的一套搜索引擎技术,它是elastic技术栈中的一部分,提供核心的数据存储、搜索、分析功能
elasticsearch之所以有如此高性能的搜索表现,正是得益于底层的倒排索引技术。那么什么是倒排索引呢?
Elasticsearch搜索原理
正向索引
我们先来回顾一下正向索引。
例如有一张名为tb_goods
的表:
id | title | price |
---|---|---|
1 | 小米手机 | 3499 |
2 | 华为手机 | 4999 |
3 | 华为小米充电器 | 49 |
4 | 小米手环 | 49 |
... | ... | ... |
其中的id
字段已经创建了索引,由于索引底层采用了B+树结构,因此我们根据id搜索的速度会非常快。但是其他字段例如title
,只在叶子节点上存在。检查到搜索条件为like '%手机%'
,如果符合则放入结果集,不符合则丢弃。
综上,根据id精确匹配时,可以走索引,查询效率较高。而当搜索条件为模糊匹配时(模糊查询只有%在关键词前面索引才会失效),由于索引无法生效,导致从索引查询退化为全表扫描,效率很差。
因此,正向索引适合于根据索引字段的精确搜索,不适合基于部分词条的模糊匹配。而倒排索引恰好解决的就是根据部分词条模糊匹配的问题。
倒排索引
倒排索引中有两个非常重要的概念:
-
文档(
Document
):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息 -
词条(
Term
):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条
创建倒排索引是对正向索引的一种特殊处理和应用,流程如下:
-
将每一个文档的数据利用分词算法根据语义拆分,得到一个个词条
-
创建表,每行数据包括词条、词条所在文档id、位置等信息
-
因为词条唯一性,可以给词条创建正向索引
词条(索引) | 文档id |
---|---|
小米 | 1,3,4 |
手机 | 1,2 |
华为 | 2,3 |
充电器 | 3 |
手环 | 4 |
倒排索引的搜索流程如下(以搜索"华为手机"为例),如图
流程描述:
1)用户输入条件"华为手机"
进行搜索。
2)对用户输入条件分词,得到词条:华为
、手机
。
3)拿着词条在倒排索引中查找(由于词条有索引,查询效率很高),即可得到包含词条的文档id:1、2、3
。
4)拿着文档id
到正向索引中查找具体文档即可(由于id
也有索引,查询效率也很高)。
虽然要先查询倒排索引,再查询正排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。
Elasticsearch安装
本项目采用docker部署
创建网络 es-net
docker network create es-net
安装 elasticsearch
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 es-net \-p 9200:9200 \-p 9300:9300 \
elasticsearch:7.12.1
访问:http://服务器id:9200/ 若出现以下JSON数据,表示安装成功
kibana安装
Kibana是elastic公司提供的用于操作Elasticsearch的可视化控制台。它的功能非常强大,包括:
-
对Elasticsearch数据的搜索、展示
-
对Elasticsearch数据的统计、聚合,并形成图形化报表、图形
-
对Elasticsearch的集群状态监控
-
它还提供了一个开发控制台(DevTools),在其中对Elasticsearch的Restful的API接口提供了语法提示
部署Kibana
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601 \
kibana:7.12.1
Kibana连接的是Elasticsearch的REST API端口,而在同一Docker网络中,端口映射并不适用,容器之间直接通过内部端口相互通信(9200端口,指向的是内部9200端口,不是对外暴露的9200端口)
访问:http://服务器id:5601/,出现以下界面表示安装成功
在开发工具中就可以执行DSL操作了
IK分词器
Elasticsearch的关键就是倒排索引,而倒排索引依赖于对文档内容的分词,而分词则需要高效、精准的分词算法,IK分词器就是这样一个中文分词算法。
IK分词器的安装
下载IK分词器
https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.12.1.zip
查看es-plugins插件容器所在位置
docker volume inspect es-plugins
将ik分词器解压后,上传至服务器容器es-plugins所在位置
重启es服务
docker restart es
进入开发工具界面,对 “java是全世界最好的语言,没有之一”,进行分词
#测试分词器
POST /_analyze
{"text": "java是全世界最好的语言,没有之一", "analyzer": "ik_smart"
}
ik分词器安装成功
IK分词器的执行模式
IK分词器包含两种模式:
-
ik_smart
:智能语义切分 -
ik_max_word
:最细粒度切分
扩展词典
打开IK分词器config目录,在IKAnalyzer.cfg.xml配置文件内容添加
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties><comment>IK Analyzer 扩展配置</comment><!--用户可以在这里配置自己的扩展字典 --><entry key="ext_dict"></entry><!--用户可以在这里配置自己的扩展停止词字典--><entry key="ext_stopwords"></entry><!--用户可以在这里配置远程扩展字典 --><!-- <entry key="remote_ext_dict">words_location</entry> --><!--用户可以在这里配置远程扩展停止词字典--><!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
在config目录下创建这两个文件,exi.dic和stopwords.dic
扩展分词"最好的语言"
禁用分词“的”
重启es容器 ,可以看到“最好的语言”已经可以被识别为是一个分词了
索引库操作
-
type
:字段数据类型,常见的简单类型有:-
字符串:
text
(可分词的文本)、keyword
(精确值,例如:品牌、国家、ip地址) -
数值:
long
、integer
、short
、byte
、double
、float
、 -
布尔:
boolean
-
日期:
date
-
对象:
object
-
-
index
:是否创建索引,默认为true
-
analyzer
:使用哪种分词器 -
properties
:该字段的子字段
我们以下面这段JSON数据为例,我们为这段数据创建索引库
email:字符串,但是不分词,不创建索引
score:只看数组中元素类型
id:java中id为Lone,而在es中,所有的id默认为字符串
创建索引库
#创建索引库,es中id默认为字符串
PUT /es_test
{"mappings": {"properties": {"id":{"type":"keyword" },"email":{"type": "keyword","index": false},"info":{ "type": "text","analyzer": "ik_smart"},"score":{"type": "float"},"name":{"type": "object", "properties": {"firstName": {"type":"keyword"},"lastName":{"type":"keyword"}}}}}
}
若需要同时根据多个字段搜索,推荐把这些字段复制到统一的一个字段中,分词查询,效率更高
创建一个统一字段all
"all":{"type":"text","analyzer":"ik_max_word" }
其他需要参与搜索的字段,复制到all中
"copy_to": "all"
综上:创建索引方案如下
#创建索引库
PUT /es_test
{"mappings": {"properties": {"email":{"type": "keyword","index": false},"info":{ "type": "text","analyzer": "ik_smart", "copy_to": "all"},"score":{"type": "float","copy_to": "all"},"name":{"type": "object", "properties": {"firstName": {"type":"keyword"},"lastName":{"type":"keyword"}}},"all":{"type":"text","analyzer":"ik_max_word" }}}
}
修改索引库
倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping。
虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。因此修改索引库能做的就是向索引库中添加新字段,或者更新索引库的基础属性。
修改索引库,新增新字段age
PUT /es_test/_mapping
{"properties": {"age":{"type": "integer"}}
}
查看索引库
GET /es_test
删除索引库
DELETE /es_test
文档操作
新增文档
新增文档:POST /索引库名/_doc/文档id
POST /es_test/_doc/1
{"email": "3111871135@qq.om","info": "java_爱好者","age":23,"score":[98.5,88.3],"name": {"firstName": "张","lastName": "三"}
}
修改文档
修改有两种方式:
-
全量修改:直接覆盖原来的文档
-
局部修改:修改文档中的部分字段
全量修改
全量修改是覆盖原来的文档,其本质是两步操作:
-
根据指定的id删除文档
-
新增一个相同id的文档
#修改文档-全量修改
PUT /es_test/_doc/1
{"info": "java是最好的语言","email": "....","name": {"firstName": "李","lastName": "四"}
}
局部修改
局部修改是只修改指定id匹配的文档中的部分字段
#修改文档-局部修改
POST /es_test/_update/1
{"doc": {"email": "ZhaoYun@itcast.cn"}
}
按id查找文档
#查看文档
GET /es_test/_doc/1
批量查找
GET /es_test/_search
删除文档
DELETE /es_test/_doc/1
RestClient
导入依赖
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
覆盖SpringBoot默认的ES版本
<properties><elasticsearch.version>7.12.1</elasticsearch.version></properties>
这里为了单元测试方便,我们创建一个测试类IndexTest
,然后将初始化的代码编写在@BeforeEach
方法中:
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.test.context.SpringBootTest;import java.io.IOException;@SpringBootTest
class HotelIndexTest {private RestHighLevelClient client;@BeforeEachvoid setUp() {client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://124.70.208.223:9200")));}@AfterEachvoid tearDown() throws IOException {client.close();}
}
或者直接采用Spring注入
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class EsConfig {@Beanpublic RestHighLevelClient restHighLevelClient(){return new RestHighLevelClient(RestClient.builder(HttpHost.create("http://124.70.208.223:9200")));}}
索引库操作
创建索引库
那么我们如何将下列MySQL数据存入es中呢?
创建对应es数据,在es中,经度和纬度作为一个字段存储,以“,”隔开
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor
public class HotelDoc {private Long id;private String name;private String address;private Integer price;private Integer score;private String brand;private String city;private String starName;private String business;private String location;private String pic;public HotelDoc(Hotel hotel) {this.id = hotel.getId();this.name = hotel.getName();this.address = hotel.getAddress();this.price = hotel.getPrice();this.score = hotel.getScore();this.brand = hotel.getBrand();this.city = hotel.getCity();this.starName = hotel.getStarName();this.business = hotel.getBusiness();this.location = hotel.getLatitude() + ", " + hotel.getLongitude();this.pic = hotel.getPic();}
}
geo_point
是Elasticsearch 中一种专门用于地理点数据的字段类型。它允许你存储和查询地球上的位置信息,通常以纬度和经度的形式表示。
geo_point
类型支持多种地理空间查询,例如距离查询、多边形查询等,并且可以用于聚合操作来分析地理位置数据。
"location":{"type": "geo_point"},
综上:es中索引库的设置为
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"}}}
}
利用RestHighLevelClient 创建索引库
package cn.itcast.hotel.constants;public class HotelIndexConstants {public static final String MAPPING_TEMPLATE = "{\n" +" \"mappings\": {\n" +" \"properties\": {\n" +" \"id\": {\n" +" \"type\": \"keyword\"\n" +" },\n" +" \"name\": {\n" +" \"type\": \"text\",\n" +" \"analyzer\": \"ik_max_word\",\n" +" \"copy_to\": \"all\"\n" +" },\n" +" \"address\": {\n" +" \"type\": \"keyword\",\n" +" \"index\": false\n" +" },\n" +" \"price\": {\n" +" \"type\": \"integer\"\n" +" },\n" +" \"score\": {\n" +" \"type\": \"integer\"\n" +" },\n" +" \"brand\": {\n" +" \"type\": \"keyword\",\n" +" \"copy_to\": \"all\"\n" +" },\n" +" \"city\": {\n" +" \"type\": \"keyword\"\n" +" },\n" +" \"starName\": {\n" +" \"type\": \"keyword\"\n" +" },\n" +" \"business\": {\n" +" \"type\": \"keyword\",\n" +" \"copy_to\": \"all\"\n" +" },\n" +" \"pic\": {\n" +" \"type\": \"keyword\",\n" +" \"index\": false\n" +" },\n" +" \"location\": {\n" +" \"type\": \"geo_point\"\n" +" },\n" +" \"all\": {\n" +" \"type\": \"text\",\n" +" \"analyzer\": \"ik_max_word\"\n" +" }\n" +" }\n" +" }\n" +"}";
}
@Testvoid testCreateIndex() throws IOException {// 1.准备Request PUT /hotelCreateIndexRequest request = new CreateIndexRequest("hotel");// 2.准备请求参数request.source(MAPPING_TEMPLATE, XContentType.JSON);// 3.发送请求client.indices().create(request, RequestOptions.DEFAULT);}
删除索引库
@Testvoid testDeleteIndex() throws IOException {// 1.准备RequestDeleteIndexRequest request = new DeleteIndexRequest("hotel");// 3.发送请求client.indices().delete(request, RequestOptions.DEFAULT);}
判断索引库是否存在
@Testvoid testExistsIndex() throws IOException {// 1.准备RequestGetIndexRequest request = new GetIndexRequest("hotel");// 3.发送请求boolean isExists = client.indices().exists(request, RequestOptions.DEFAULT);System.out.println(isExists ? "存在" : "不存在");}
文档操作
新增文档
@Testvoid testAddDocument() throws IOException {// 1.查询数据库hotel数据Hotel hotel = hotelService.getById(61083L);// 2.转换为HotelDocHotelDoc hotelDoc = new HotelDoc(hotel);// 3.转JSONString json = JSON.toJSONString(hotelDoc);// 1.准备RequestIndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());// 2.准备请求参数DSL,其实就是文档的JSON字符串request.source(json, XContentType.JSON);// 3.发送请求client.index(request, RequestOptions.DEFAULT);}
查看指定文档
@Testvoid testGetDocumentById() throws IOException {// 1.准备Request // GET /hotel/_doc/{id}GetRequest request = new GetRequest("hotel", "61083");// 2.发送请求GetResponse response = client.get(request, RequestOptions.DEFAULT);// 3.解析响应结果String json = response.getSourceAsString();HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);System.out.println("hotelDoc = " + hotelDoc);}
删除指定文档
@Testvoid testDeleteDocumentById() throws IOException {// 1.准备Request // DELETE /hotel/_doc/{id}DeleteRequest request = new DeleteRequest("hotel", "61083");// 2.发送请求client.delete(request, RequestOptions.DEFAULT);}
更新指定文档
@Testvoid testUpdateById() throws IOException {// 1.准备RequestUpdateRequest request = new UpdateRequest("hotel", "61083");// 2.准备参数request.doc("price", "870");// 3.发送请求client.update(request, RequestOptions.DEFAULT);}
批量添加文档
@Testvoid testBulkRequest() throws IOException {// 查询所有的酒店数据List<Hotel> list = hotelService.list();// 1.准备RequestBulkRequest request = new BulkRequest();// 2.准备参数for (Hotel hotel : list) {// 2.1.转为HotelDocHotelDoc hotelDoc = new HotelDoc(hotel);// 2.2.转jsonString json = JSON.toJSONString(hotelDoc);// 2.3.添加请求request.add(new IndexRequest("hotel").id(hotel.getId().toString()).source(json, XContentType.JSON));}// 3.发送请求client.bulk(request, RequestOptions.DEFAULT);}
DSL查询
Elasticsearch的查询可以分为两大类:
-
叶子查询(Leaf query clauses):一般是在特定的字段里查询特定值,属于简单查询,很少单独使用。
-
复合查询(Compound query clauses):以逻辑方式组合多个叶子查询或者更改叶子查询的行为方式。
无条件查询的类型是:match_all,由于match_all无条件,所以条件位置不写即可。因此其查询语句如下
查询所有
查询所有-完整形式
GET /hotel/_search
{"query": {"match_all": {}}
}
查询所有-简写形式
GET /hotel/_search
@Testvoid testMatchAll() throws IOException {// 1.准备requestSearchRequest request = new SearchRequest("hotel");// 2.准备请求参数request.source().query(QueryBuilders.matchAllQuery());// 3.发送请求,得到响应SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.结果解析handleResponse(response);}
private void handleResponse(SearchResponse response) {SearchHits searchHits = response.getHits();// 4.1.总条数long total = searchHits.getTotalHits().value;System.out.println("总条数:" + total);// 4.2.获取文档数组SearchHit[] hits = searchHits.getHits();// 4.3.遍历for (SearchHit hit : hits) {// 4.4.获取sourceString json = hit.getSourceAsString();// 4.5.反序列化,非高亮的HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);// 4.7.打印System.out.println(hotelDoc);}}
虽然是match_all,但是响应结果中并不会包含索引库中的所有文档,而是仅有10条。这是因为处于安全考虑,elasticsearch设置了默认的查询页数。
全文检索查询
GET /{索引库名}/_search
{"query": {"match": {"字段名": "搜索条件"}}
}
GET /{索引库名}/_search
{"query": {"multi_match": {"query": "搜索条件","fields": ["字段1", "字段2"]}}
}
@Testvoid testMatch() throws IOException {// 1.准备requestSearchRequest request = new SearchRequest("hotel");// 2.准备请求参数// request.source().query(QueryBuilders.matchQuery("all", "外滩如家"));request.source().query(QueryBuilders.multiMatchQuery("外滩如家", "name", "brand", "city"));// 3.发送请求,得到响应SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.结果解析handleResponse(response);}
精确查询
range
是范围查询,对于范围筛选的关键字有:
-
gte
:大于等于 -
gt
:大于 -
lte
:小于等于 -
lt
:小于
GET /{索引库名}/_search
{"query": {"range": {"字段名": {"gte": {最小值},"lte": {最大值}}}}
}
@Testvoid testBool() throws IOException {// 1.准备requestSearchRequest request = new SearchRequest("hotel");// 2.准备请求参数/*BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();// 2.1.mustboolQuery.must(QueryBuilders.termQuery("city", "杭州"));// 2.2.filterboolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));*/request.source().query(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("city", "杭州")).filter(QueryBuilders.rangeQuery("price").lte(250)));// 3.发送请求,得到响应SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.结果解析handleResponse(response);}
地理查询
查询该地点附近5公里内的所以酒店
GET /hotel/_search
{"query": {"geo_distance":{"distance":"4km","location":"31.25,121.47"}}
}
@Testvoid testDistance() throws IOException {// 1.准备requestSearchRequest request = new SearchRequest("hotel");GeoDistanceQueryBuilder geoDistanceQuery = QueryBuilders.geoDistanceQuery("location").point(31.25, 121.47) // 纬度, 经度.distance("4km");request.source().query(geoDistanceQuery);// 3.发送请求,得到响应SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.结果解析handleResponse(response);}
算分函数查询
当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。
从elasticsearch5.1开始,采用的相关性打分算法是BM25算法,公式如下:
基于这套公式,就可以判断出某个文档与用户搜索的关键字之间的关联度,还是比较准确的。但是,在实际业务需求中,常常会有竞价排名的功能。不是相关度越高排名越靠前,而是掏的钱多的排名靠前。
function score 查询中包含四部分内容:
原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)
过滤条件:filter部分,符合该条件的文档才会重新算分
算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
weight:函数结果是常量
field_value_factor:以文档中的某个字段值作为函数结果
random_score:以随机数作为函数结果
script_score:自定义算分函数算法
运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
multiply:相乘
replace:用function score替换query score
其它,例如:sum、avg、max、min
GET /hotel/_search
{"query": {"function_score": {"query": { .... }, // 原始查询,可以是任意条件"functions": [ // 算分函数{"filter": { // 满足的条件,品牌必须是Iphone"term": {"brand": "Iphone"}},"weight": 10 // 算分权重为2}],"boost_mode": "multipy" // 加权模式,求乘积}}
}
- 文档 A 的原始得分为
x
,因为它的品牌是"Iphone"
,所以它的最终得分将是x * 10
。 - 文档 B 的原始得分为
y
,因为它不符合filter
条件,所以它的最终得分仍然是y
,不会受到weight
的影响。
bool查询
bool查询,即布尔查询。就是利用逻辑运算来组合一个或多个查询子句的组合。bool查询支持的逻辑运算有:
must:必须匹配每个子查询,类似“与”
should:选择性匹配子查询,类似“或”
must_not:必须不匹配,不参与算分,类似“非”
filter:必须匹配,不参与算分
GET /items/_search
{"query": {"bool": {"must": [{"match": {"name": "手机"}}],"should": [{"term": {"brand": { "value": "vivo" }}},{"term": {"brand": { "value": "小米" }}}],"must_not": [{"range": {"price": {"gte": 2500}}}],"filter": [{"range": {"price": {"lte": 1000}}}]}}
}
出于性能考虑,与搜索关键字无关的查询尽量采用must_not或filter逻辑运算,避免参与相关性算分。
分页排序
elasticsearch默认是根据相关度算分(_score
)来排序,但是也支持自定义方式对搜索结果排序。不过分词字段无法排序,能参与排序字段类型有:keyword
类型、数值类型、地理坐标类型、日期类型等
elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。
基础分页
elasticsearch中通过修改
from
、size
参数来控制要返回的分页结果:
from
:从第几个文档开始
size
:总共查询几个文档
类似于mysql中的limit ?, ?
GET /items/_search
{"query": {"match_all": {}},"from": 0, // 分页开始的位置,默认为0"size": 10, // 每页文档数量,默认10"sort": [{"price": {"order": "desc"}}]
}
@Testvoid testSortAndPage() throws IOException {int page = 2,size = 5;// 1.准备requestSearchRequest request = new SearchRequest("hotel");// 2.准备请求参数// 2.1.queryrequest.source().query(QueryBuilders.matchAllQuery());// 2.2.排序sortrequest.source().sort("price", SortOrder.ASC);// 2.3.分页 from\sizerequest.source().from((page - 1) * size).size(size);// 3.发送请求,得到响应SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.结果解析handleResponse(response);}
高亮
什么是高亮显示呢?
我们在百度,京东搜索时,关键字会变成红色,比较醒目,这叫高亮显示
观察页面源码,你会发现两件事情:
高亮词条都被加了
<em>
标签
<em>
标签都添加了红色样式
css样式肯定是前端实现页面的时候写好的,但是前端编写页面的时候是不知道页面要展示什么数据的,不可能给数据加标签。而服务端实现搜索功能,要是有elasticsearch
做分词搜索,是知道哪些词条需要高亮的。
因此词条的高亮标签肯定是由服务端提供数据的时候已经加上的。
因此实现高亮的思路就是:
用户输入搜索关键字搜索数据
服务端根据搜索关键字到elasticsearch搜索,并给搜索结果中的关键字词条添加
html
标签前端提前给约定好的
html
标签添加CSS
样式
事实上elasticsearch已经提供了给搜索关键字加标签的语法,无需我们自己编码。
GET /{索引库名}/_search
{"query": {"match": {"搜索字段": "搜索关键字"}},"highlight": {"fields": {"高亮字段名称": {"pre_tags": "<em>","post_tags": "</em>"}}}
}
GET /hotel/_search
{"query": {"match": {"name": "酒店上海"}},"highlight": {"fields": {"name": {"pre_tags": "<em>","post_tags": "</em>"}}}
}
@Testvoid testHighlight() throws IOException {// 1.准备requestSearchRequest request = new SearchRequest("hotel");// 2.准备请求参数// 2.1.queryrequest.source().query(QueryBuilders.matchQuery("all", "外滩如家"));// 2.2.高亮request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));// 3.发送请求,得到响应SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.结果解析handleResponse(response);}private void handleResponse(SearchResponse response) {SearchHits searchHits = response.getHits();// 4.1.总条数long total = searchHits.getTotalHits().value;System.out.println("总条数:" + total);// 4.2.获取文档数组SearchHit[] hits = searchHits.getHits();// 4.3.遍历for (SearchHit hit : hits) {// 4.4.获取sourceString json = hit.getSourceAsString();// 4.5.反序列化,非高亮的HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);// 4.6.处理高亮结果// 1)获取高亮mapMap<String, HighlightField> map = hit.getHighlightFields();// 2)根据字段名,获取高亮结果HighlightField highlightField = map.get("name");// 3)获取高亮结果字符串数组中的第1个元素String hName = highlightField.getFragments()[0].toString();// 4)把高亮结果放到HotelDoc中hotelDoc.setName(hName);// 4.7.打印System.out.println(hotelDoc);}}
相关文章:
Elasticsearch快速入门
Elasticsearch是由elastic公司开发的一套搜索引擎技术,它是elastic技术栈中的一部分,提供核心的数据存储、搜索、分析功能 elasticsearch之所以有如此高性能的搜索表现,正是得益于底层的倒排索引技术。那么什么是倒排索引呢? Elasticsearch…...
【Java数据结构】二叉树相关算法
第一题:获取二叉树中结点个数 得到二叉树结点个数,如果结点为空则返回0,然后再用递归计算左树结点个数根结点(1个)右树结点个数。 public int nodeSize(Node root){if (root null)return 0;return nodeSize1(root.l…...
30分钟内搭建一个全能轻量级springboot 3.4 + 脚手架 <1> 5分钟快速创建一个springboot web项目
快速导航 <1> 5分钟快速创建一个springboot web项目 <2> 5分钟集成好最新版本的开源swagger ui,并使用ui操作调用接口 <3> 5分钟集成好druid并使用druid自带监控工具监控sql请求 <4> 5分钟集成好mybatisplus并使用mybatisplus generator自…...
vue3学习日记6 - Layout
最近发现职场前端用的框架大多为vue,所以最近也跟着黑马程序员vue3的课程进行学习,以下是我的学习记录 视频网址: Day2-17.Layout-Pinia优化重复请求_哔哩哔哩_bilibili 学习日记: vue3学习日记1 - 环境搭建-CSDN博客 vue3学…...
1/14 C++
练习:将图形类的获取周长和获取面积函数设置成虚函数,完成多态 再定义一个全局函数,能够在该函数中实现:无论传递任何图形,都可以输出传递的图形的周长和面积 #include <iostream>using namespace std; class Sh…...
【Uniapp-Vue3】页面生命周期onLoad和onReady
一、onLoad函数 onLoad在页面载入时触发,多用于页面跳转时进行参数传递。 我们在跳转的时候传递参数name和age: 接受参数: import {onLoad} from "dcloudio/uni-app"; onLoad((e)>{...}) 二、onReady函数 页面生命周期函数中的onReady其…...
使用 configparser 读取 INI 配置文件
使用 configparser 读取 INI 配置文件 适合于读取 .ini 格式的配置文件。 配置文件示例 (config.ini): [DEFAULT] host localhost port 3306 [database] user admin password secret import configparser# 创建配置解析器 config configparser.ConfigParser()# 读取配…...
类模板的使用方法
目录 类模板的使用方法 1.类模板语法 2.类模板和函数模板区别 3.类模板中成员函数创建时机 4.类函数对象做函数参数 5.类模板和继承 6.类模板成员函数类外实现 7.类模板分文件编写 person.hpp 实现cpp文件: 8.类模板与友元 9.类模板案例 MyArray.hpp …...
docker mysql5.7如何设置不区分大小写
环境 docker部署,镜像是5.7,操作系统是centos 操作方式 mysql 配置文件是放在 /etc/mysql/mysql.conf.d/mysqld.cnf, vim /etc/mysql/mysql.conf.d/mysqld.cnf lower_case_table_names1 重启mysql容器 验证 SHOW VARIABLES LIKE low…...
Docker与虚拟机的区别及常用指令详解
在现代软件开发中,容器化和虚拟化技术已经成为不可或缺的工具。Docker和虚拟机(VM)是两种常见的技术, 它们都可以帮助开发者在不同的环境中运行应用程序。然而,它们的工作原理和使用场景有很大的不同。本文将详细探讨D…...
C#异步和多线程,Thread,Task和async/await关键字--12
目录 一.多线程和异步的区别 1.多线程 2.异步编程 多线程和异步的区别 二.Thread,Task和async/await关键字的区别 1.Thread 2.Task 3.async/await 三.Thread,Task和async/await关键字的详细对比 1.Thread和Task的详细对比 2.Task 与 async/await 的配合使用 3. asy…...
第一次作业三种方式安装mysql(Windows和linux下)作业
在Windows11上安装sever(服务)端和客户端 server端安装 打开官网MySQL 进入到主页 点击DOWMLOAD 进入下载界面 点击下方MySQL Community (GPL) Downloads 进入社区版mysql下载界面 点击 MySQL Community Server 进入server端下载 选择8.4.3LTS&…...
ubuntu官方软件包网站 字体设置
在https://ubuntu.pkgs.org/22.04/ubuntu-universe-amd64/xl2tpd_1.3.16-1_amd64.deb.html搜索找到需要的软件后,点击,下滑, 即可在Links和Download找到相关链接,下载即可, 但是找不到ros的安装包, 字体设…...
深拷贝与浅拷贝
作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生在读,研究方向无线联邦学习 擅长领域:驱动开发,嵌入式软件开发,BSP开发 作者主页:一个平凡而乐于分享的小比特的个人主页…...
No one knows regex better than me
No one knows regex better than me 代码分析,传了两个参数zero,first,然后$second对两个所传的参数进行了拼接 好比:?zero1&first2 传入后就是: 12 然后对$second进行了正则匹配,匹配所传入的参数是否包含字符串Yeedo|wa…...
scala基础学习(数据类型)-集合
文章目录 集合创建集合isEmpty获取数据添加元素删除元素常见方法交集 &差集 diff --并集 unionto stringto listto Arrayto Map其余常用方法 集合 Scala Set(集合)是没有重复的对象集合,所有的元素都是唯一的。 Scala 集合分为可变的和不可变的集合。 默认情…...
如何使用 Excel 进行多元回归分析?
多元回归分析,一种统计方法,用来探索一个因变量(即结果变量)与多个自变量(即预测变量)之间的关系。广泛用于预测、趋势分析以及因果关系的分析。 听起来这个方法很复杂,但其实在 Excel 中可以很…...
思维转换:突破思维桎梏,创造更高效的工作与生活
在现代职场和生活中,我们经常面临着各种挑战和问题,有时候虽然付出了很多努力,但依然难以找到更有效的解决方案。这时,或许我们需要的不是更多的努力,而是一次“思维转换”。这一概念看似简单,但它背后却蕴…...
ClickHouse-CPU、内存参数设置
常见配置 1. CPU资源 1、clickhouse服务端的配置在config.xml文件中 config.xml文件是服务端的配置,在config.xml文件中指向users.xml文件,相关的配置信息实际是在users.xml文件中的。大部分的配置信息在users.xml文件中,如果在users.xml文…...
Spring Boot 项目启动后自动加载系统配置的多种实现方式
Spring Boot 项目启动后自动加载系统配置的多种实现方式 在 Spring Boot 项目中,可以通过以下几种方式实现 在项目启动完成后自动加载系统配置缓存操作 的需求: 1. 使用 CommandLineRunner CommandLineRunner 是一个接口,可以用来在 Spring…...
scrapy库解决ja3/tls指纹验证问题
pip install curl_cffi0.7.4 pip install scrapy-fingerprint0.1.3seetings.py打开中间件 DOWNLOADER_MIDDLEWARES { "scrapy_fingerprint.fingerprintmiddlewares.FingerprintMiddleware": 100 }yield scrapy.Request(urlurl,callbackself.parse) 改为以下 from sc…...
二进制、八进制、十进制和十六进制的相互转换
printf 函数 printf 函数是 C 语言中用于将格式化的数据输出到标准输出(通常是屏幕)的函数。它位于 stdio.h 头文件中,因此在使用之前需要包含该头文件。 printf 函数的格式说明符 格式说明符说明示例%d 或 %i输出或输入十进制有符号整数p…...
分布式缓存redis
分布式缓存redis 1 redis单机(单节点)部署缺点 (1)数据丢失问题:redis是内存存储,服务重启可能会丢失数据 (2)并发能力问题:redis单节点(单机)部…...
day08_Kafka
文章目录 day08_Kafka课程笔记一、今日课程内容一、消息队列(了解)**为什么消息队列就像是“数据的快递员”?****实际意义**1、产生背景2、消息队列介绍2.1 常见的消息队列产品2.2 应用场景2.3 消息队列中两种消息模型 二、Kafka的基本介绍1、…...
fbx 环境搭建
python 3.7 3.8 版本支持 https://github.com/Shiiho11/FBX-Python-SDK-for-Python3.x 只有python3.7 https://www.autodesk.com/developer-network/platform-technologies/fbx-sdk-2020-3 scipy安装: python安装scipy_scipy1.2.1库怎么安装-CSDN博客 smpl 2 fbx…...
【大数据】机器学习------神经网络模型
一、神经网络模型 1. 基本概念 神经网络是一种模拟人类大脑神经元结构的计算模型,由多个神经元(节点)组成,这些节点按照不同层次排列,通常包括输入层、一个或多个隐藏层和输出层。每个神经元接收来自上一层神经元的输…...
YOLOv5训练长方形图像详解
文章目录 YOLOv5训练长方形图像详解一、引言二、数据集准备1、创建文件夹结构2、标注图像3、生成标注文件 三、配置文件1、创建数据集配置文件2、选择模型配置文件 四、训练模型1、修改训练参数2、开始训练 五、使用示例1、测试模型2、评估模型 六、总结 YOLOv5训练长方形图像详…...
【Vim Masterclass 笔记11】S06L24 + L25:Vim 文本的插入、变更、替换与连接操作同步练习(含点评课)
文章目录 S06L24 Exercise 06 - Inserting, Changing, Replacing, and Joining1 训练目标2 操作指令2.1. 打开 insert-practice.txt 文件2.2. 练习 i 命令2.3. 练习 I 命令2.4. 练习 a 命令2.5. 练习 A 命令2.6. 练习 o 命令2.7. 练习 O 命令2.8. 练习 j 命令2.9. 练习 R 命令2…...
【计算机网络】深入浅出计算机网络
第一章 计算机网络在信息时代的作用 计算机网络已由一种通信基础设施发展成一种重要的信息服务基础设施 CNNIC 中国互联网网络信息中心 因特网概述 网络、互联网和因特网 网络(Network)由若干结点(Node)和连接这些结点的链路…...
HTTP详解——HTTP基础
HTTP 基本概念 HTTP 是超文本传输协议 (HyperText Transfer Protocol) 超文本传输协议(HyperText Transfer Protocol) HTTP 是一个在计算机世界里专门在 两点 之间 传输 文字、图片、音视频等 超文本 数据的 约定和规范 1. 协议 约定和规范 2. 传输 两点之间传输…...
ubuntu 安装 python
一、安装python依赖的包。 sudo apt-get install -y make zlib1g zlib1g-dev build-essential libbz2-dev libsqlite3-dev libssl-dev libxslt1-dev libffi-dev openssl python3-tklibsqlite3-dev需要在python安装之前安装,如果用户操作系统已经安装python环境&…...
CSS:定位
CSS定位核心知识点详解 CSS定位是网页布局中的重要概念,它允许开发者将元素放置在页面的指定位置。以下是对CSS定位所有相关详细重要知识点的归纳: 为什么要使用定位: 小黄色块在图片上移动,吸引用户的眼球。 当我们滚动窗口的…...
ros2笔记-7.1 机器人导航介绍
7.1 机器人导航介绍 7.1.1 同步定位与地图构建 想要导航,就是要确定当前位置跟目标位置。确定位置就是定位问题。 手机的卫星导航在室内 受屏蔽,需要其他传感器获取位置信息。 利用6.5 章节的仿真,打开并运行 会发现轨迹跟障碍物都被记录…...
一些常见的Java面试题及其答案
Java基础 1. Java中的基本数据类型有哪些? 答案:Java中的基本数据类型包括整数类型(byte、short、int、long)、浮点类型(float、double)、字符类型(char)和布尔类型(boo…...
今日总结 2025-01-14
学习目标 掌握运用 VSCode 开发 uni - app 的配置流程。学会将配置完善的项目作为模板上传至 Git,实现复用。项目启动 创建项目:借助 Vue - Cli 方式创建项目,推荐从国内地址 https://gitee.com/dcloud/uni - preset - vue/repository/archiv…...
图像处理|开运算
开运算是图像形态学中的一种基本操作,通常用于去除小的噪声点,同时保留目标物体的整体形状。它结合了 腐蚀 和 膨胀 操作,且顺序为 先腐蚀后膨胀(先腐蚀后膨胀,胀开了,开运算)。 开运算的作用 …...
基于当前最前沿的前端(Vue3 + Vite + Antdv)和后台(Spring boot)实现的低代码开发平台
项目是一个基于当前最前沿的前端技术栈(Vue3 Vite Ant Design Vue,简称Antdv)和后台技术栈(Spring Boot)实现的低代码开发平台。以下是对该项目的详细介绍: 一、项目概述 项目名称:lowcode-s…...
ASP.NET Core - 依赖注入(三)
ASP.NET Core - 依赖注入(三) 4. 容器中的服务创建与释放 4. 容器中的服务创建与释放 我们使用了 IoC 容器之后,服务实例的创建和销毁的工作就交给了容器去处理,前面也讲到了服务的生命周期,那三种生命周期中对象的创…...
Unity 视频导入unity后,播放时颜色变得很暗很深,是什么原因导致?
视频正常播放时的颜色: 但是,当我在unity下,点击视频播放按钮时,视频的颜色立马变得十分昏暗: 解决办法: 将File—BuildSettings—PlayerSettings—OtherSettings下的Color Space改为:Gamma即可…...
数仓建模(五)选择数仓技术栈:Hive ClickHouse 其它
在大数据技术的飞速发展下,数据仓库(Data Warehouse,简称数仓)成为企业处理和分析海量数据的核心工具。市场上主流数仓技术栈丰富,如Hive、ClickHouse、Druid、Greenplum等,对于初学者而言,选择…...
MySQL主从:如何处理“Got Fatal Error 1236”或 MY-013114 错误(percona译文)
错误的 GTID 如今,典型的复制设置使用 GTID 模式,完整的错误消息可能如下所示: mysql > show replica status\G *************************** 1. row ***************************Replica_IO_Running: NoReplica_SQL_Running: YesLast_I…...
01.14周二F34-Day55打卡
文章目录 1. Jim 在看电视的时候他的老婆正在做饭。(两个动作同时进行)2. 他刚睡着电话就响了。3. 我正在想事情,这时忽然有人从后面抓我胳膊。4. 我们总是边吃火锅边唱歌。5. 他一听说出了事故,马上就来了现场。6. He entered the room until I returned. (英译汉)7.直到…...
Docker 部署 Typecho
1. 官网 https://typecho.org/插件 & 主题 https://github.com/typecho-fans/plugins https://typechx.com/ https://typecho.work/2. 通过 compose 文件安装 github官网: https://github.com/typecho/Dockerfile 新建一个目录,存放 typecho 的相…...
electron 打包后的 exe 文件,运行后是空白窗口
一、代码相关问题 1. 页面加载失败 1.1 原因 在 Electron 应用中,若loadFile或loadURL方法指定的页面路径或 URL 错误,就无法正确加载页面,导致窗口空白。 1.2. 解决 仔细检查loadFile或loadURL方法中传入的路径或 URL 是否正确…...
《leetcode-runner》如何手搓一个debug调试器——架构
本文主要聚焦leetcode-runner对于debug功能的整体设计,并讲述设计原因以及存在的难点 设计引入 让我们来思考一下,一个最简单的调试器需要哪些内容 首先,它能够接受用户的输入 其次,它能够读懂用户想让调试器干嘛,…...
matlab实现了一个优化的遗传算法,用于求解注汽站最优位置的问题
function [best_chromosome, best_fitness] optimized_genetic_algorithm()%% 遗传算法参数初始化% 定义井信息,包括坐标、管道长度、流量、压力等wells defineWells(); % 返回井的结构体数组N length(wells); % 注汽井数量% 遗传算法相关参数L_chromosome 20; …...
八股学习 Redis
八股学习 Redis 使用场景常见问题问题1、2示例场景缓存穿透解决方案一解决方案二 问题3示例场景缓存击穿解决方案 问题4示例场景缓存雪崩解决方案 问题5示例场景双写一致性强一致方案允许延时一致方案 问题6RDB方式AOF方式两种方式对比 问题7示例场景惰性删除定期删除 使用场景…...
C++ 中 :: 的各种用法
C 中 :: 的各种用法 文章目录 C 中 :: 的各种用法1. 全局作用域解析示例:访问全局变量 2. 类作用域2.1. 访问类的静态成员示例:访问静态成员2.2. 定义类外成员函数示例:定义类外成员函数 3. 命名空间作用域3.1. 访问命名空间中的成员示例&…...
【Redis】初识分布式系统
目录 单机架构 分布式系统 应用数据分离架构 应用服务集群架构 读写分离/主从分离架构 冷热分离架构 垂直分库 微服务架构 分布式名词概念 本篇博文,将根据分布式系统的演进一步一步介绍每一种架构的形式,最后为大家总结了一些分布式中常用的…...
【EI 会议征稿】第四届材料工程与应用力学国际学术会议(ICMEAAE 2025)
2025 4th International Conference on Materials Engineering and Applied Mechanics 重要信息 大会官网:www.icmeaae.com 大会时间:2025年3月7-9日 大会地点:中国西安 截稿时间:2025年1月24日23:59 接受/拒稿通知…...