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. 注意事项
- 
精度问题:GeoHash 精度有限,不适合需要极高精度的场景
 - 
数据一致性:定期清理过期数据,避免 Sorted Set 过大
 - 
内存使用:大量地理位置数据会占用较多内存,需要监控
 - 
集群部署:在 Redis Cluster 中,Geospatial 数据会根据 key 分布到不同节点
 
Redis Geospatial 为地理位置相关应用提供了简单高效的解决方案,特别适合需要实时地理位置查询的场景。