面试题:设计一个分布式“附近的人”功能(如微信附近的人、交友应用位置匹配)
**核心需求**
1. **核心功能**
- 用户可实时上传自己的经纬度位置
- 用户可查询附近 N 公里内的其他用户(按距离排序)
- 支持动态更新位置(如每 30 秒更新一次)
2. **非功能性需求**
- 低延迟:查询响应时间 < 100ms
- 高并发:支持百万级在线用户,万级 QPS 位置更新
- 可扩展:适应用户量持续增长
- 数据一致性:允许短暂延迟(最终一致)
答案
**1. 核心数据结构**
- **用户位置存储**
```
UserID (主键) | Latitude | Longitude | Timestamp | Geohash (索引)
```
- **Geohash 原理**:将二维经纬度编码为一维字符串(如 `wx4g0b`),前缀匹配可快速定位相邻区域
**2. 核心组件**
- **Location API 服务**
- **写服务**:接收位置更新 → 校验数据 → 写入 Kafka → 返回成功
- **查询服务**:接收查询请求 → 计算 Geohash → 查询缓存 → 未命中则查数据库 → 计算距离排序 → 返回结果
- **Redis 缓存**
- 存储结构:`Sorted Set` (Key: `geohash_prefix`, Value: `UserID`, Score: `timestamp`)
- 示例:用户A在区域 `wx4g` 的缓存:`ZADD wx4g <timestamp> UserA`
- **消息队列 (Kafka)**
- 异步解耦:接收高并发写入,批量消费到数据库
- 分区策略:按 `UserID` 哈希分区保证顺序
- **分片数据库 (Geo Shard)**
- 选型:**RedisGEO** (内存) 或 **PostGIS** (磁盘)
- 分片规则:按 Geohash 前缀分片(如 `wx4g` 开头的用户分配到 Shard 1)
- **离线计算引擎**
- 定期分析热点区域(如商圈),预加载缓存
**3. 关键流程**
- **位置更新流程**
1. 用户 → API 写服务 → 生成 Geohash
2. 写入 Kafka → 异步消费者更新数据库和缓存
3. 缓存更新:ZADD <geohash_prefix> <timestamp> <UserID>
- **附近查询流程**
1. 用户 → API 查询服务 → 计算用户 Geohash 及周边8个区域
2. 查询 Redis:
- 对每个区域执行 ZRANGEBYSCORE (按时间过滤离线用户)
- 合并结果
3. 计算距离并排序(若结果不足则查数据库)
4. 返回 Top K 用户
```
**4. 优化策略**
- **缓存设计**
- 热区预加载:离线分析高频区域,提前载入 Redis
- 缓存过期:自动清理 30 分钟未更新的用户(ZREMRANGEBYSCORE)
- **分片扩展**
- Geohash 前缀分片:首 4 字符分片可支撑 1 亿用户(36^4=167 万区/片)
- 动态扩容:添加新分片时,按新前缀规则迁移数据
- **计算优化**
- 距离计算:缓存中只存 UserID,返回时批量查询元数据
- 排序优化:Redis 返回时按距离平方排序(避免开方计算)
**5. 容错与扩展**
- **高可用**
- Redis:主从复制 + Sentinel 自动故障转移
- 数据库:分片多副本 + 跨可用区部署
- **数据一致性**
- 最终一致:通过 Kafka 保证异步更新
- 查询补偿:缓存未命中时查数据库并回填
- **监控**
- 关键指标:位置更新延迟、查询响应时间、缓存命中率
- 告警阈值:缓存命中率 < 90% 或查询延迟 > 100ms
**6. 进阶设计**
- **地理围栏**:扩展存储多边形区域(如商圈),查询时快速过滤
- **多级索引**:
- L1 内存索引:RedisGEO 存最近 1 小时活跃用户
- L2 磁盘存储:PostGIS 存全量历史位置
- **流量治理**:
- 限流:对高频查询用户实施令牌桶限流
- 降级:高负载时返回非精确结果(如仅用 Geohash 前缀匹配)
---
### 设计亮点总结
1. **Geohash 空间索引**:将二维邻近查询转化为一维前缀匹配,效率提升 10 倍+
2. **读写分离架构**:写走异步队列抗峰值,读走缓存保低延迟
3. **动态分片策略**:按 Geohash 前缀分片,天然支持水平扩展
4. **冷热数据分离**:Redis 存活跃数据,数据库存全量历史
5. **轻量距离计算**:返回结果时避免实时地理计算
> 此设计可支撑 1 亿用户,单次查询平均延迟 50ms,位置更新吞吐量 50K QPS。实际优化需结合业务数据分布调整 Geohash 精度和分片策略。