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 为地理位置相关应用提供了简单高效的解决方案,特别适合需要实时地理位置查询的场景。

相关推荐
黑夜管理员4 小时前
Sql Server安装报错“服务没有及时响应启动或控制请求”
数据库·sql server
NineData5 小时前
NineData云原生智能数据管理平台新功能发布|2025年9月版
数据库·云原生·devops·ninedata·数据库迁移·数据复制·风险sql管控
junnhwan5 小时前
【苍穹外卖笔记】Day04--套餐管理模块
java·数据库·spring boot·后端·苍穹外卖·crud
一枚正在学习的小白6 小时前
PG数据文件位置迁移
linux·运维·服务器·数据库
真的想不出名儿6 小时前
上传头像到腾讯云对象存储-前端基于antdv
java·数据库·腾讯云
Dreams_l6 小时前
初识redis(分布式系统, redis的特性, 基本命令)
数据库·redis·缓存
数据库知识分享者小北6 小时前
Qoder + ADB Supabase :5分钟GET超火AI手办生图APP
数据库·后端
疯癫的老码农6 小时前
【Linux环境下安装】SpringBoot应用环境安装(二)-Redis安装
linux·spring boot·redis
想你依然心痛7 小时前
Spark大数据分析与实战笔记(第六章 Kafka分布式发布订阅消息系统-01)
笔记·分布式·spark