首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐
2025年5月31日 星期六 1:52am

Elasticsearch 搜索高级

  • 25-04-24 18:47
  • 2753
  • 5461
blog.csdn.net

Elasticsearch 搜索高级

建议阅读顺序:

  1. Elasticsearch 入门
  2. Elasticsearch 搜索
  3. Elasticsearch 搜索高级(本文)
  4. Elasticsearch 高级

1. 修改文档得分

1.1 function_score

当我们利用 match 查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。

在实际业务需求中,常常会有竞价排名的功能。不是相关度越高排名越靠前,而是掏的钱多的排名靠前。

例子:给小米这个品牌的手机算分提高十倍

GET /items/_search
{
  "query": {
    "function_score": {
      "query": { "match": { "name":"手机" } }, 
      "functions": [
        {
          "filter": { "term": { "brand": "小米" } },
          "weight": 10 
        }
      ],
      "boost_mode": "multiply"
    }
  },
  "from": 0, "size": 10
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

function score 查询中包含四部分内容:

  1. 原始查询条件:query 部分,基于这个条件搜索文档,并且基于 BM25 算法给文档打分,原始算分;
  2. 过滤条件:filter 部分,符合该条件的文档才会重新算分
  3. 算分函数:符合 filter 条件的文档要根据这个函数做运算,得到的函数算分,有四种函数
    1. weight:函数结果是常量
    2. field_value_factor:以文档中的某个字段值作为函数结果,适用于那些需要根据某个数值字段来影响文档排序的情况。
    3. random_score:以随机数作为函数结果,用于测试或某些特定的用例,比如创建一个随机排序的效果
    4. script_score:自定义算分函数算法,允许你在查询时动态地编写脚本来计算每个文档的分数
  4. boost_mode 运算模式:决定了如何将评分函数的结果与基础查询得分相结合,包括:
    • multiply:评分函数的结果与基础查询的得分相乘。这是默认行为,适用于希望评分函数增强或减弱基础查询得分的情况。
    • replace:评分函数的结果将完全替换基础查询的得分。这意味着最终得分将完全基于评分函数的结果,而不考虑基础查询的原始得分。
    • sum:评分函数的结果与基础查询的得分相加。这使得评分函数的结果直接增加到基础查询得分上,适合于希望累加评分因素的情况。
    • avg:评分函数的结果与基础查询的得分取平均值。这种方式适用于希望平衡基础查询得分与评分函数得分的情况。

1.2 Java Client

@Test
void testFunctionScoreQuery() throws Exception {
    //构建请求
    SearchRequest.Builder builder = new SearchRequest.Builder();
    //设置索引
    builder.index("items");
    //设置查询条件
    SearchRequest.Builder searchRequestBuilder = builder.query(
      q -> q.functionScore(
        f -> f.query(
          q1 -> q1.match(
            m -> m.field("name").query("手机")
          )
        )
        .functions(
          fn -> fn.filter(
            f1 -> f1.term(
              t -> t.field("brand").value("小米")
            )
          )
          .weight(10d)
        )
        .boostMode(FunctionBoostMode.Multiply)
      )
    ).from(0).size(10);
    SearchRequest build = searchRequestBuilder.build();
    //执行请求
    SearchResponse<ItemDoc> searchResponse = esClient.search(build, ItemDoc.class);
    //解析结果
    handleResponse(searchResponse);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

2. 深度分页

2.1 深度分页问题

elasticsearch 的数据一般会采用分片存储,也就是把一个索引中的数据分成 N 份,存储到不同节点上。这种存储方式比较有利于数据扩展,但给分页带来了一些麻烦。

例如:此时需要取出所有数据的前 1000 名,就需要先在每个分片中取出前 1000,再汇总后,再取出前 1000。

当查询分页深度较大时,汇总数据过多,对内存和 CPU 会产生非常大的压力,特别是在 from 值非常大的情况下。这是因为 Elasticsearch 需要先跳过前面的所有文档才能获取到所需的文档,这可能导致大量的磁盘 I/O 操作和 CPU 使用率。

因此 elasticsearch 会限制 from + size 请求:

  1. size 参数的最大值:

    默认情况下,size 参数的最大值被限制为 10000。这意味着每次查询最多只能返回 10000 条记录。

  2. from 参数的最大值:

    默认情况下,from 参数的最大值也被限制为 10000。这意味着请求不能超过第 10000 条记录之后的数据。

这意味着,理论上,您可以查询的最大页数是 10000 / size。例如,如果 size 设置为 100,则最多可以查询 100 页。

2.2 search after

针对深度分页,elasticsearch 提供了两种解决方案:

  1. search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
  2. scroll滚动查询:原理将排序后的文档id形成快照,保存下来,基于快照做分页。官方已经不推荐使用。

search after 举例:

查询第一页:

查询第二页:

依次类推。。。。

如何使用 search_after 实现降序排序呢,排序字段是 ID?

第一页的 search_after 值就需要设置一个最大值

GET /items/_search
{
  "query": { "bool": {} },
  "sort": [
    { "id": { "order": "desc" } }
  ],
  "size": 10, 
  "search_after":[999999999999]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

使用 Java client 实现深度分页

@Test
void testSearchAfter() throws IOException {
  // 1.创建Request
  SearchRequest.Builder builder = new SearchRequest.Builder();
  builder.index("items");

  builder.query(
    q -> q.bool(b -> b)
  ).sort(
    s1 -> s1.field(f -> f.field("id").order(SortOrder.Asc))
  )
  .searchAfter("0")
  .size(1);
  SearchRequest request = builder.build();
  SearchResponse<ItemDoc> response = esClient.search(request, ItemDoc.class);
  // 解析响应
  handleResponse(response);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2.3 总结

大多数情况下,我们采用普通分页就可以了。查看百度、京东等网站,会发现其分页都有限制。例如百度最多支持 77 页,每页不足 20 条。京东最多 100 页,每页最多 60 条。

因此,一般我们采用限制分页深度的方式即可。

3. 地理坐标查询

3.1 介绍

所谓的地理坐标查询,其实就是根据经纬度查询

常见的使用场景包括:

  • 携程:搜索我附近的酒店
  • 滴滴:搜索我附近的出租车
  • 微信:搜索我附近的人

3.2 矩形范围查询

矩形范围查询,也就是 geo_bounding_box 查询,查询坐标落在某个矩形范围的所有文档:

查询时,需要指定矩形的左上、右下两个点的坐标,然后画出一个矩形,落在该矩形内的都是符合条件的点。

语法如下:

// geo_bounding_box查询
GET /indexName/_search
{
  "query": {
    "geo_bounding_box": {
      "FIELD": {
        // 左上点
        "top_left": {
          // 纬度
          "lat": 31.1,
          // 经度
          "lon": 121.5
        },
        // 右下点
        "bottom_right": { "lat": 30.9, "lon": 121.7 }
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  1. 操作

    添加映射:

    PUT /items/_mapping
    {
      "properties": {
        "location": { "type": "geo_point" }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    通过 高德地图API 提供的 API 获取经纬度

    更新数据坐标

    POST /items/_update/584391
    {
      "doc": {
        "location": { "lat": 40.06, "lon": 116.34 }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  2. 测试

    找到左上和右下的坐标

    GET /items/_search
    {
      "query": {
        "geo_bounding_box": {
          "location": {
            "top_left": { "lat": 40.08, "lon": 116.32 },
            "bottom_right": { "lat": 40.04, "lon": 116.36 }
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

3.3 附近搜索

附近查询,也叫做距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档。

语法:

// geo_distance 查询
GET /indexName/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km", // 半径
      "FIELD": "31.21,121.5" // 圆心
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

测试:

GET /items/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km", 
      "location": "40.061034,116.345999" 
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

4. Java Client

GeoPoint 类型是 Spring data elasticsearch 中的一个类,需要将它的依赖添加到 pom.xml 中


<dependency>
  <groupId>org.springframework.datagroupId>
  <artifactId>spring-data-elasticsearchartifactId>
dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

在实体类中添加属性:

@ApiModelProperty("地理坐标")
private GeoPoint location;
  • 1
  • 2

DSL 语句:

GET /items/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": { "lat": 40.08, "lon": 116.32 },
        "bottom_right": { "lat": 40.04, "lon": 116.36 }
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

代码:

@Test
void testGeo() throws Exception {
  //构建请求
  SearchRequest.Builder builder = new SearchRequest.Builder();
  builder.index("items");
  builder.query(
    q -> q.geoBoundingBox(
      g -> g.field("location").boundingBox(
        b -> b.tlbr(
          tlbr->tlbr.topLeft(t -> t.latlon(l -> l.lat(40.08).lon(116.32)))
          .bottomRight(b1 -> b1.latlon(l -> l.lat(40.04).lon(116.36)))
        )
      )
    )
  );
  SearchRequest build = builder.build();
  //执行请求
  SearchResponse<ItemDoc> searchResponse = esClient.search(build, ItemDoc.class);
  //解析结果
  handleResponse(searchResponse);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
注:本文转载自blog.csdn.net的Code额的文章"https://blog.csdn.net/m0_73656461/article/details/146610866"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top