SpringBoot + Redis + Kafka + MongoDB:高稳定车辆GPS数据处理实战
作为拥有八年经验的Java技术组长,我将分享一个高稳定性的车辆GPS数据处理方案。系统核心在于双写一致性保障和百万级数据处理能力的设计。
场景核心流程
车辆GPS设备 → Kafka实时数据流 → SpringBoot消费 → 并行写入MongoDB和Redis
一、技术选型背后的思考
- Kafka:承接车辆高频GPS数据(支持水平扩展+消息持久化)
- MongoDB:存储原始轨迹数据(Schema Free+时间序列数据友好)
- Redis:提供实时位置查询(内存级读写性能)
- 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 CircuitBreaker | Redis不可用时跳过写入 |
队列监控 | Micrometer+Prometheus | 实时监控写入队列积压情况 |
3. 灾难恢复方案
- Redis数据重建:通过MongoDB的oplog实时同步
- 消息回溯:Kafka offset手动重置能力
- 双集群热备:Redis主从集群+哨兵机制
五、性能优化关键点
-
批量写入: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; });
-
内存控制:Redis的Hash结构压缩存储
# 存储优化对比 String存储:vehicle:gps:1001 → {JSON字符串} Hash存储:hset vehicle:gps 1001 {压缩字段} # 内存节省40%+
六、组长经验之谈
- 监控比优化更重要:
- Grafana看板必须包含:Kafka延迟、Redis内存、MongoDB慢查询
- 压测是稳定性的基石:
# 使用kafka-producer-perf-test bin/kafka-producer-perf-test.sh --topic gps-test \ --num-records 1000000 --record-size 1024 --throughput -1
- 失效设计原则:
- 永远假设Redis会挂
- 永远认为网络会抖动
- 始终准备消息积压方案
血泪教训:曾在凌晨3点因未设置Redis TTL导致OOM,全组紧急扩容!
最后:架构演进方向
- 实时计算:Flink处理超速预警
- 分级存储:Elasticsearch实现轨迹检索
- 边缘计算:车载设备本地预处理