Elasticsearch 搜索高级

Elasticsearch 搜索高级

建议阅读顺序:

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

1. 修改文档得分

1.1 function_score

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

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

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

json 复制代码
GET /items/_search
{
  "query": {
    "function_score": {
      "query": { "match": { "name":"手机" } }, 
      "functions": [
        {
          "filter": { "term": { "brand": "小米" } },
          "weight": 10 
        }
      ],
      "boost_mode": "multiply"
    }
  },
  "from": 0, "size": 10
}

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

java 复制代码
@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);
}

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 页。

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

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

search after 举例:

查询第一页:

查询第二页:

依次类推。。。。

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

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

json 复制代码
GET /items/_search
{
  "query": { "bool": {} },
  "sort": [
    { "id": { "order": "desc" } }
  ],
  "size": 10, 
  "search_after":[999999999999]
}

使用 Java client 实现深度分页

java 复制代码
@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);
}

2.3 总结

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

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

3. 地理坐标查询

3.1 介绍

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

常见的使用场景包括:

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

3.2 矩形范围查询

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

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

语法如下:

json 复制代码
// 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. 操作

    添加映射:

    json 复制代码
    PUT /items/_mapping
    {
      "properties": {
        "location": { "type": "geo_point" }
      }
    }

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

    更新数据坐标

    json 复制代码
    POST /items/_update/584391
    {
      "doc": {
        "location": { "lat": 40.06, "lon": 116.34 }
      }
    }
  2. 测试

    找到左上和右下的坐标

    json 复制代码
    GET /items/_search
    {
      "query": {
        "geo_bounding_box": {
          "location": {
            "top_left": { "lat": 40.08, "lon": 116.32 },
            "bottom_right": { "lat": 40.04, "lon": 116.36 }
          }
        }
      }
    }

3.3 附近搜索

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

语法:

json 复制代码
// geo_distance 查询
GET /indexName/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km", // 半径
      "FIELD": "31.21,121.5" // 圆心
    }
  }
}

测试:

复制代码
GET /items/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km", 
      "location": "40.061034,116.345999" 
    }
  }
}

4. Java Client

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

xml 复制代码
<!-- 加入spring data elasticsearch -->
<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-elasticsearch</artifactId>
</dependency>

在实体类中添加属性:

java 复制代码
@ApiModelProperty("地理坐标")
private GeoPoint location;

DSL 语句:

json 复制代码
GET /items/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": { "lat": 40.08, "lon": 116.32 },
        "bottom_right": { "lat": 40.04, "lon": 116.36 }
      }
    }
  }
}

代码:

java 复制代码
@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);
}
相关推荐
A叶子叶1 分钟前
Kong网关部署研究
python·spring cloud·微服务·gateway·kong
算家云18 分钟前
Ubuntu 22.04安装MongoDB:GLM4模型对话数据收集与微调教程
大数据·人工智能·mongodb·ubuntu·elasticsearch·算家云·glm4微调
小样vvv9 小时前
【Es】基础入门:开启全文搜索的大门
大数据·elasticsearch·搜索引擎
herogus丶10 小时前
【LLM】Elasticsearch作为向量库入门指南
elasticsearch·docker·langchain
小李同学_LHY13 小时前
微服务架构中的精妙设计:环境和工程搭建
java·spring·微服务·springcloud
Elastic 中国社区官方博客14 小时前
Elasticsearch:使用 Azure AI 文档智能解析 PDF 文本和表格数据
大数据·人工智能·elasticsearch·搜索引擎·pdf·全文检索·azure
光仔December17 小时前
【Elasticsearch入门到落地】10、初始化RestClient
elasticsearch·搜索引擎·全文检索·ik分词器·restclient
点点滴滴的记录17 小时前
Sentinel 相关知识点
java·微服务·sentinel
Code额1 天前
Elasticsearch 的搜索功能
elasticsearch·微服务
lilye661 天前
程序化广告行业(44/89):岗位职责与RTB竞价逻辑深度解析
大数据·elasticsearch·flask·memcache