Redis在地理空间数据+实时数据分析中的具体应用场景

附近门店搜索系统

业务需求:

  • 用户可查询当前位置附近5公里内的所有门店
  • 结果按距离排序,显示距离信息
  • 系统需要支持高频的位置更新和查询

①、数据结构

java 复制代码
// 门店位置信息
@Data
public class StoreLocation {
    private Long storeId;
    private String storeName;
    private String address;
    private Double longitude; // 经度
    private Double latitude;  // 纬度
}
java 复制代码
@Data
class NearbyStore {
    private Long storeId;
    private String storeName;
    private String address;
    private Double distance;
    private String distanceUnit;
}

②、Redis配置

java 复制代码
@Configuration
public class RedisGeoConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

③、地理位置服务实现

java 复制代码
@Service
public class GeoLocationService {
    
    private static final String STORE_GEO_KEY = "stores:geo";
    private final RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    public GeoLocationService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

	/**
		添加或更新门店位置
	*/
	public void addOrUpdateStoreLocation(StoreLocation store){
		
		redisTemplate.opsForGeo().add(STORE_GEO_KEY, 
            new Point(store.getLongitude(), store.getLatitude()), 
            store.getStoreId().toString());
	}

	/**
		删除门店位置
	*/
	public void removeStoreLocation(){
		redisTemplate.opsForGeo().remove(STORE_GEO_KEY, storeId.toString());
	}

	/**
		查询附近门店
	*/
	public List<NearByStore> findNearByStores(double longitude, double latitude, double distance, int limit){
		
		Circle within = new Cicle(new Point(longitude,latitude),new Distance(distance, Metrics.KILOMETERS));
		RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
            .newGeoRadiusArgs()
            .includeDistance()
            .sortAscending()
            .limit(limit);
        
        GeoResults<RedisGeoCommands.GeoLocation<Object>> results = redisTemplate.opsForGeo()
            .radius(STORE_GEO_KEY, within, args);
        
        return results.getContent().stream()
            .map(this::convertToNearbyStore)
            .collect(Collectors.toList());
	}

	private NearbyStore convertToNearbyStore(GeoResult<RedisGeoCommands.GeoLocation<Object>> geoResult) {
        RedisGeoCommands.GeoLocation<Object> location = geoResult.getContent();
        Distance distance = geoResult.getDistance();
        
        NearbyStore store = new NearbyStore();
        store.setStoreId(Long.parseLong(location.getName().toString()));
        store.setDistance(distance.getValue());
        store.setDistanceUnit(distance.getUnit());
        
        // 这里可以补充查询门店详细信息
        // store.setStoreName(...);
        // store.setAddress(...);
        
        return store;
    }

	/**
     * 计算两个位置之间的距离
     */
    public Distance calculateDistance(Long storeId1, Long storeId2) {
        return redisTemplate.opsForGeo().distance(
            STORE_GEO_KEY, 
            storeId1.toString(), 
            storeId2.toString(), 
            Metrics.KILOMETERS
        );
    }
    
    /**
     * 获取门店位置坐标
     */
    public Point getStorePosition(Long storeId) {
        List<Point> points = redisTemplate.opsForGeo()
            .position(STORE_GEO_KEY, storeId.toString());
        return points != null && !points.isEmpty() ? points.get(0) : null;
    }
}
java 复制代码
@RestController
@RequestMapping("/api/stores")
public class StoreController {
    
    private final GeoLocationService geoLocationService;
    private final StoreService storeService;
    
    @Autowired
    public StoreController(GeoLocationService geoLocationService, StoreService storeService) {
        this.geoLocationService = geoLocationService;
        this.storeService = storeService;
    }
    
    @PostMapping("/location")
    public ResponseEntity<Void> updateStoreLocation(@RequestBody StoreLocation storeLocation) {
        geoLocationService.addOrUpdateStoreLocation(storeLocation);
        return ResponseEntity.ok().build();
    }
    
    @GetMapping("/nearby")
    public ResponseEntity<List<NearbyStore>> findNearbyStores(
            @RequestParam double longitude,
            @RequestParam double latitude,
            @RequestParam(defaultValue = "5") double distance,
            @RequestParam(defaultValue = "10") int limit) {
        
        List<NearbyStore> nearbyStores = geoLocationService
            .findNearbyStores(longitude, latitude, distance, limit);
        
        // 补充门店详细信息
        nearbyStores.forEach(store -> {
            Store storeInfo = storeService.getStoreById(store.getStoreId());
            if (storeInfo != null) {
                store.setStoreName(storeInfo.getName());
                store.setAddress(storeInfo.getAddress());
            }
        });
        
        return ResponseEntity.ok(nearbyStores);
    }
}

用户行为实时分析系统

业务需求:

  • 实时记录用户点击、浏览、购买等行为
  • 统计热门商品实时排行榜
  • 分析用户行为路径
  • 实时计算关键指标(PV、UV、转化率等)

①、事件驱动模型

java 复制代码
// 用户行为事件基类
@Data
public abstract class UserEvent {
    private String eventId;
    private Long userId;
    private Long timestamp;
    private String deviceId;
    private String ipAddress;
}

// 页面浏览事件
@Data
@EqualsAndHashCode(callSuper = true)
public class PageViewEvent extends UserEvent {
    private String pageUrl;
    private String referrer;
    private Long productId; // 如果是商品页
    private Long duration;  // 停留时长(毫秒)
}

// 商品点击事件
@Data
@EqualsAndHashCode(callSuper = true)
public class ProductClickEvent extends UserEvent {
    private Long productId;
    private String position; // 页面位置
}

// 购买事件
@Data
@EqualsAndHashCode(callSuper = true)
public class PurchaseEvent extends UserEvent {
    private Long orderId;
    private List<PurchaseItem> items;
    private BigDecimal amount;
    
    @Data
    public static class PurchaseItem {
        private Long productId;
        private Integer quantity;
        private BigDecimal price;
    }
}

②、实时分析服务实现

java 复制代码
@Service
public class RealtimeAnalyticsService {
    
    private static final String PAGE_VIEWS_KEY = "stats:pageviews";
    private static final String UNIQUE_VISITORS_KEY = "stats:unique_visitors";
    private static final String PRODUCT_CLICKS_KEY = "stats:product_clicks";
    private static final String PRODUCT_HOT_RANK_KEY = "stats:product_hot_rank";
    private static final String USER_SESSION_PATH_KEY = "user:session:path:";
    
    private final RedisTemplate<String, Object> redisTemplate;
    private final ObjectMapper objectMapper;
    
    @Autowired
    public RealtimeAnalyticsService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.objectMapper = new ObjectMapper();
    }

	/**
		处理用户行为事件
	*/
	/**
     * 处理用户行为事件
     */
    public void processUserEvent(UserEvent event) throws JsonProcessingException {
        String eventJson = objectMapper.writeValueAsString(event);
        String eventType = event.getClass().getSimpleName().toLowerCase();
        
        // 1. 存储原始事件(使用Stream)
        redisTemplate.opsForStream().add("user_events", Collections.singletonMap(
            eventType, eventJson
        ));
        
        // 2. 根据事件类型处理
        if (event instanceof PageViewEvent) {
            processPageView((PageViewEvent) event);
        } else if (event instanceof ProductClickEvent) {
            processProductClick((ProductClickEvent) event);
        } else if (event instanceof PurchaseEvent) {
            processPurchase((PurchaseEvent) event);
        }
    }
    
    private void processPageView(PageViewEvent event) {
        String dayKey = LocalDate.now().format(DateTimeFormatter.ISO_DATE);
        
        // 1. 总PV统计
        redisTemplate.opsForHyperLogLog().add(PAGE_VIEWS_KEY + ":" + dayKey, event.getEventId());
        
        // 2. UV统计(按设备ID)
        redisTemplate.opsForHyperLogLog().add(UNIQUE_VISITORS_KEY + ":" + dayKey, event.getDeviceId());
        
        // 3. 记录用户会话路径
        if (event.getUserId() != null) {
            String pathKey = USER_SESSION_PATH_KEY + event.getUserId();
            redisTemplate.opsForList().rightPush(pathKey, event.getPageUrl());
            redisTemplate.expire(pathKey, 24, TimeUnit.HOURS); // 保留24小时
        }
        
        // 4. 如果是商品页,增加商品热度
        if (event.getProductId() != null) {
            redisTemplate.opsForZSet().incrementScore(
                PRODUCT_HOT_RANK_KEY, 
                event.getProductId().toString(), 
                1 + (event.getDuration() != null ? event.getDuration() / 10000.0 : 0)
            );
        }
    }
    
    private void processProductClick(ProductClickEvent event) {
        // 1. 商品点击计数
        redisTemplate.opsForHash().increment(
            PRODUCT_CLICKS_KEY, 
            event.getProductId().toString(), 
            1
        );
        
        // 2. 增加商品热度
        redisTemplate.opsForZSet().incrementScore(
            PRODUCT_HOT_RANK_KEY, 
            event.getProductId().toString(), 
            2 // 点击比浏览权重更高
        );
    }
    
    private void processPurchase(PurchaseEvent event) {
        // 1. 处理每个购买商品
        for (PurchaseItem item : event.getItems()) {
            // 商品销量统计
            redisTemplate.opsForHash().increment(
                "stats:product_sales", 
                item.getProductId().toString(), 
                item.getQuantity()
            );
            
            // 增加商品热度(购买权重最高)
            redisTemplate.opsForZSet().incrementScore(
                PRODUCT_HOT_RANK_KEY, 
                item.getProductId().toString(), 
                10 * item.getQuantity()
            );
        }
    }
    
    /**
     * 获取实时PV数据
     */
    public Long getPageViews(String date) {
        return redisTemplate.opsForHyperLogLog().size(PAGE_VIEWS_KEY + ":" + date);
    }
    
    /**
     * 获取实时UV数据
     */
    public Long getUniqueVisitors(String date) {
        return redisTemplate.opsForHyperLogLog().size(UNIQUE_VISITORS_KEY + ":" + date);
    }
    
    /**
     * 获取热门商品排行榜
     */
    public List<ProductRank> getHotProducts(int topN) {
        Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
            .reverseRangeWithScores(PRODUCT_HOT_RANK_KEY, 0, topN - 1);
        
        return tuples.stream()
            .map(tuple -> new ProductRank(
                Long.parseLong(tuple.getValue().toString()),
                tuple.getScore()))
            .collect(Collectors.toList());
    }
    
    /**
     * 获取商品点击量
     */
    public Long getProductClicks(Long productId) {
        Object clicks = redisTemplate.opsForHash().get(PRODUCT_CLICKS_KEY, productId.toString());
        return clicks != null ? Long.parseLong(clicks.toString()) : 0L;
    }
    
    /**
     * 获取用户行为路径
     */
    public List<String> getUserSessionPath(Long userId) {
        List<Object> path = redisTemplate.opsForList().range(USER_SESSION_PATH_KEY + userId, 0, -1);
        return path != null ? path.stream()
            .map(Object::toString)
            .collect(Collectors.toList()) : Collections.emptyList();
    }
    
    @Data
    @AllArgsConstructor
    public static class ProductRank {
        private Long productId;
        private Double hotScore;
    }
}

③、事件消费服务

java 复制代码
@Service
public class UserEventConsumer {
    
    private static final Logger logger = LoggerFactory.getLogger(UserEventConsumer.class);
    private final RealtimeAnalyticsService analyticsService;
    private final ObjectMapper objectMapper;
    
    @Autowired
    public UserEventConsumer(RealtimeAnalyticsService analyticsService, ObjectMapper objectMapper) {
        this.analyticsService = analyticsService;
        this.objectMapper = objectMapper;
    }
    
    @KafkaListener(topics = "user_events")
    public void consumeUserEvent(ConsumerRecord<String, String> record) {
        try {
            JsonNode root = objectMapper.readTree(record.value());
            String eventType = root.path("eventType").asText();
            String eventData = root.path("data").toString();
            
            UserEvent event = null;
            switch (eventType) {
                case "pageview":
                    event = objectMapper.readValue(eventData, PageViewEvent.class);
                    break;
                case "productclick":
                    event = objectMapper.readValue(eventData, ProductClickEvent.class);
                    break;
                case "purchase":
                    event = objectMapper.readValue(eventData, PurchaseEvent.class);
                    break;
                default:
                    logger.warn("Unknown event type: {}", eventType);
            }
            
            if (event != null) {
                analyticsService.processUserEvent(event);
            }
        } catch (Exception e) {
            logger.error("Error processing user event", e);
        }
    }
}

④、数据展示控制器

java 复制代码
@RestController
@RequestMapping("/api/analytics")
public class AnalyticsController {
    
    private final RealtimeAnalyticsService analyticsService;
    private final ProductService productService;
    
    @Autowired
    public AnalyticsController(RealtimeAnalyticsService analyticsService, 
                             ProductService productService) {
        this.analyticsService = analyticsService;
        this.productService = productService;
    }
    
    @GetMapping("/today-stats")
    public ResponseEntity<Map<String, Object>> getTodayStats() {
        String today = LocalDate.now().format(DateTimeFormatter.ISO_DATE);
        
        Map<String, Object> stats = new HashMap<>();
        stats.put("pageViews", analyticsService.getPageViews(today));
        stats.put("uniqueVisitors", analyticsService.getUniqueVisitors(today));
        
        return ResponseEntity.ok(stats);
    }
    
    @GetMapping("/hot-products")
    public ResponseEntity<List<HotProductDTO>> getHotProducts(
            @RequestParam(defaultValue = "10") int topN) {
        
        List<RealtimeAnalyticsService.ProductRank> ranks = 
            analyticsService.getHotProducts(topN);
        
        List<HotProductDTO> result = ranks.stream()
            .map(rank -> {
                Product product = productService.getProductById(rank.getProductId());
                HotProductDTO dto = new HotProductDTO();
                dto.setProductId(rank.getProductId());
                dto.setProductName(product != null ? product.getName() : "Unknown");
                dto.setHotScore(rank.getHotScore());
                dto.setClicks(analyticsService.getProductClicks(rank.getProductId()));
                return dto;
            })
            .collect(Collectors.toList());
        
        return ResponseEntity.ok(result);
    }
    
    @GetMapping("/user-path/{userId}")
    public ResponseEntity<List<String>> getUserPath(@PathVariable Long userId) {
        return ResponseEntity.ok(analyticsService.getUserSessionPath(userId));
    }
    
    @Data
    public static class HotProductDTO {
        private Long productId;
        private String productName;
        private Double hotScore;
        private Long clicks;
    }
}
相关推荐
tuokuac16 分钟前
maven与maven-archetype-plugin版本匹配问题
java·maven
ankleless31 分钟前
Spring Boot 实战:从项目搭建到部署优化
java·spring boot·后端
野生技术架构师1 小时前
2025年中高级后端开发Java岗八股文最新开源
java·开发语言
静若繁花_jingjing1 小时前
JVM常量池
java·开发语言·jvm
David爱编程2 小时前
为什么线程不是越多越好?一文讲透上下文切换成本
java·后端
csxin2 小时前
Spring Boot 中如何设置 serializer 的 TimeZone
java·后端
杨过过儿3 小时前
【Task02】:四步构建简单rag(第一章3节)
android·java·数据库
青云交3 小时前
Java 大视界 -- Java 大数据分布式计算在基因测序数据分析与精准医疗中的应用(400)
java·hadoop·spark·分布式计算·基因测序·java 大数据·精准医疗
荔枝爱编程3 小时前
如何在 Docker 容器中使用 Arthas 监控 Java 应用
java·后端·docker