Redis中Geospatial 实际应用指南

Redis 的 Geospatial(地理空间)功能在实际项目中非常实用,它基于 Sorted Set 数据结构,通过 GeoHash 算法将经纬度编码为分数,实现了高效的地理位置存储和查询。

1. Geospatial 核心命令

bash 复制代码
# 添加地理位置
GEOADD key longitude latitude member

# 获取地理位置
GEOPOS key member

# 计算距离
GEODIST key member1 member2 [unit]

# 附近搜索
GEORADIUS key longitude latitude radius unit [WITHDIST] [WITHCOORD] [COUNT count]
GEORADIUSBYMEMBER key member radius unit [WITHDIST] [WITHCOORD] [COUNT count]

# GeoHash(用于外部地图服务)
GEOHASH key member

2. 实际应用场景

2.1 附近的人/商家搜索

python 复制代码
import redis
import json

class NearbyService:
    def __init__(self):
        self.redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
    
    def add_user_location(self, user_id, lng, lat):
        """添加用户位置"""
        self.redis_client.geoadd(
            'user:locations',
            lng, lat, f"user:{user_id}"
        )
    
    def find_nearby_users(self, center_lng, center_lat, radius_km=5, limit=10):
        """查找附近用户"""
        return self.redis_client.georadius(
            'user:locations',
            center_lng, center_lat, radius_km, 'km',
            withdist=True,  # 返回距离
            withcoord=True, # 返回坐标
            count=limit,    # 限制数量
            sort='ASC'      # 按距离排序
        )
    
    def calculate_distance(self, user1_id, user2_id):
        """计算两个用户间的距离"""
        return self.redis_client.geodist(
            'user:locations',
            f"user:{user1_id}",
            f"user:{user2_id}",
            unit='km'
        )

# 使用示例
service = NearbyService()

# 添加测试数据
service.add_user_location("1001", 116.3974, 39.9093)  # 北京
service.add_user_location("1002", 116.4074, 39.9193)  # 北京附近
service.add_user_location("1003", 121.4737, 31.2304)  # 上海

# 查找天安门附近的用户
nearby_users = service.find_nearby_users(116.3974, 39.9093, 10)
print("附近用户:", nearby_users)

2.2 外卖/快递配送系统

python 复制代码
class DeliverySystem:
    def __init__(self):
        self.redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
    
    def add_restaurant(self, restaurant_id, lng, lat, name):
        """添加餐厅位置"""
        self.redis_client.geoadd('restaurants', lng, lat, restaurant_id)
        self.redis_client.hset(f'restaurant:{restaurant_id}', 'name', name)
    
    def add_rider(self, rider_id, lng, lat):
        """添加骑手位置"""
        self.redis_client.geoadd('riders', lng, lat, f"rider:{rider_id}")
    
    def find_nearest_riders(self, restaurant_id, radius_km=3, count=5):
        """为餐厅寻找最近骑手"""
        # 获取餐厅位置
        pos = self.redis_client.geopos('restaurants', restaurant_id)
        if not pos or not pos[0]:
            return []
        
        lng, lat = pos[0]
        
        # 查找附近骑手
        riders = self.redis_client.georadius(
            'riders', lng, lat, radius_km, 'km',
            withdist=True,
            count=count,
            sort='ASC'
        )
        return riders
    
    def track_delivery_range(self, order_id, target_lng, target_lat, max_radius_km):
        """监控配送范围"""
        key = f"delivery:range:{order_id}"
        
        # 设置目标位置和半径
        self.redis_client.geoadd(key, target_lng, target_lat, "target")
        self.redis_client.expire(key, 3600)  # 1小时过期
        
        return key

# 使用示例
delivery = DeliverySystem()

# 添加餐厅
delivery.add_restaurant("r001", 116.3974, 39.9093, "北京烤鸭店")

# 添加骑手
delivery.add_rider("d001", 116.4000, 39.9100)
delivery.add_rider("d002", 116.3900, 39.9000)

# 寻找最近骑手
nearest_riders = delivery.find_nearest_riders("r001")
print("最近骑手:", nearest_riders)

2.3 车辆监控与电子围栏

python 复制代码
class VehicleTrackingSystem:
    def __init__(self):
        self.redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
    
    def update_vehicle_location(self, vehicle_id, lng, lat):
        """更新车辆位置"""
        self.redis_client.geoadd('vehicles:current', lng, lat, vehicle_id)
        
        # 同时保存到历史记录(使用时间戳)
        timestamp_key = f"vehicles:history:{vehicle_id}"
        self.redis_client.geoadd(timestamp_key, lng, lat, str(int(time.time())))
        
        # 保留最近100个位置点
        self.redis_client.zremrangebyrank(timestamp_key, 0, -100)
    
    def create_geofence(self, fence_id, points):
        """创建电子围栏"""
        # points: [(lng1, lat1), (lng2, lat2), ...]
        fence_key = f"geofence:{fence_id}"
        
        # 添加围栏边界点
        for i, (lng, lat) in enumerate(points):
            self.redis_client.geoadd(fence_key, lng, lat, f"point_{i}")
        
        return fence_key
    
    def check_vehicles_in_fence(self, fence_id, radius_km=0.1):
        """检查围栏内车辆"""
        fence_key = f"geofence:{fence_id}"
        
        # 获取围栏中心点(使用第一个点作为参考)
        center = self.redis_client.geopos(fence_key, "point_0")
        if not center or not center[0]:
            return []
        
        lng, lat = center[0]
        
        # 查找围栏内的车辆
        vehicles = self.redis_client.georadius(
            'vehicles:current',
            lng, lat, radius_km, 'km',
            withdist=True,
            withcoord=True
        )
        return vehicles
    
    def get_vehicle_trail(self, vehicle_id, start_time=0, end_time=None):
        """获取车辆轨迹"""
        if end_time is None:
            end_time = int(time.time())
        
        key = f"vehicles:history:{vehicle_id}"
        positions = self.redis_client.zrangebyscore(
            key, start_time, end_time, withscores=True
        )
        return positions

# 使用示例
tracking = VehicleTrackingSystem()

# 更新车辆位置
tracking.update_vehicle_location("car001", 116.3974, 39.9093)

# 创建禁行区域围栏
fence_points = [
    (116.3964, 39.9083),
    (116.3984, 39.9083),
    (116.3984, 39.9103),
    (116.3964, 39.9103)
]
tracking.create_geofence("restricted_area", fence_points)

# 检查禁行区域内车辆
violations = tracking.check_vehicles_in_fence("restricted_area")
print("禁行区域内车辆:", violations)

3. 性能优化建议

3.1 数据分片策略

python 复制代码
def get_shard_key(base_key, lng, lat, shard_size=10):
    """根据地理位置分片"""
    # 将经纬度映射到分片
    lng_shard = int((lng + 180) / 360 * shard_size)
    lat_shard = int((lat + 90) / 180 * shard_size)
    return f"{base_key}:{lng_shard}:{lat_shard}"

# 使用分片存储
shard_key = get_shard_key("user:locations", lng, lat)
redis_client.geoadd(shard_key, lng, lat, user_id)

3.2 缓存策略

python 复制代码
def get_nearby_users_cached(center_lng, center_lat, radius_km=5):
    """带缓存的附近用户查询"""
    cache_key = f"nearby:{round(center_lng, 2)}:{round(center_lat, 2)}:{radius_km}"
    
    # 尝试从缓存获取
    cached = redis_client.get(cache_key)
    if cached:
        return json.loads(cached)
    
    # 查询数据库
    result = find_nearby_users(center_lng, center_lat, radius_km)
    
    # 缓存结果(30秒过期)
    redis_client.setex(cache_key, 30, json.dumps(result))
    
    return result

4. 注意事项

  1. 精度问题:GeoHash 精度有限,不适合需要极高精度的场景

  2. 数据一致性:定期清理过期数据,避免 Sorted Set 过大

  3. 内存使用:大量地理位置数据会占用较多内存,需要监控

  4. 集群部署:在 Redis Cluster 中,Geospatial 数据会根据 key 分布到不同节点

Redis Geospatial 为地理位置相关应用提供了简单高效的解决方案,特别适合需要实时地理位置查询的场景。

相关推荐
小陈工2 小时前
Python Web开发入门(十七):Vue.js与Python后端集成——让前后端真正“握手言和“
开发语言·前端·javascript·数据库·vue.js·人工智能·python
科技小花7 小时前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸7 小时前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain7 小时前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希7 小时前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神8 小时前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员8 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java8 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿8 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴8 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存