news 2026/8/29 3:01:16

Redis Geo 实战:深入探索附近的人、LBS 场景与 Geohash 原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis Geo 实战:深入探索附近的人、LBS 场景与 Geohash 原理

一、Redis Geo 概述


1.1 Redis Geo 是什么

Redis Geo 是 Redis 3.2 版本引入的地理位置数据结构,用于存储地理位置信息并进行相关计算。它基于有序集合(Sorted Set)实现,通过 Geohash 算法将二维的经纬度坐标转换为一维的字符串,从而能够高效地实现地理位置相关的操作。


1.2 Geo 数据结构介绍

Redis Geo 使用有序集合(Sorted Set)来存储地理位置数据,其中成员(member)为位置名称,分数(score)为通过 Geohash 算法编码后的位置值。这种结构使得 Redis 能够利用有序集合的高效特性来处理地理位置数据。


1.3 Redis Geo 应用场景概述

Redis Geo 主要应用于以下场景:

  1. 附近的人功能
  2. 商家推荐系统
  3. 地理围栏应用
  4. 实时位置追踪
  5. 路径规划与导航


二、附近的人实现原理


2.1 基本实现思路

实现'附近的人'功能的基本思路是:

  1. 存储用户的地理位置信息
  2. 计算目标位置与用户当前位置的距离
  3. 筛选出在一定范围内的用户


2.2 GEOADD 命令介绍

GEOADD 命令用于向 Geo 集合中添加地理位置信息,其语法为:

GEOADD key longitude latitude member [longitude latitude member ...]

例如:

GEOADD user_locations 116.404 39.915 user1 116.407 39.918 user2


2.3 GEOHASH 命令介绍

GEOHASH 命令用于将地理位置信息编码为 Geohash 字符串,其语法为:

GEOHASH key member [member ...]

例如:

GEOHASH user_locations user1 user2


2.4 GEODIST 命令介绍

GEODIST 命令用于计算两个地理位置之间的距离,其语法为:

GEODIST key member1 member2 [unit]

[unit] 参数可选,包括:

  • meters:米
  • kilometers:千米
  • miles:英里
  • feet:英尺


2.5 GEOSEARCH 命令介绍

GEOSEARCH 命令用于查找指定范围内的地理位置信息,其语法为:

GEOSEARCH key FROMLONLAT longitude latitude BYRADIUS radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]

例如:

GEOSEARCH user_locations FROMLONLAT 116.404 39.915 BYRADIUS 1 km WITHDIST COUNT 5


三、LBS 场景实战


3.1 附近的人功能实现

3.1.1 实现流程

用户位置更新

使用 GEOADD 存储用户位置

用户查询附近的人

使用 GEOSEARCH 查询指定范围内的用户

返回结果展示给用户


3.1.2 代码实现

import redis # 连接 Redis r = redis.Redis(host='localhost', port=6379, db=0) def add_user_location(user_id, longitude, latitude): """添加用户位置信息""" r.geoadd('user_locations', longitude, latitude, user_id) def get_nearby_users(user_id, radius=1, unit='km'): """获取附近的人""" # 获取当前用户位置 user_location = r.geopos('user_locations', user_id) if not user_location: return [] longitude, latitude = user_location[0] # 查询附近的人 nearby_users = r.geosearch('user_locations', f'FROMLONLAT {longitude} {latitude}', f'BYRADIUS {radius} {unit}', 'WITHDIST', 'COUNT', 10) # 格式化返回结果 result = [] for user in nearby_users: user_id = user[0] distance = user[1] result.append({ 'user_id': user_id, 'distance': distance }) return result


3.2 商家推荐系统

3.2.1 实现思路

商家推荐系统可以根据用户当前位置推荐附近的商家,提高用户体验和转化率。


3.2.2 代码实现

import redis # 连接 Redis r = redis.Redis(host='localhost', port=6379, db=0) def add_shop_location(shop_id, longitude, latitude, category): """添加商家位置信息""" r.geoadd(f'shops:{category}', longitude, latitude, shop_id) def get_nearby_shops(user_id, category, radius=5, unit='km'): """获取附近的商家""" # 获取用户位置 user_location = r.geopos('user_locations', user_id) if not user_location: return [] longitude, latitude = user_location[0] # 查询附近的商家 nearby_shops = r.geosearch(f'shops:{category}', f'FROMLONLAT {longitude} {latitude}', f'BYRADIUS {radius} {unit}', 'WITHCOORD', 'WITHDIST', 'WITHHASH') # 格式化返回结果 result = [] for shop in nearby_shops: shop_id = shop[0] distance = shop[1] coordinates = shop[2] result.append({ 'shop_id': shop_id, 'distance': distance, 'coordinates': coordinates }) return result


3.3 地理围栏应用

地理围栏(Geofencing)是一种基于地理位置的技术,可以在特定地理边界内触发相应操作。


3.3.1 实现思路

  1. 定义地理围栏的边界点
  2. 用户进入或离开围栏时触发事件
  3. 执行相应的业务逻辑


3.3.2 代码实现

import redis import math # 连接 Redis r = redis.Redis(host='localhost', port=6379, db=0) def is_point_in_polygon(point, polygon): """判断点是否在多边形内""" x, y = point n = len(polygon) inside = False j = n - 1 for i in range(n): xi, yi = polygon[i] xj, yj = polygon[j] if ((yi > y) != (yj > y)) and (x < (xj - xi) * (y - yi) / (yj - yi) + xi): inside = not inside j = i return inside def check_geofence(user_id, fence_id): """检查用户是否在地理围栏内""" # 获取用户位置 user_location = r.geopos('user_locations', user_id) if not user_location: return False longitude, latitude = user_location[0] user_point = (longitude, latitude) # 获取围栏边界点 fence_points = r.lrange(f'geofence:{fence_id}', 0, -1) polygon = [] for point in fence_points: lon, lat = map(float, point.split(',')) polygon.append((lon, lat)) # 判断是否在围栏内 return is_point_in_polygon(user_point, polygon)


3.4 实时位置追踪

实时位置追踪功能可以实时显示用户或车辆的移动轨迹。


3.4.1 实现思路

  1. 定期更新用户位置
  2. 保存历史轨迹
  3. 实时推送位置更新


3.4.2 代码实现

import redis import time from threading import Thread # 连接 Redis r = redis.Redis(host='localhost', port=6379, db=0) def update_user_location(user_id, longitude, latitude): """更新用户位置""" # 添加当前位置 r.geoadd('user_locations', longitude, latitude, user_id) # 保存到历史轨迹 timestamp = int(time.time()) r.zadd(f'trajectory:{user_id}', {f'{longitude},{latitude}': timestamp}) def track_user_location(user_id, interval=5): """追踪用户位置""" while True: # 模拟获取用户位置 # 实际应用中应从 GPS 设备或移动端获取 longitude = 116.404 + (time.time() % 100) * 0.001 # 模拟移动 latitude = 39.915 + (time.time() % 100) * 0.001 update_user_location(user_id, longitude, latitude) time.sleep(interval) def get_user_trajectory(user_id, start_time, end_time): """获取用户轨迹""" trajectory = r.zrangebyscore(f'trajectory:{user_id}', start_time, end_time, withscores=True) result = [] for point, timestamp in trajectory: lon, lat = map(float, point.decode().split(',')) result.append({ 'longitude': lon, 'latitude': lat, 'timestamp': timestamp }) return result # 启动位置追踪线程 # Thread(target=track_user_location, args=('user1',)).start()


四、Geohash 底层原理


4.1 Geohash 算法原理

Geohash 是一种将二维经纬度坐标编码成一维字符串的算法。其基本原理是:


  1. 将地球划分为多个矩形区域
  2. 对每个区域进行递归细分
  3. 为每个细分区域分配一个编码
  4. 将多个编码组合成最终的 Geohash 字符串


4.2 经纬度与 Geohash 转换

4.2.1 编码过程

  1. 将经度范围[-180, 180]和纬度范围[-90, 90]分别映射到[0, 1]
  2. 交替对经度和纬度进行二分处理
  3. 根据落在哪个区间选择 0 或 1
  4. 将二进制结果转换为 base32 编码


4.2.2 解码过程

  1. 将 base32 编码转换为二进制
  2. 交替解码为经度和纬度的二进制表示
  3. 将二进制映射回经纬度范围
  4. 计算中心点的经纬度坐标


4.3 Geohash 精度与优化

Geohash 的精度取决于编码的长度:

  • 1位:约5000km×5000km
  • 2位:约1250km×625km
  • 3位:×156km
  • 4位:×39km
  • 5位:×4.9km
  • 6位:×1.2km
  • 7位:×153m
  • 8位:×19m
  • 9位:×2.4m
  • 10位:×60cm


4.3.1 精度选择策略

根据应用场景选择合适的 Geohash 精度:

  • 城市级别:5-6位
  • 区域级别:7-8位
  • 街道级别:9-10位


4.3.2 优化策略

  1. 前缀匹配优化:利用 Geohash 的前缀特性快速筛选候选点
  2. 多级索引:结合不同精度的 Geohash 进行多级索引
  3. 内存优化:使用更紧凑的数据结构存储 Geohash


4.4 Geohash 的局限性

4.4.1 边界问题

Geohash 在边界附近可能出现错误,特别是在赤道和本初子午线附近。


4.4.2 形状畸变

在高纬度地区,相同长度的 Geohash 代表的实际距离差异很大。


4.4.3 邻接关系

相邻的 Geohash 不一定表示地理位置相邻,尤其是在编码长度较短时。


五、最佳实践与优化


5.1 性能优化策略

5.1.1 合理使用索引

  1. 为高频查询的地理位置建立索引
  2. 使用复合索引优化多条件查询


5.1.2 批量操作

  1. 使用 GEOADD 批量添加位置数据
  2. 使用 GEOSEARCH 批量查询位置信息


5.1.3 数据分片

  1. 按地理位置范围进行分片
  2. 使用一致性哈希算法分配数据


5.2 数据一致性保障

5.2.1 事务处理

使用 Redis 事务确保位置更新的原子性:

MULTI GEOADD key lon lat member other commands EXEC


5.2.2 数据持久化

  1. 启用 RDB 持久化
  2. 配置 AOF 持久化
  3. 设置合理的持久化策略


5.3 扩展性设计

5.3.1 读写分离

  1. 使用 Redis Cluster 实现读写分离
  2. 配置主从复制


5.3.2 多级缓存

  1. 使用本地缓存减少 Redis 访问
  2. 实现缓存穿透和缓存雪崩防护


5.3.3 水平扩展

  1. 使用 Redis Cluster 进行水平扩展
  2. 合理分配分片策略
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 2:56:37

Python爬虫实战:构建商品价格监控系统与反爬策略详解

1. 项目缘起&#xff1a;当技术宅遇上购物节又到了一年一度的双十一&#xff0c;身边的朋友们不是在研究满减攻略&#xff0c;就是在直播间蹲守。作为一个喜欢用技术解决实际问题的开发者&#xff0c;看着满屏的优惠信息和复杂的活动规则&#xff0c;我就在想&#xff0c;能不能…

作者头像 李华
网站建设 2026/8/29 2:54:56

基于LSTM的地铁AFC客流量预测:数据预处理与特征工程全解析

简介&#xff1a;时间序列预测是数据科学中极具挑战性的任务之一&#xff0c;其核心在于从历史数据中捕捉周期性、趋势性等规律。在轨道交通场景中&#xff0c;AFC自动售检票系统每天产生海量刷卡流水&#xff0c;为客流预测提供了真实、高密度的数据基础。然而&#xff0c;原始…

作者头像 李华
网站建设 2026/8/29 2:53:50

AI提效后时间怎么分配?从量化指标到工程落地的完整指南

最近围绕 Meta CTO 在内部沟通中关于 AI 提效时间分配的讨论&#xff0c;在开发者社区被反复转发。抛开公司内部管理的具体细节不谈&#xff0c;它把一个长期被忽略的问题推到了台前&#xff1a;AI 到底省出了多少时间&#xff0c;省出来的时间应该用来增加工作量、改善工程质量…

作者头像 李华
网站建设 2026/8/29 2:51:48

I2C控制器Busy死锁根因分析与总线恢复机制设计

1. 问题初见&#xff1a;I2C 控制器卡死在 Busy 状态做嵌入式开发的朋友&#xff0c;十有八九都跟 I2C 打过交道。这协议看起来简单&#xff0c;就两根线——SCL 时钟、SDA 数据&#xff0c;加上上拉电阻就能跑&#xff0c;最高速率还能到 3.4Mbps&#xff08;高速模式&#xf…

作者头像 李华
网站建设 2026/8/29 2:51:41

构建个人C++知识体系:从零散笔记到高效检索与实战应用

1. 从笔记到体系&#xff1a;为什么你的C笔记需要“再整理” 很多C学习者&#xff0c;包括我自己在初学阶段&#xff0c;都有过类似的经历&#xff1a;跟着教程、啃着大部头&#xff0c;在IDE里敲下一个个“Hello World”、类定义和模板特化。笔记本&#xff08;无论是电子的还…

作者头像 李华
网站建设 2026/8/29 2:50:48

Java+Spring Boot构建六爻排盘系统:算法、接口与小程序实战

简介&#xff1a;六爻排盘是传统文化数字化中颇具代表性的场景&#xff0c;其本质是将阴阳爻、五行生克等规则转化为可计算的数据模型。二进制表示卦象、随机数模拟铜钱正反&#xff0c;是程序实现起卦逻辑的基础。后端采用Spring Boot构建分层服务&#xff0c;通过静态映射表完…

作者头像 李华