项目总结之 ES 学习

酒旅项目之 ES 搜索

背景

​ 最近一个多月跟着师哥和同学们一起做了一个酒旅项目,这个项目是依托微信小程序提供线上预定酒店和旅游的互联网产品。希望解决的用户的痛点如下:

  • 提高用户搜索酒店和预定酒店的效率
  • 售后功能保障了用户的合法权益
  • 基于数据分析提供用户多需求场景组合产品

以下是项目架构图:
在这里插入图片描述

流程

  1. 首先经历了熟悉产品和产品流程梳理
  2. 然后进行项目代码熟悉和数据库设计
  3. 接下来进行了接口设计和任务分工
  4. 编写各自功能模块代码,最后交由师哥验收

ES

​ 在任务分工中,我被分配到了编写基于 Elasticsearch 实现酒店列表的搜索功能。期望根据不同的查询条件实现酒店列表的快速搜索展示,由于之前没有使用过 Elasticsearch 整合 Spring Boot ,因此需要通过边学习,边尝试的方式去完成这个功能。以下介绍本次项目所学习到的 Elasticsearch 知识和我本人编写的复杂查询功能。

什么是ES

​ ElasticSearch是一款非常强大的、基于Lucene的开源搜索及分析引擎;它是一个实时的分布式搜索分析引擎,它能让你以前所未有的速度和规模,去探索你的数据。

主要功能和使用场景

主要功能:

1)海量数据的分布式存储以及集群管理,达到了服务与数据的高可用以及水平扩展;

2)近实时搜索,性能卓越。对结构化、全文、地理位置等类型数据的处理;

3)海量数据的近实时分析(聚合功能)

应用场景:

1)网站搜索、垂直搜索、代码搜索;

2)日志管理与分析、安全指标监控、应用性能监控、Web抓取舆情分析;

ES 查询 在 Java 中的实现

​ 在本次项目中主要使用 Elasticsearch 整合 Spring Boot 的方式,通过 ElasticsearchTemplate 完成多条件复合查询。

在本次项目中主要使用了 布尔查询、分页查询、过滤查询、地理位置查询等。

布尔查询
概念和特点
  • 子查询可以任意顺序出现
  • 可以嵌套多个查询,包括bool查询
  • 如果bool查询中没有must条件,should中必须至少满足一条才会返回结果。

bool查询包含四种操作符,分别是must,should,must_not,filter。他们均是一种数组,数组里面是对应的判断条件。

  • must: 必须匹配。贡献算分
  • must_not:过滤子句,必须不能匹配,但不贡献算分
  • should: 选择性匹配,至少满足一条。贡献算分
  • filter: 过滤子句,必须匹配,但不贡献算分
过滤查询

我们可以通过组合过滤器 filter 使用多条件等值查询。

query 和 filter 的区别:query 查询的时候,会先比较查询条件,然后计算分值,最后返回文档结果;而 filter 是先判断是否满足查询条件,如果不满足会缓存查询结果(记录该文档不满足结果),满足的话,就直接缓存结果,filter 不会对结果进行评分,能够提高查询效率。

例1 整合过滤查询、分页查询、布尔查询

​ 查询书籍中 name 词项匹配计算机,价格大于等于 40 并且小于等于 100 的书籍,按价格升序,分页条件为第一页前 10 个。

DSL 查询语句:

GET books/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "name": {
              "value":"计算机"
            }
          }
        }
      ],
      "filter": [
        {
          "range": {
            "price": {
              "gte": 40,
              "lte": 100
            }
          }
        }
      ]
    }
  },
  "sort": [
    {
      "price": {
        "order": "asc"
      }
    }
  ], 
  "size": 10, 
  "from": 0
}

Java 代码:

@Test
void contextLoads() {
    // 复杂查询编写
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
    // 1. bool 查询 (过滤型查询 builder)
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
    // 1.2 范围查询 builder
    RangeQueryBuilder range = new RangeQueryBuilder("price");
    range.gte(40).lte(100);
    TermQueryBuilder term = new TermQueryBuilder("name","计算机");
    boolQueryBuilder.must(term);
    boolQueryBuilder.filter(range);
    // 2. 分页查询
    PageRequest pageRequest = PageRequest.of(0, 10);
    // 3. 按价格升序排序
    FieldSortBuilder priceSortBuilder = SortBuilders.fieldSort("price");
    priceSortBuilder.order(SortOrder.ASC);

    // 整合复杂搜索
    NativeSearchQuery searchQuery = nativeSearchQueryBuilder.withFilter(boolQueryBuilder).withPageable(pageRequest).withSort(priceSortBuilder).build();

    //搜索获取结果集
    SearchHits<Books> booksSearchHits = restTemplate.search(searchQuery, Books.class);
    ArrayList<Books> bookList = new ArrayList<>();
    for (SearchHit<Books> booksSearchHit : booksSearchHits) {
        Books book = booksSearchHit.getContent();
        bookList.add(book);
    }
    for (Books books : bookList) {
        System.out.println(books.getAuthor() + "==>" + books.getName() + "==>" + books.getPrice());
    }

}
地理位置查询

使用 geo_distance query 实现给出一个中心点,查询距离该中心点指定范围内的文档的功能。

DSL 例子如下:

GET geo/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match_all": {}
        }
      ],
      "filter": [
        {
          "geo_distance": {
            "distance": "600km",
            "location": {
              "lat": 34.288991865037524,
              "lon": 108.9404296875
            }
          }
        }
      ]
    }
  }
}

Java 代码:

@Data
@EqualsAndHashCode(callSuper = false)
@Document(indexName = "geo",shards = 1,replicas = 1)
public class Geo {
    private static final long serialVersionUID = -2L;
    @Id
    private Long id;
    @Field(type = FieldType.Keyword, analyzer = "ik_max_word")
    private String name;
    @GeoPointField
    private GeoPoint location;
}


public SearchHits<Geo> getDistanceQuery(@RequestParam double lat, @RequestParam double lon, @RequestParam int distance){

        PageRequest page = PageRequest.of(0, 5);
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        GeoDistanceQueryBuilder geoDistanceQueryBuilder = new GeoDistanceQueryBuilder("location");
        boolean validLatitude = GeoUtils.isValidLatitude(lat);
        boolean validLongitude = GeoUtils.isValidLongitude(lon);
        geoDistanceQueryBuilder.distance(distance, DistanceUnit.KILOMETERS).point(new GeoPoint(lat, lon));

        NativeSearchQuery searchQuery = nativeSearchQueryBuilder.withPageable(page).withQuery(new MatchAllQueryBuilder()).withFilter(geoDistanceQueryBuilder).build();

        SearchHits<Geo> geoSearchHits = elasticsearchRestTemplate.search(searchQuery, Geo.class);
        for (SearchHit<Geo> geoSearchHit : geoSearchHits) {
            System.out.println(geoSearchHit.getContent());
        }
        return geoSearchHits;
    }
设置得分权重查询
 @Test
public void matchQuery(){

    ArrayList<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
    filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("name","计算机"),
            ScoreFunctionBuilders.weightFactorFunction(5)));
    filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("info","计算机"),
            ScoreFunctionBuilders.weightFactorFunction(1)));

    FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
    FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders).scoreMode(FunctionScoreQuery.ScoreMode.SUM).setMinScore(1);

    NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
    builder.withQuery(functionScoreQueryBuilder);
}

项目功能实现

根据对 ES 功能和查询 API 的学习,再根据项目需求编写代码如下:

        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        // 分页
        PageRequest page = PageRequest.of(vo.getPageIndex(), vo.getPageSize());
        nativeSearchQueryBuilder.withPageable(page);

        // 通过改变权重影响关键字查询:名称 > 品牌 > 地址 搜索
        FunctionScoreQueryBuilder functionScoreQuery = getFunctionScoreQuery(vo);
        if(ObjectUtil.isNotNull(functionScoreQuery)){
            nativeSearchQueryBuilder.withQuery(functionScoreQuery);
        }else{
            nativeSearchQueryBuilder.withQuery(new MatchAllQueryBuilder());
        }

        // bool查询过滤 主要通过城市名,附近地标,星级,价格范围进行过滤
        BoolQueryBuilder boolQuery = getBoolQuery(vo);
        nativeSearchQueryBuilder.withFilter(boolQuery);

        // 排序
        Integer sort = vo.getSort();
        SortBuilder sortQuery = getSortQuery(sort, vo);
        nativeSearchQueryBuilder.withSort(sortQuery);

        // 构造总查询
        NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
        SearchHits<EsHotel> hotelSearchHits = elasticsearchRestTemplate.search(searchQuery, EsHotel.class);

        // 封装统一分页返回类
        return getCommonPageResult(hotelSearchHits,vo);

以上代码基本满足了项目需求,我也学习到了 ES 知识和相关 API 使用,更重要的是收获了整个项目流程开发的经验,为我以后更好地学习打下了坚实地基础。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值