SpringBoot + Redis + Kafka + MongoDB:高稳定车辆GPS数据处理实战

712 阅读3分钟

SpringBoot + Redis + Kafka + MongoDB:高稳定车辆GPS数据处理实战

作为拥有八年经验的Java技术组长,我将分享一个高稳定性的车辆GPS数据处理方案。系统核心在于双写一致性保障百万级数据处理能力的设计。


场景核心流程

车辆GPS设备 → Kafka实时数据流 → SpringBoot消费 → 并行写入MongoDB和Redis

一、技术选型背后的思考

  1. Kafka:承接车辆高频GPS数据(支持水平扩展+消息持久化)
  2. MongoDB:存储原始轨迹数据(Schema Free+时间序列数据友好)
  3. Redis:提供实时位置查询(内存级读写性能)
  4. SpringBoot:整合生态(简化配置+监控能力)

关键决策点:选择异步双写而非事务,避免分布式事务的性能瓶颈


二、核心架构设计(抗百万级QPS)

graph TD
    A[GPS设备] -->|HTTP/CoAP| B(Kafka集群)
    B --> C[SpringBoot消费者组]
    C --> D[异步写入MongoDB]
    C --> E[异步写入Redis]
    D --> F[MongoDB分片集群]
    E --> G[Redis哨兵集群]

三、关键代码实现(组长级最佳实践)

1. Kafka消费者配置(带容错)

@KafkaListener(topics = "${kafka.topic.vehicle-gps}", groupId = "gps-group")
public void handleGpsData(ConsumerRecord<String, VehicleGps> record) {
    try {
        // 双写操作封装(见步骤2)
        gpsDataService.processAndSave(record.value());
    } catch (Exception e) {
        // 异常处理策略:
        // 1. 记录错误数据到死信队列
        // 2. 触发告警通知
        log.error("GPS数据处理失败: {}", record.value(), e);
        deadLetterService.saveFailedRecord(record);
    }
}

2. 双写核心逻辑(带降级保护)

@Async("gpsWriteExecutor") // 使用独立线程池
public void processAndSave(VehicleGps gps) {
    // MongoDB写入(带重试)
    mongoTemplate.insert(gps);
    
    // Redis写入(降级策略)
    try {
        String redisKey = "vehicle:gps:" + gps.getVehicleId();
        redisTemplate.opsForValue().set(
            redisKey, 
            gps.toJsonString(), 
            30, TimeUnit.SECONDS // 设置TTL防止内存溢出
        );
    } catch (RedisException e) {
        // Redis故障时降级:
        // 1. 记录日志
        // 2. 触发熔断告警
        log.warn("Redis写入失败,已降级处理", e);
        redisAlarmService.notify(e);
    }
}

3. 线程池配置(稳定性关键)

# application.yml
gps-write-executor:
  core-pool-size: 20
  max-pool-size: 100
  queue-capacity: 1000
  thread-name-prefix: gps-writer-

四、稳定性保障策略(生产环境验证)

1. 写入幂等性设计

// MongoDB使用设备ID+时间戳做唯一索引
@Document(collection = "vehicle_gps")
public class VehicleGps {
    @Indexed(name = "uidx_vehicle_ts", unique = true)
    private String vehicleId;
    private long timestamp; 
}

2. 压力保护三剑客

策略实现方式效果
背压控制Kafka消费者限速(max.poll.records)防止消费者过载
熔断降级Spring Cloud CircuitBreakerRedis不可用时跳过写入
队列监控Micrometer+Prometheus实时监控写入队列积压情况

3. 灾难恢复方案

  • Redis数据重建:通过MongoDB的oplog实时同步
  • 消息回溯:Kafka offset手动重置能力
  • 双集群热备:Redis主从集群+哨兵机制

五、性能优化关键点

  1. 批量写入:Redis Pipeline批量更新(提升3-5倍吞吐)

    redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
        gpsList.forEach(gps -> connection.set(
            ("vehicle:gps:"+gps.getVehicleId()).getBytes(),
            gps.toJsonString().getBytes(),
            Expiration.seconds(30),
            RedisStringCommands.SetOption.UPSERT
        ));
        return null;
    });
    
  2. 内存控制:Redis的Hash结构压缩存储

    # 存储优化对比
    String存储:vehicle:gps:1001 → {JSON字符串}
    Hash存储:hset vehicle:gps 1001 {压缩字段}
    # 内存节省40%+
    

六、组长经验之谈

  1. 监控比优化更重要
    • Grafana看板必须包含:Kafka延迟、Redis内存、MongoDB慢查询
  2. 压测是稳定性的基石
    # 使用kafka-producer-perf-test
    bin/kafka-producer-perf-test.sh --topic gps-test \
    --num-records 1000000 --record-size 1024 --throughput -1
    
  3. 失效设计原则
    • 永远假设Redis会挂
    • 永远认为网络会抖动
    • 始终准备消息积压方案

血泪教训:曾在凌晨3点因未设置Redis TTL导致OOM,全组紧急扩容!


最后:架构演进方向

  1. 实时计算:Flink处理超速预警
  2. 分级存储:Elasticsearch实现轨迹检索
  3. 边缘计算:车载设备本地预处理