Redis 中的地理空间索引,也就是大家常说的 geo 功能,说实话,很多人把它想得太复杂或者太简单了。你要是真以为它就是个简单的经纬度存储库,那迟早会在高并发场景下栽跟头。我最近帮一家做同城配送的客户重构了他们的附近人模块,原本用的 MySQL+GeoHash 方案,QPS 一到峰值就炸,最后迁移到了 Redis 的 geo 功能在 redis 的架构中,性能直接翻了五六倍。这中间的坑,只有踩过才知道。
先别急着上代码,咱们得搞清楚为什么选它。Redis 底层用的是 ZSET(有序集合),把经纬度通过 GeoHash 算法编码成整数。这玩意儿厉害就厉害在,它能做范围查询,也能做距离计算,而且速度极快。但是!很多教程里只告诉你怎么添加数据,没人告诉你数据量大了之后,ZSET 的排序开销会吃掉你的 CPU。比如我们要查某商家周围 500 米内的骑手,这时候如果不用好命令,服务器能给你干冒烟。
具体怎么搞?别整那些虚的,直接上干货。第一步,初始化你的地理位置。用 GEOADD 命令,格式很简单:GEOADD key longitude latitude member。这里的 member 是你业务里的唯一 ID,比如用户 ID 或店铺 ID。注意,经纬度顺序千万别反了,一个是经度一个是纬度,搞反了你在地图上找到的可能是个太平洋岛,而不是北京三环。我在测试的时候,有次因为顺序错了,查出来的结果全在南极附近,笑得我肚子疼,但这事儿真的很致命,一定要在单元测试里写死边界值。
第二步,查询附近的资源。这里要特别注意 RANGE 和 RADIUS 命令的区别。GEOSEARCH 是较新版本推荐的,兼容性更好。你要查某个坐标周边的人,用 GEOSEARCH FROMMEMBER 或者 GEOSEARCH FROMLONGITUDE LATITUDE。关键点来了,返回的数据里默认不包含距离,除非你指定 COUNT 或者带上特定参数。有些朋友反馈说查不出距离,其实是没加 DISTANCE 参数,或者客户端解析错了 JSON 结构。这里建议直接返回 JSON 格式,前端自己算或者后端算清楚再丢回去,别让用户端做额外请求,每一毫秒都在烧钱。
第三步,维护数据的一致性。这是最容易被忽视的。Redis 里的 geo 功能在 redis 是内存数据库,重启就没了。所以你必须配合持久化机制,RDB 或 AOF 都得开。另外,如果数据需要跨服务共享,别傻乎乎地每次都同步到 MySQL,那样 IO 压力大得吓人。正确的姿势是:先写 Redis 保证查询快,异步线程轮询变更,再批量插入 MySQL 做冷备和复杂分析。我见过有个哥们直接在业务逻辑里双写,结果因为网络抖动导致数据不一致,排查了一周,全是泪水。
还有一点,关于去重。GEOADD 命令在添加同一个 member 时,会自动更新位置,不会报错。这意味着如果你用户漂移了,直接发新坐标就行,不用先 DELETE 再 ADD,省了一步 IO,这在高频移动场景下特别重要。比如网约车司机,每秒都在动,少一次网络往返,体验就好一分。
最后提醒一下,Redis 单线程模型在处理大 Key 时会阻塞。如果你的附近人列表返回几千个人,记得分页,或者限制 COUNT 数量。不要试图一次性拉取全量数据,那会让你的 Redis 实例假死。我在生产环境设过阈值,最多返回 100 个结果,多余的让用户滑动加载。这样既保证了主流程流畅,又避免了内存溢出。
总之,Redis 的 geo 功能在 redis 的生态里是个神器,但用不好就是杀手。别盲目崇拜它的速度,要理解背后的 GeoHash 原理,控制好数据量,做好异步兜底。只要你踩对了步子,它能给你带来的性能提升是质的飞跃。别等到线上崩了才后悔没早点深入了解这些细节。希望这篇经验能帮你在地理空间开发的路上少摔几个跟头,毕竟,代码写得漂亮,头发还能多留两根。