Elasticsearch 实现类似美团附近场所搜索功能

284 阅读6分钟

一、环境准备

在开始使用 Elasticsearch 实现类似美团附近场所搜索功能之前,你需要完成以下环境准备工作:

  1. 安装 Elasticsearch:从 Elasticsearch 官方网站 下载并安装适合你操作系统的版本。安装完成后,启动 Elasticsearch 服务。
  2. 选择客户端工具:你可以使用 Kibana 作为可视化工具来执行 DSL 语句,Kibana 提供了直观的界面,方便你进行索引管理、数据查询等操作。也可以使用 Postman 或其他 HTTP 客户端工具来发送请求。

二、创建索引和 Mapping

我们要创建一个名为 places 的索引,并定义其 Mapping 以支持距离搜索、名称、场所类型和场所价格。以下是创建索引和 Mapping 的 DSL 语句:


PUT /places
{
    "mappings": {
        "properties": {
            "location": {
                "type": "geo_point"
            },
            "name": {
                "type": "text"
            },
            "place_type": {
                "type": "keyword"
            },
            "price": {
                "type": "double"
            }
        }
    }
}
  • geo_point 类型:用于存储地理位置信息(经纬度),支持距离搜索。Elasticsearch 会对该类型的数据进行特殊处理,以便高效地执行地理空间查询。
  • text 类型:适用于存储需要进行全文搜索的文本数据,如场所名称。Elasticsearch 会对 text 类型的字段进行分词处理,以便在查询时能够进行更灵活的匹配。
  • keyword 类型:用于存储精确匹配的字符串,如场所类型。keyword 类型的字段不会进行分词处理,适合用于过滤和聚合操作。
  • double 类型:用于存储浮点数,如场所价格。

三、批量插入数据

为了模拟美团附近的场所信息,我们准备每种类型(景点、酒店、娱乐场所)各 10 条数据,并批量插入到 Elasticsearch 中。以下是批量插入数据的 DSL 语句:

POST /places/_bulk { "index": { "_id": 1 } } { "location": { "lat": 30.00, "lon": 120.00 }, "name": "西湖景区", "place_type": "景点", "price": 50 } { "index": { "_id": 2 } } { "location": { "lat": 30.01, "lon": 120.01 }, "name": "灵隐寺", "place_type": "景点", "price": 70 } { "index": { "_id": 3 } } { "location": { "lat": 30.02, "lon": 120.02 }, "name": "西溪湿地", "place_type": "景点", "price": 60 } { "index": { "_id": 4 } } { "location": { "lat": 30.03, "lon": 120.03 }, "name": "千岛湖", "place_type": "景点", "price": 100 } { "index": { "_id": 5 } } { "location": { "lat": 30.04, "lon": 120.04 }, "name": "乌镇", "place_type": "景点", "price": 80 } { "index": { "_id": 6 } } { "location": { "lat": 30.05, "lon": 120.05 }, "name": "南浔古镇", "place_type": "景点", "price": 75 } { "index": { "_id": 7 } } { "location": { "lat": 30.06, "lon": 120.06 }, "name": "普陀山", "place_type": "景点", "price": 90 } { "index": { "_id": 8 } } { "location": { "lat": 30.07, "lon": 120.07 }, "name": "雁荡山", "place_type": "景点", "price": 85 } { "index": { "_id": 9 } } { "location": { "lat": 30.08, "lon": 120.08 }, "name": "天台山", "place_type": "景点", "price": 72 } { "index": { "_id": 10 } } { "location": { "lat": 30.09, "lon": 120.09 }, "name": "莫干山", "place_type": "景点", "price": 65 } { "index": { "_id": 11 } } { "location": { "lat": 30.10, "lon": 120.10 }, "name": "杭州西湖国宾馆", "place_type": "酒店", "price": 1500 } { "index": { "_id": 12 } } { "location": { "lat": 30.11, "lon": 120.11 }, "name": "杭州JW万豪酒店", "place_type": "酒店", "price": 1200 } { "index": { "_id": 13 } } { "location": { "lat": 30.12, "lon": 120.12 }, "name": "杭州洲际酒店", "place_type": "酒店", "price": 1300 } { "index": { "_id": 14 } } { "location": { "lat": 30.13, "lon": 120.13 }, "name": "杭州黄龙饭店", "place_type": "酒店", "price": 1100 } { "index": { "_id": 15 } } { "location": { "lat": 30.14, "lon": 120.14 }, "name": "杭州君悦酒店", "place_type": "酒店", "price": 1400 } { "index": { "_id": 16 } } { "location": { "lat": 30.15, "lon": 120.15 }, "name": "杭州西湖希尔顿欢朋酒店", "place_type": "酒店", "price": 800 } { "index": { "_id": 17 } } { "location": { "lat": 30.16, "lon": 120.16 }, "name": "杭州全季酒店", "place_type": "酒店", "price": 600 } { "index": { "_id": 18 } } { "location": { "lat": 30.17, "lon": 120.17 }, "name": "杭州如家酒店", "place_type": "酒店", "price": 300 } { "index": { "_id": 19 } } { "location": { "lat": 30.18, "lon": 120.18 }, "name": "杭州汉庭酒店", "place_type": "酒店", "price": 250 } { "index": { "_id": 20 } } { "location": { "lat": 30.19, "lon": 120.19 }, "name": "杭州7天酒店", "place_type": "酒店", "price": 200 } { "index": { "_id": 21 } } { "location": { "lat": 30.20, "lon": 120.20 }, "name": "杭州浪浪浪水公园", "place_type": "娱乐场所", "price": 200 } { "index": { "_id": 22 } } { "location": { "lat": 30.21, "lon": 120.21 }, "name": "杭州烂苹果乐园", "place_type": "娱乐场所", "price": 180 } { "index": { "_id": 23 } } { "location": { "lat": 30.22, "lon": 120.22 }, "name": "杭州宋城千古情", "place_type": "娱乐场所", "price": 300 } { "index": { "_id": 24 } } { "location": { "lat": 30.23, "lon": 120.23 }, "name": "杭州极地海洋公园", "place_type": "娱乐场所", "price": 250 } { "index": { "_id": 25 } } { "location": { "lat": 30.24, "lon": 120.24 }, "name": "杭州杭州野生动物世界", "place_type": "娱乐场所", "price": 220 } { "index": { "_id": 26 } } { "location": { "lat": 30.25, "lon": 120.25 }, "name": "杭州未来世界公园", "place_type": "娱乐场所", "price": 150 } { "index": { "_id": 27 } } { "location": { "lat": 30.26, "lon": 120.26 }, "name": "杭州嘟嘟城儿童职业体验馆", "place_type": "娱乐场所", "price": 130 } { "index": { "_id": 28 } } { "location": { "lat": 30.27, "lon": 120.27 }, "name": "杭州青少年活动中心", "place_type": "娱乐场所", "price": 80 } { "index": { "_id": 29 } } { "location": { "lat": 30.28, "lon": 120.28 }, "name": "杭州溜冰场", "place_type": "娱乐场所", "price": 60 } { "index": { "_id": 30 } } { "location": { "lat": 30.29, "lon": 120.29 }, "name": "杭州保龄球馆", "place_type": "娱乐场所", "price": 50 }

  • _bulk API:用于批量操作,一次性插入多条文档。通过 _bulk API 可以减少与 Elasticsearch 之间的通信次数,提高插入效率。
  • index 操作:用于将文档插入到指定的索引中。_id 是文档的唯一标识符,如果不指定,Elasticsearch 会自动生成一个唯一的 ID。

四、搜索的使用及不同距离计算方式

4.1 搜索附近的场所

以下是一个搜索距离指定位置(经纬度)一定范围内的场所的 DSL 语句:

GET /places/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "geo_distance": {
                        "distance": "10km",
                        "location": {
                            "lat": 30.00,
                            "lon": 120.00
                        }
                    }
                }
            ]
        }
    }
}
  • _search API:用于在 Elasticsearch 中执行搜索操作。
  • bool 查询:是一种组合查询,允许你将多个查询条件组合在一起。must 表示这些条件必须都满足。
  • geo_distance 查询:用于搜索距离指定地理位置一定范围内的文档。distance 参数指定了距离范围,location 参数指定了参考位置。

4.2 不同距离计算方式

4.2.1 arc(默认方式)

使用球面几何计算两点之间的最短距离,考虑了地球的曲率。这种方式计算结果更精确,但计算复杂度较高,适用于需要高精度距离计算的场景。以下是使用 arc 方式的 DSL 语句:

GET /places/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "geo_distance": {
                        "distance": "10km",
                        "distance_type": "arc",
                        "location": {
                            "lat": 30.00,
                            "lon": 120.00
                        }
                    }
                }
            ]
        }
    }
}
  • 球面几何:地球是一个近似的球体,两点之间的最短路径是通过这两点和地球球心所确定的大圆上的劣弧,也就是所谓的 “大圆距离”。arc 方式计算的就是这种大圆距离。

4.2.2 plane

使用平面几何计算两点之间的距离,忽略了地球的曲率。这种方式计算速度快,但在大距离或跨纬度范围较大的场景下,计算结果可能不够精确。以下是使用 plane 方式的 DSL 语句:

GET /places/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "geo_distance": {
                        "distance": "10km",
                        "distance_type": "plane",
                        "location": {
                            "lat": 30.00,
                            "lon": 120.00
                        }
                    }
                }
            ]
        }
    }
}
  • 平面几何plane 方式假定地球表面是一个平面,使用勾股定理等平面几何方法计算两点之间的距离。在距离较短、对精度要求不是特别高的场景下,这种方式可以提高查询性能。

4.3 搜索特定类型和价格范围的附近场所

以下是一个搜索距离指定位置一定范围内,特定类型且价格在一定范围内的场所的 DSL 语句:

GET /places/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "geo_distance": {
                        "distance": "10km",
                        "location": {
                            "lat": 30.00,
                            "lon": 120.00
                        }
                    }
                },
                {
                    "term": {
                        "place_type": "酒店"
                    }
                },
                {
                    "range": {
                        "price": {
                            "gte": 500,
                            "lte": 1500
                        }
                    }
                }
            ]
        }
    }
}
  • term 查询:用于精确匹配某个字段的值。在这个例子中,我们使用 term 查询来筛选出场所类型为 “酒店” 的文档。

  • range 查询:用于筛选某个字段的值在指定范围内的文档。在这个例子中,我们使用 range 查询来筛选出价格在 500 到 1500 之间的文档。

通过以上步骤,你可以使用 Elasticsearch 实现类似美团附近景点、酒店、娱乐场所的搜索功能。