GEO地理位置:附近的人与LBS服务
GEO核心命令 # 添加地理位置 GEOADD key longitude latitude member # 获取位置坐标 GEOPOS key member [member ...] # 计算距离 GEODIST key member1 member2 [unit] # 范围查询 GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [COUNT count] # 以成员为中心查询 GEORADIUSBYMEMBER key member radius unit [WITHCOORD] [WITHDIST] [COUNT count] # 获取GeoHash GEOHASH key member [member ...] 单位:m(米)、km(千米)、mi(英里)、ft(英尺) 实战案例 案例1:附近的人 @Service public class NearbyPeopleService { @Autowired private RedisTemplate<String, String> redis; private static final String GEO_KEY = "user:location"; // 更新用户位置 public void updateLocation(Long userId, double longitude, double latitude) { redis.opsForGeo().add(GEO_KEY, new Point(longitude, latitude), String.valueOf(userId)); } // 查找附近的人(5km内) public List<NearbyUser> findNearby(Long userId, double radius) { // 获取用户位置 List<Point> positions = redis.opsForGeo().position(GEO_KEY, String.valueOf(userId)); if (positions == null || positions.isEmpty()) { return Collections.emptyList(); } Point userPos = positions.get(0); // 查询附近的人 GeoResults<GeoLocation<String>> results = redis.opsForGeo().radius( GEO_KEY, userPos, new Distance(radius, Metrics.KILOMETERS), GeoRadiusCommandArgs.newGeoRadiusArgs() .includeDistance() .sortAscending() .limit(50) ); if (results == null) { return Collections.emptyList(); } List<NearbyUser> nearbyUsers = new ArrayList<>(); for (GeoResult<GeoLocation<String>> result : results) { Long nearbyUserId = Long.valueOf(result.getContent().getName()); if (!nearbyUserId.equals(userId)) { // 排除自己 NearbyUser user = new NearbyUser(); user.setUserId(nearbyUserId); user.setDistance(result.getDistance().getValue()); nearbyUsers.add(user); } } return nearbyUsers; } // 计算两用户间距离 public Double getDistance(Long userId1, Long userId2) { Distance distance = redis.opsForGeo().distance( GEO_KEY, String.valueOf(userId1), String.valueOf(userId2), Metrics.KILOMETERS ); return distance != null ? distance.getValue() : null; } } 案例2:外卖配送范围判断 @Service public class DeliveryService { @Autowired private RedisTemplate<String, String> redis; private static final String STORE_GEO_KEY = "store:location"; private static final double MAX_DELIVERY_DISTANCE = 5.0; // 5km配送范围 // 添加店铺位置 public void addStore(Long storeId, double longitude, double latitude) { redis.opsForGeo().add(STORE_GEO_KEY, new Point(longitude, latitude), String.valueOf(storeId)); } // 查找可配送的店铺 public List<DeliveryStore> findAvailableStores(double longitude, double latitude) { GeoResults<GeoLocation<String>> results = redis.opsForGeo().radius( STORE_GEO_KEY, new Circle(new Point(longitude, latitude), new Distance(MAX_DELIVERY_DISTANCE, Metrics.KILOMETERS)), GeoRadiusCommandArgs.newGeoRadiusArgs() .includeDistance() .includeCoordinates() .sortAscending() ); if (results == null) { return Collections.emptyList(); } return results.getContent().stream() .map(result -> { DeliveryStore store = new DeliveryStore(); store.setStoreId(Long.valueOf(result.getContent().getName())); store.setDistance(result.getDistance().getValue()); store.setLongitude(result.getContent().getPoint().getX()); store.setLatitude(result.getContent().getPoint().getY()); return store; }) .collect(Collectors.toList()); } // 判断是否在配送范围内 public boolean isInDeliveryRange(Long storeId, double longitude, double latitude) { List<Point> positions = redis.opsForGeo().position(STORE_GEO_KEY, String.valueOf(storeId)); if (positions == null || positions.isEmpty()) { return false; } Point storePos = positions.get(0); Distance distance = calculateDistance(storePos, new Point(longitude, latitude)); return distance.getValue() <= MAX_DELIVERY_DISTANCE; } private Distance calculateDistance(Point p1, Point p2) { // Haversine公式计算距离 double lat1 = Math.toRadians(p1.getY()); double lat2 = Math.toRadians(p2.getY()); double lon1 = Math.toRadians(p1.getX()); double lon2 = Math.toRadians(p2.getX()); double dLat = lat2 - lat1; double dLon = lon2 - lon1; double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) * Math.sin(dLon / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); double distance = 6371 * c; // 地球半径6371km return new Distance(distance, Metrics.KILOMETERS); } } 案例3:打车服务(司机匹配) @Service public class TaxiService { @Autowired private RedisTemplate<String, String> redis; private static final String DRIVER_GEO_KEY = "driver:location"; // 司机上线,更新位置 public void driverOnline(Long driverId, double longitude, double latitude) { redis.opsForGeo().add(DRIVER_GEO_KEY, new Point(longitude, latitude), String.valueOf(driverId)); // 设置过期时间(10分钟,超时下线) redis.expire(DRIVER_GEO_KEY, 10, TimeUnit.MINUTES); } // 司机下线 public void driverOffline(Long driverId) { redis.opsForGeo().remove(DRIVER_GEO_KEY, String.valueOf(driverId)); } // 乘客叫车,匹配最近的司机 public List<Driver> matchNearbyDrivers(double longitude, double latitude, int count) { GeoResults<GeoLocation<String>> results = redis.opsForGeo().radius( DRIVER_GEO_KEY, new Circle(new Point(longitude, latitude), new Distance(3, Metrics.KILOMETERS)), // 3km范围内 GeoRadiusCommandArgs.newGeoRadiusArgs() .includeDistance() .includeCoordinates() .sortAscending() .limit(count) ); if (results == null) { return Collections.emptyList(); } return results.getContent().stream() .map(result -> { Driver driver = new Driver(); driver.setDriverId(Long.valueOf(result.getContent().getName())); driver.setDistance(result.getDistance().getValue()); driver.setLongitude(result.getContent().getPoint().getX()); driver.setLatitude(result.getContent().getPoint().getY()); return driver; }) .collect(Collectors.toList()); } // 定期更新司机位置(心跳) @Scheduled(fixedRate = 30000) // 每30秒 public void updateDriverLocations() { // 从GPS服务获取所有在线司机位置 List<DriverLocation> locations = fetchDriverLocations(); for (DriverLocation location : locations) { driverOnline(location.getDriverId(), location.getLongitude(), location.getLatitude()); } } private List<DriverLocation> fetchDriverLocations() { // 从GPS服务获取位置 return new ArrayList<>(); } } 案例4:充电桩查询 @Service public class ChargingStationService { @Autowired private RedisTemplate<String, String> redis; private static final String STATION_GEO_KEY = "charging:station"; // 添加充电桩 public void addStation(Long stationId, double longitude, double latitude) { redis.opsForGeo().add(STATION_GEO_KEY, new Point(longitude, latitude), String.valueOf(stationId)); } // 查找附近充电桩 public List<ChargingStation> findNearbyStations( double longitude, double latitude, double radiusKm) { GeoResults<GeoLocation<String>> results = redis.opsForGeo().radius( STATION_GEO_KEY, new Circle(new Point(longitude, latitude), new Distance(radiusKm, Metrics.KILOMETERS)), GeoRadiusCommandArgs.newGeoRadiusArgs() .includeDistance() .includeCoordinates() .sortAscending() ); if (results == null) { return Collections.emptyList(); } return results.getContent().stream() .map(result -> { Long stationId = Long.valueOf(result.getContent().getName()); // 从数据库获取充电桩详情 ChargingStation station = getStationDetails(stationId); station.setDistance(result.getDistance().getValue()); return station; }) .collect(Collectors.toList()); } private ChargingStation getStationDetails(Long stationId) { // 从数据库查询 return new ChargingStation(); } } 性能优化 1. 缓存用户位置 // 避免频繁GEO查询 public Point getUserLocation(Long userId) { String cacheKey = "user:pos:" + userId; // 先查缓存 String cached = redis.opsForValue().get(cacheKey); if (cached != null) { String[] parts = cached.split(","); return new Point(Double.parseDouble(parts[0]), Double.parseDouble(parts[1])); } // GEO查询 List<Point> positions = redis.opsForGeo().position(GEO_KEY, String.valueOf(userId)); if (positions != null && !positions.isEmpty()) { Point point = positions.get(0); // 缓存5分钟 redis.opsForValue().set(cacheKey, point.getX() + "," + point.getY(), 5, TimeUnit.MINUTES); return point; } return null; } 2. 批量查询优化 // 批量获取位置 public Map<Long, Point> batchGetLocations(List<Long> userIds) { String[] members = userIds.stream() .map(String::valueOf) .toArray(String[]::new); List<Point> positions = redis.opsForGeo().position(GEO_KEY, members); Map<Long, Point> result = new HashMap<>(); for (int i = 0; i < userIds.size(); i++) { if (positions != null && i < positions.size()) { result.put(userIds.get(i), positions.get(i)); } } return result; } 底层原理 GeoHash编码: ...