ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

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

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 主要应用于以下场景附近的人功能商家推荐系统地理围栏应用实时位置追踪路径规划与导航二、附近的人实现原理2.1 基本实现思路实现附近的人功能的基本思路是存储用户的地理位置信息计算目标位置与用户当前位置的距离筛选出在一定范围内的用户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 user22.3 GEOHASH 命令介绍GEOHASH 命令用于将地理位置信息编码为 Geohash 字符串其语法为GEOHASH key member [member ...]例如GEOHASH user_locations user1 user22.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(hostlocalhost, port6379, db0) def add_user_location(user_id, longitude, latitude): 添加用户位置信息 r.geoadd(user_locations, longitude, latitude, user_id) def get_nearby_users(user_id, radius1, unitkm): 获取附近的人 # 获取当前用户位置 user_location r.geopos(user_locations, user_id) if not user_location: return [] longitude, latitude user_location[0] # 查询附近的人 nearby_users r.geosearch(user_locations, fFROMLONLAT {longitude} {latitude}, fBYRADIUS {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 result3.2 商家推荐系统3.2.1 实现思路商家推荐系统可以根据用户当前位置推荐附近的商家提高用户体验和转化率。3.2.2 代码实现import redis # 连接 Redis r redis.Redis(hostlocalhost, port6379, db0) def add_shop_location(shop_id, longitude, latitude, category): 添加商家位置信息 r.geoadd(fshops:{category}, longitude, latitude, shop_id) def get_nearby_shops(user_id, category, radius5, unitkm): 获取附近的商家 # 获取用户位置 user_location r.geopos(user_locations, user_id) if not user_location: return [] longitude, latitude user_location[0] # 查询附近的商家 nearby_shops r.geosearch(fshops:{category}, fFROMLONLAT {longitude} {latitude}, fBYRADIUS {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 result3.3 地理围栏应用地理围栏(Geofencing)是一种基于地理位置的技术可以在特定地理边界内触发相应操作。3.3.1 实现思路定义地理围栏的边界点用户进入或离开围栏时触发事件执行相应的业务逻辑3.3.2 代码实现import redis import math # 连接 Redis r redis.Redis(hostlocalhost, port6379, db0) 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(fgeofence:{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 实现思路定期更新用户位置保存历史轨迹实时推送位置更新3.4.2 代码实现import redis import time from threading import Thread # 连接 Redis r redis.Redis(hostlocalhost, port6379, db0) def update_user_location(user_id, longitude, latitude): 更新用户位置 # 添加当前位置 r.geoadd(user_locations, longitude, latitude, user_id) # 保存到历史轨迹 timestamp int(time.time()) r.zadd(ftrajectory:{user_id}, {f{longitude},{latitude}: timestamp}) def track_user_location(user_id, interval5): 追踪用户位置 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(ftrajectory:{user_id}, start_time, end_time, withscoresTrue) result [] for point, timestamp in trajectory: lon, lat map(float, point.decode().split(,)) result.append({ longitude: lon, latitude: lat, timestamp: timestamp }) return result # 启动位置追踪线程 # Thread(targettrack_user_location, args(user1,)).start()四、Geohash 底层原理4.1 Geohash 算法原理Geohash 是一种将二维经纬度坐标编码成一维字符串的算法。其基本原理是将地球划分为多个矩形区域对每个区域进行递归细分为每个细分区域分配一个编码将多个编码组合成最终的 Geohash 字符串4.2 经纬度与 Geohash 转换4.2.1 编码过程将经度范围[-180, 180]和纬度范围[-90, 90]分别映射到[0, 1]交替对经度和纬度进行二分处理根据落在哪个区间选择 0 或 1将二进制结果转换为 base32 编码4.2.2 解码过程将 base32 编码转换为二进制交替解码为经度和纬度的二进制表示将二进制映射回经纬度范围计算中心点的经纬度坐标4.3 Geohash 精度与优化Geohash 的精度取决于编码的长度1位约5000km×5000km2位约1250km×625km3位×156km4位×39km5位×4.9km6位×1.2km7位×153m8位×19m9位×2.4m10位×60cm4.3.1 精度选择策略根据应用场景选择合适的 Geohash 精度城市级别5-6位区域级别7-8位街道级别9-10位4.3.2 优化策略前缀匹配优化利用 Geohash 的前缀特性快速筛选候选点多级索引结合不同精度的 Geohash 进行多级索引内存优化使用更紧凑的数据结构存储 Geohash4.4 Geohash 的局限性4.4.1 边界问题Geohash 在边界附近可能出现错误特别是在赤道和本初子午线附近。4.4.2 形状畸变在高纬度地区相同长度的 Geohash 代表的实际距离差异很大。4.4.3 邻接关系相邻的 Geohash 不一定表示地理位置相邻尤其是在编码长度较短时。五、最佳实践与优化5.1 性能优化策略5.1.1 合理使用索引为高频查询的地理位置建立索引使用复合索引优化多条件查询5.1.2 批量操作使用 GEOADD 批量添加位置数据使用 GEOSEARCH 批量查询位置信息5.1.3 数据分片按地理位置范围进行分片使用一致性哈希算法分配数据5.2 数据一致性保障5.2.1 事务处理使用 Redis 事务确保位置更新的原子性MULTI GEOADD key lon lat member other commands EXEC5.2.2 数据持久化启用 RDB 持久化配置 AOF 持久化设置合理的持久化策略5.3 扩展性设计5.3.1 读写分离使用 Redis Cluster 实现读写分离配置主从复制5.3.2 多级缓存使用本地缓存减少 Redis 访问实现缓存穿透和缓存雪崩防护5.3.3 水平扩展使用 Redis Cluster 进行水平扩展合理分配分片策略
返回列表