**核心需求**
- **核心功能**
-
用户可实时上传自己的经纬度位置
-
用户可查询附近 N 公里内的其他用户(按距离排序)
-
支持动态更新位置(如每 30 秒更新一次)
- **非功能性需求**
-
低延迟:查询响应时间 < 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. 关键流程**
- **位置更新流程**
-
用户 → API 写服务 → 生成 Geohash
-
写入 Kafka → 异步消费者更新数据库和缓存
-
缓存更新:ZADD <geohash_prefix> <timestamp> <UserID>
- **附近查询流程**
-
用户 → API 查询服务 → 计算用户 Geohash 及周边8个区域
-
查询 Redis:
-
对每个区域执行 ZRANGEBYSCORE (按时间过滤离线用户)
-
合并结果
-
计算距离并排序(若结果不足则查数据库)
-
返回 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 前缀匹配)
设计亮点总结
-
**Geohash 空间索引**:将二维邻近查询转化为一维前缀匹配,效率提升 10 倍+
-
**读写分离架构**:写走异步队列抗峰值,读走缓存保低延迟
-
**动态分片策略**:按 Geohash 前缀分片,天然支持水平扩展
-
**冷热数据分离**:Redis 存活跃数据,数据库存全量历史
-
**轻量距离计算**:返回结果时避免实时地理计算
> 此设计可支撑 1 亿用户,单次查询平均延迟 50ms,位置更新吞吐量 50K QPS。实际优化需结合业务数据分布调整 Geohash 精度和分片策略。