文章目录
「章节总览」
【ElasticSearch 第一篇 https://blog.csdn.net/weixin_45404884/article/details/137402463】
【ElasticSearch 第二篇 https://blog.csdn.net/weixin_45404884/article/details/137505489】
【ElasticSearch 第三篇 https://blog.csdn.net/weixin_45404884/article/details/137548120】
二、ElasticSearch详解
1.DSL 查询文档
(1)DSL 查询语法分类
Elasticsearch 提供了基于 JSON 的 DSL ( Domain Specific Language)来定义查询。常见的查询类型包括:
- 查询所有:查询出所有数据,一般测试用。例如: match_all
- 全文检索( full text )查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
- match_query
- multi_match_query
- 精确查询:根据精确词条值查找数据,一般是查找 keyword 、数值、日期、 boolean 等类型字段。例如:
- ids
- range
- term
- 地理( geo )查询:根据经纬度查询。例如:
- geo_distance
- geo_bounding_box
- 复合( compound )查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
- bool
- function_score
(2)DSL Query 基本语法
GET /indexName/_search
{
"query": {
" 查询类型 ": {
" 查询条件 ": " 条件值 "
}
}
}
(3)查询所有
GET /hotel/_search
{
"query": {
"match_all": {}
}
}
(4)全文检索查询
全文检索查询,会对用户输入内容分词,常用于搜索框搜索:
- match查询,根据一个字段查询。
GET /hotel/_search
{
"query": {
"match": {
"city": "杭州"
}
}
}
- multi_match查询,根据多个字段查询,参与查询字段越多,查询性能越差。
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "四",
"fields": [
"star_name",
"name"
]
}
}
}
搜索结果:
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": 1.2039728,
"hits": [
{
"_index": "hotel",
"_id": "2",
"_score": 1.2039728,
"_source": {
"name": "李四",
"email": "[email protected]",
"price": 43,
"score": 56,
"brand": "如家",
"city": "北京",
"star_name": "三星",
"business": "天安门",
"location": "23.4234234,34.32131",
"pic": "/1/2/3"
}
},
{
"_index": "hotel",
"_id": "3",
"_score": 0.6931471,
"_source": {
"name": "王五",
"email": "[email protected]",
"price": 78,
"score": 43,
"brand": "贝壳",
"city": "杭州",
"star_name": "四星",
"business": "西湖",
"location": "23.4234234,34.32131",
"pic": "/1/2/3"
}
},
{
"_index": "hotel",
"_id": "4",
"_score": 0.6931471,
"_source": {
"name": "赵六",
"email": "[email protected]",
"price": 78,
"score": 43,
"brand": "贝壳",
"city": "杭州",
"star_name": "四星",
"business": "西湖",
"location": "23.4234234,34.32131",
"pic": "/1/2/3"
}
}
]
}
}
(5)精确查询
精确查询一般是查找 keyword 、数值、日期、 boolean 等类型字段。所以不会对搜索条件分词。常见的有:
- term :根据词条精确值查询,根据词条精确匹配,一般搜索 keyword 类型、数值类型、布尔类型、日期类型字段。
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "上海"
}
}
}
}
- range :根据值的范围查询
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 70,
"lte": 80
}
}
}
}
(6)地理查询
根据经纬度查询。常见的使用场景包括:
- 携程:搜索我附近的酒店
- 滴滴:搜索我附近的出租车
- 微信:搜索我附近的人
例如:
- geo_bounding_box :查询 geo_point 值落在某个矩形范围的所有文档
GET /hotel/_search
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": {
"lat": 24,
"lon": 35
},
"bottom_right": {
"lat": 22,
"lon": 33
}
}
}
}
}
- geo_distance :查询到指定中心点小于某个距离值的所有文档
GET /hotel/_search
{
"query": {
"geo_distance": {
"distance": "500km",
"location": "22,33"
}
}
}
(7)复合查询
相关性算分
当我们利用 match 查询时,文档结果会根据与搜索词条的关联度打分( _score ),返回结果时按照分值降序排列。
- F-IDF :在 elasticsearch5.0 之前,会随着词频增加而越来越大。
- BM25 :在 elasticsearch5.0 之后,会随着词频增加而增大,但增长曲线会趋于水平。
TF算法:
T
F
(
词条频率
)
=
词条出现次数
文档中词条总数
TF(词条频率) = \dfrac{词条出现次数}{文档中词条总数} {}
TF(词条频率)=文档中词条总数词条出现次数
TF- IDF算法:
I
D
F
(
逆文档频率
)
=
log
(
文档总数
包含词条的文档总数
)
IDF(逆文档频率) =\log( \dfrac{文档总数}{包含词条的文档总数} {})
IDF(逆文档频率)=log(包含词条的文档总数文档总数)
s
c
o
r
e
=
∑
i
n
T
F
(
词条频率
)
∗
I
D
F
(
逆文档频率
)
score =\sum \limits_{i}^nTF(词条频率) * IDF(逆文档频率)
score=i∑nTF(词条频率)∗IDF(逆文档频率)
BM25算法
s
c
o
r
e
(
Q
,
d
)
=
∑
i
n
log
(
1
+
N
−
n
+
0.5
n
+
0.5
∗
f
i
f
i
+
k
1
∗
(
1
−
b
+
b
∗
d
l
a
v
g
d
l
)
)
score(Q,d) =\sum \limits_{i}^n \log(1+ \dfrac{N-n+0.5}{n+0.5} * \dfrac{f_i}{f_i+k_1 * (1-b+b*\dfrac{dl}{avgdl})})
score(Q,d)=i∑nlog(1+n+0.5N−n+0.5∗fi+k1∗(1−b+b∗avgdldl)fi)
fuction score
算分函数查询,可以控制文档相关性算分,控制文档排名。
- 过滤条件:哪些文档要加分
- 算分函数:如何计算 function score
- 加权方式: function score 与 query score 如何运算
GET /hotel/_search
{
"query": {
"function_score": {
"query": {
"match": {
"all": " 外滩 "
}
},
"functions": [
{
"filter": {
"term": {
"id": "1"
}
},
"weight": 10
}
],
"boost_mode": "multiply"
}
}
}
bool 查询
布尔查询是一个或多个查询子句的组合。子查询的组合方式有:
- must :必须匹配每个子查询,类似“与”
- should :选择性匹配子查询,类似“或”
- must_not :必须不匹配,不参与算分,类似“非”
- filter :必须匹配,不参与算分
##搜索品牌包含“如家”,价格不高于400 ,在坐标 23.21,33.5 周围 1000km范围内的酒店。
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"brand": "如家"
}
}
],
"must_not": [
{
"range": {
"price": {
"gt": 400
}
}
}
],
"filter": [
{
"geo_distance": {
"distance": "1000km",
"location": {
"lat": 23.21,
"lon": 33.5
}
}
}
]
}
}
}
2. 搜索结果处理
(1)排序
elasticsearch 支持对搜索结果排序,默认是根据相关度算分( _score )来排序。可以排序字段类型有: keyword类型、数值类型、地理坐标类型、日期类型等。
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"price": "desc" // 排序字段和排序方式 ASC 、 DESC
}
]
}
(2)分页
elasticsearch 默认情况下只返回 top10 的数据。而如果要查询更多数据就需要修改分页参数了。
elasticsearch 中通过修改 from 、 size 参数来控制要返回的分页结果:
GET /hotel/_search
{
"query": {
"match_all": {}
},
"from": 0, // 分页开始的位置,默认为0
"size": 10, // 期望获取的文档总数
"sort": [
{"price": "asc"}
]
}
(3)深度分页问题
ES 是分布式的,所以会面临深度分页问题。例如按 price 排序后,获取 from = 990 , size =10 的数据:
聚合所有结果,重新排序选取前 1000 个。
- 首先在每个数据分片上都排序并查询前1000 条文档。
- 然后将所有节点的结果聚合,在内存中重新排序选出前 1000 条文档。
- 最后从这 1000 条中,选取从 990 开始的10 条文档。
如果搜索页数过深,或者结果集( from +size )越大,对内存和 CPU 的消耗也越高。因此 ES 设定结果集查询的上限是 10000。
针对深度分页, ES 提供了两种解决方案,官方文档:
• search after :分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
• scroll :原理将排序数据形成快照,保存在内存。官方已经不推荐使用。
(4)高亮
高亮:就是在搜索结果中把搜索关键字突出显示。
原理是这样的:
- 将搜索结果中的关键字用标签标记出来
- 在页面中给标签添加 css 样式
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "上海"
}
}
},
"highlight": {
"fields": {
"city": {
"pre_tags": "<em>",
"post_tags": "<em>"
}
}
}
}
返回结果以高亮形式返回:
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.6486585,
"hits": [
{
"_index": "hotel",
"_id": "1",
"_score": 1.6486585,
"_source": {
"name": "张三",
"email": "[email protected]",
"price": 12,
"score": 12,
"brand": "汉庭",
"city": "上海",
"star_name": "五星",
"business": "虹桥",
"location": "23.423424, 34.32131",
"pic": "/1/2/3"
},
"highlight": {
"city": [
"<em>上海<em>"
]
}
}
]
}
}
3.RestClient 查询文档
(1)查询所有
@Test
public void testMatchAll() throws IOException {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://localhost:9200")
));
try {
// 1.准备 Request
SearchRequest request = new SearchRequest("hotel");
// 2.组织 DSL参数
request.source().query(QueryBuilders.matchAllQuery());
// 3.发送请求,得到响应结果
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//解析响应结果
SearchHits searchHits = response.getHits();
// 4.1.查询的总条数
long total = searchHits.getTotalHits().value;
// 4.2.查询的结果数组
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
// 4.3.得到 source
String json = hit.getSourceAsString();
System.out.println(json);
}
}finally {
client.close();
}
}
(2)全文检索查询
全文检索的 match 和 multi_match 查询与 match_all 的 API 基本一致。差别是查询条件,也就是 query 的部分。
同样是利用 QueryBuilders 提供的方法:
//单字段查询
QueryBuilders.matchQuery("all", " 如家 ");
//多字段查询
QueryBuilders.multiMatchQuery(" 如家 ", "name", "business");
(3)精确查询
精确查询常见的有 term 查询和 range 查询,同样利用 QueryBuilders 实现:
//词条查询
QueryBuilders.termQuery("city", " 杭州 ");
//范围查询
QueryBuilders.rangeQuery("price").gte(100).lte(150);
(4)复合查询
精确查询常见的有 term 查询和 range 查询,同样利用 QueryBuilders 实现:
//创建布尔查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//添加 must条件
boolQuery.must(QueryBuilders.termQuery("city", " 杭州 "));
//添加 filter条件
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));
(5)排序和分页
//查询
request.source().query(QueryBuilders.matchAllQuery());
//分页
request.source().from(0).size(5);
//价格排序
request.source().sort("price", SortOrder.ASC);
(6)高亮
请求request
request.source().highlighter(new HighlightBuilder().field("name")
//是否需要与查询字段匹配
.requireFieldMatch(false));
高亮处理
//获取 source
HotelDoc hotelDoc = JSON.parseObject(hit.getSourceAsString(),
HotelDoc.class);
//处理高亮
Map<String, HighlightField> highlightFields =
hit.getHighlightFields();
if (!CollectionUtils.isEmpty(highlightFields)) {
//获取高亮字段结果
HighlightField highlightField = highlightFields.get("name");
if (highlightField != null) {
//取出高亮结果数组中的第一个,就是酒店名称
String name = highlightField.getFragments()[0].string();
hotelDoc.setName(name);
}
}
下一篇
https://blog.csdn.net/weixin_45404884/article/details/137548120