Appearance
微信/社交 App「附近的人」是怎么实现的
更新: 6/18/2026 字数: 0 字 时长: 0 分钟

一、功能定位:它到底在解决什么问题
"附近的人"要回答一个看起来简单的问题:在我周围一定范围内(比如 1 公里),有哪些用户,按距离从近到远排出来。
难点不在"算两点距离"——那是初中几何。难点在规模:一个城市可能有几百万活跃用户,你不可能把每个人的坐标拿出来跟自己算一遍距离再排序,那是千万级的计算,每次刷新都做一遍直接把服务器拖垮。所以核心命题其实是:怎么在海量坐标里,快速筛出"附近"这一小撮,而不用全量扫描。
整个功能拆下来就是四件事:客户端拿到自己的位置、上报给服务器、服务器在空间索引里查出附近的人、按距离返回。下面逐层拆。
二、核心技术逻辑:把"二维位置"变成"可索引的一维 key"

位置是经度、纬度两个维度。数据库的索引(B+ 树那种)天生适合在一个维度上做范围查询,对二维的"既要经度接近又要纬度接近"无能为力。业界的主流解法是把二维坐标编码成一维,最常用的是 GeoHash。
GeoHash 的思路:把整张世界地图反复二分。先把经度范围 -180~180 对半切,你在左半还是右半记一个 bit;纬度也对半切记一个 bit;经纬交替、一层层切下去,切得越细,定位越精确。最后把这串 bit 转成 base32 字符串,比如 wx4g0ec。
它有一个关键性质:前缀越长,代表的区域越小;两个点的 GeoHash 前缀相同得越多,它们在地理上就越接近。比如 wx4g0 和 wx4g1 共享 wx4g 前缀,说明落在同一个小方格附近。这样一来,"找附近的人"就变成了"找 GeoHash 前缀相同的人"——一个普通的字符串前缀匹配,数据库索引能高效处理。
一个边界情况要知道:GeoHash 把空间切成方格,两个点可能距离很近但恰好跨在方格边界两侧,前缀就对不上了。所以实际查询要连查周围 8 个相邻格子(九宫格),再在结果里做精确距离过滤,避免漏掉边界上的人。
现在的工程实践里,很少自己手撸 GeoHash,更多直接用 Redis 的 GEO 命令(底层是 ZSet + GeoHash 编码)或数据库自带的空间索引(如 MongoDB 的 2dsphere、PostGIS)。原理都是这一套。
三、定位计算流程:从"上报"到"返回列表"

一次完整的"看附近的人",链路是这样的:
第 1 步:客户端定位。 手机通过 GPS、基站、WiFi 三者融合定位拿到经纬度(室外靠 GPS,室内靠 WiFi/基站补充)。这一步必须在用户授权定位权限之后才能做。
第 2 步:上报坐标。 客户端把经纬度发给服务器。服务器用 Redis GEO 把"用户 ID → 坐标"写进去:
bash
# 写入某用户的位置(经度 纬度 成员)
GEOADD nearby:users 113.324520 23.099994 "user_1001"第 3 步:查询附近。 服务器以当前用户坐标为圆心,按指定半径查:
bash
# 查圆心 1000 米内的成员,带上距离、按近到远排序、最多取 50 个
GEOSEARCH nearby:users FROMMEMBER "user_1001" \
BYRADIUS 1000 m ASC COUNT 50 WITHDISTRedis 内部先用 GeoHash 锁定圆心所在格子及相邻格子,把候选人缩小到几十上百个,再对这一小撮算精确的球面距离(Haversine 公式,考虑地球曲率)做最终过滤和排序。全程不碰全量数据,这是它快的根本原因。
第 4 步:返回。 服务器拿到附近用户 ID 列表,补上昵称、头像、距离等信息,按距离排好返回给客户端展示。
整个过程里,"先用空间索引粗筛、再算精确距离细排"这个两段式是关键——粗筛把候选量从百万降到百级,细排只在小集合上做。
四、隐私处理机制:位置是高度敏感数据

位置数据一旦泄露,能直接推断出一个人的家、公司、行踪,是最敏感的个人信息之一。正规产品在这块有几条硬约束:
- 授权前置:必须用户明确授权定位权限、并主动开启"附近的人"功能后才采集和上报,不能偷偷拿。这是《个人信息保护法》和各应用商店的强制要求。
- 精度降级:对外展示只给"距离"(比如"800 米以内"),绝不暴露对方的精确经纬度。有的产品还会对距离做模糊化处理,只显示分段(<1km、1-5km),防止有人用三点定位反推目标位置。
- 可清除、可关闭:用户能随时清除自己上报的位置、关闭功能后立刻从索引里移除(
GEODEL或设过期时间)。很多产品给位置数据设 TTL,一段时间不活跃就自动失效,不长期留存。 - 最小化与脱敏:服务端存储时位置和身份信息分表、加密;日志里不打印原始坐标。
设计这个功能时,隐私不是做完功能再补的环节,而是从采集那一刻就要定好边界——什么时候采、采多细、存多久、给谁看,每一步都得有依据。
五、工程实现步骤:自己搭一套要做什么

把上面的原理落成一套可运行的服务,分这几步:
1. 客户端采集与上报。 申请定位权限,融合定位拿到坐标,做节流(别每秒上报,按移动距离或固定间隔,比如移动超过 50 米或每 30 秒上报一次),减少电量和流量消耗。
2. 服务端接收与写入。 提供上报接口,校验登录态和权限,把坐标写进 Redis GEO。给每个位置记录设过期时间,自动淘汰不活跃用户。
javascript
// Node.js 伪代码:上报
async function reportLocation(userId, lng, lat) {
await redis.geoadd('nearby:users', lng, lat, userId);
await redis.expire(`loc:${userId}`, 1800); // 30 分钟无更新则失效
}3. 查询接口。 接收当前用户位置和半径,调 GEOSEARCH 拿候选,过滤掉自己、黑名单、已关闭功能的人,再补全用户资料返回。
javascript
async function findNearby(userId, lng, lat, radius = 1000) {
const list = await redis.geosearch(
'nearby:users', 'FROMLONLAT', lng, lat,
'BYRADIUS', radius, 'm', 'ASC', 'COUNT', 50, 'WITHDIST'
);
return list.filter(u => u.member !== userId);
}4. 分页与性能。 数据量大时按距离分桶分页;热点城市可以按地区分片(不同城市用不同 Redis key 或实例),避免单 key 过大。
5. 隐私与合规收尾。 加上授权校验、距离模糊化、一键清除位置、功能开关,日志脱敏。这部分不能省。
进阶方向: 用户量再上一个量级时,单机 Redis 扛不住,要做分片集群;对实时性要求高的场景(比如打车、外卖骑手)会用更专业的空间数据库或自研空间索引(如 Google S2、Uber H3 的六边形网格),但核心思想和 GeoHash 一脉相承——把二维空间映射成可索引的编码,用粗筛+细排避免全量计算。