【面试题】阿里面试:如何在附近1万家商户中,快速找到理你最近的5家商户?

基于Geohash与优先队列的商户就近搜索方案

核心思路
通过Geohash将二维地理位置编码为一维字符串实现快速区域筛选,结合优先队列(堆排序)动态维护最近商户列表,平衡计算效率与准确性。

数据预处理阶段

商户数据需提前建立Geohash索引并存储。以MySQL为例,商户表结构设计如下:

CREATE TABLE merchants (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100),
    longitude DOUBLE,  -- 经度
    latitude DOUBLE,   -- 纬度
    geohash CHAR(12)   -- 9级Geohash约±19米精度
);
CREATE INDEX idx_geohash ON merchants(geohash);

使用Java生成Geohash(依赖commons-codec库):

public static String encodeGeohash(double lat, double lon, int precision) {
    GeoHash geoHash = GeoHash.withCharacterPrecision(lat, lon, precision);
    return geoHash.toBase32();
}

实时查询阶段

输入参数
用户坐标(userLat, userLon),搜索半径radius(单位:米),返回数量limit

步骤说明

  1. 计算候选Geohash块
    根据半径确定所需Geohash精度级别(如半径500米对应7级编码),获取用户所在块及相邻8个块(Moore邻居):
List<String> getAdjacentGeohashes(String baseGeohash) {
    GeoHash center = GeoHash.fromGeohashString(baseGeohash);
    return Arrays.stream(center.getAdjacent())
           .map(GeoHash::toBase32)
           .collect(Collectors.toList());
}
  1. 数据库批量查询
    使用IN语句查询所有候选块的商户:
SELECT id, name, latitude, longitude 
FROM merchants 
WHERE geohash IN (:geohashList)
  1. 精确距离计算与筛选
    使用优先队列维护Top5最近商户,避免全量排序:
PriorityQueue<Merchant> queue = new PriorityQueue<>(
    (a,b) -> Double.compare(b.distance, a.distance)  // 最大堆
);

for (Merchant merchant : candidateMerchants) {
    double dist = haversine(userLat, userLon, merchant.lat, merchant.lon);
    if (dist <= radius) {
        merchant.distance = dist;
        queue.offer(merchant);
        if (queue.size() > limit) queue.poll();  // 淘汰最远元素
    }
}

距离计算公式(Haversine)

static double haversine(double lat1, double lon1, double lat2, double lon2) {
    double dLat = Math.toRadians(lat2 - lat1);
    double dLon = Math.toRadians(lon2 - lon1);
    double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
               Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * 
               Math.sin(dLon/2) * Math.sin(dLon/2);
    return 6371000 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));  // 地球半径6371km
}

性能优化措施

  1. 多级Geohash缓存
    建立不同精度的Geohash索引(如6-9级),根据查询半径自动选择最优级别

  2. 分布式计算
    对于超大规模数据,采用Elasticsearch的geo_point类型或RedisGEO模块:

// Elasticsearch查询示例
SearchRequest request = new SearchRequest("merchants");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
    .query(QueryBuilders.geoDistanceQuery("location")
        .point(userLat, userLon)
        .distance(radius, DistanceUnit.METERS))
    .size(limit);
request.source(sourceBuilder);
  1. 结果缓存
    对高频查询坐标进行TTL缓存,减少重复计算

方案优势

  1. 时间复杂度可控
    Geohash预过滤使距离计算量从O(N)降至O(1)~O(√N),优先队列筛选保持O(N logK)

  2. 扩展性强
    支持水平分片,可通过Geohash前缀实现数据分区

  3. 精度可调
    Geohash级别与搜索半径动态匹配,避免过度计算

该方案在美团、饿了么等LBS系统中有成熟应用,经实际验证可支持万级QPS的附近商户查询需求。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小冷coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值