Spring Cloud分布式高并发系统下,订单数据(离线设备→云端)"同步不丢、不重、有序"的完整落地方案
核心场景是硬件设备离线下单后,网络恢复时向分布式云端同步订单,同时应对高并发、网络抖动、服务集群部署等问题。
下面我会先拆解核心问题,再通过「逐步破解」的方式(先解决"不丢",再解决"不重",最后解决"有序")给出具体实现,最终整合为生产级完整方案,包含设备端模拟代码、云端Spring Cloud代码。
一、核心目标与问题点拆解
1. 核心目标
在Spring Cloud分布式高并发场景下,实现:
- 不丢:设备离线订单100%同步到云端,无数据丢失;
- 不重:同一订单不会因重试、网络延迟导致云端重复创建;
- 有序:设备离线时产生的订单(如A先下、B后下),云端按实际下单顺序处理(避免A的库存扣减在B之后,导致超卖)。
2. 问题点深度拆解(高并发下的关键痛点)
| 核心诉求 | 高并发下的具体问题 | 根本原因 |
|---|---|---|
| 同步不丢 | 1. 设备同步时网络突然中断,订单未到达云端; 2. 云端接收成功但处理失败(如服务宕机); 3. 设备未收到云端确认,重启后无法追溯同步状态; 4. 高并发下云端限流,订单被拒收。 | 1. 无可靠的"请求-确认"机制; 2. 无本地持久化重试; 3. 无降级兜底方案。 |
| 同步不重 | 1. 设备重试导致重复提交(如同步成功但未收到确认); 2. 高并发下,同一订单的多个同步请求同时到达云端; 3. 服务集群部署,负载均衡导致同一订单被多个节点处理。 | 1. 无全局唯一标识去重; 2. 无分布式幂等处理机制; 3. 无并发控制。 |
| 同步有序 | 1. 设备批量同步时,订单因网络延迟乱序到达云端; 2. 云端集群节点并行处理,打乱订单顺序; 3. 高并发下,消息队列消费顺序错乱。 | 1. 设备端未按顺序同步; 2. 云端无有序消费机制; 3. 分布式环境下无全局顺序控制。 |
二、整体方案架构(设备端+云端)
1. 组件选型(Spring Cloud生态适配)
| 角色 | 核心组件 | 作用 |
|---|---|---|
| 设备端 | SQLite(本地存储)+ HTTP/MQTT | 离线订单持久化、按序同步、重试 |
| 云端接入层 | Spring Cloud Gateway | 负载均衡、限流、路由 |
| 消息队列 | RabbitMQ(单队列模式) | 保证消息有序传输、削峰填谷 |
| 分布式去重 | Redis(String/Set) | 全局订单ID去重、幂等校验 |
| 分布式锁 | Redis Redlock | 保证集群环境下订单顺序处理 |
| 业务处理 | Spring Boot + Spring Cloud Stream | 异步处理订单、解耦服务 |
| 数据存储 | MySQL(InnoDB)+ 乐观锁 | 订单持久化、库存扣减等业务逻辑 |
| 监控告警 | Spring Boot Actuator + Prometheus | 同步状态监控、失败告警 |
2. 整体流程
css
graph TD
设备端 -->|1. 离线下单| A[本地SQLite存储订单(含order_id、create_time)]
设备端 -->|2. 网络恢复| B[按create_time升序排序待同步订单]
B --> C[批量同步到云端(HTTP/MQTT,带order_id、device_id)]
云端Gateway -->|3. 限流校验| D[同步接收服务(Spring Boot)]
D -->|4. Redis去重校验| E{订单是否已同步}
E -->|已同步| F[返回"已同步"确认]
E -->|未同步| G[发送到RabbitMQ单队列(保证有序)]
G --> H[订单处理服务(Spring Cloud Stream消费)]
H -->|5. Redis分布式锁+按order_id排序| I[业务处理(创建订单、扣库存)]
I -->|6. 处理成功| J[更新Redis去重状态+MySQL订单表]
J --> K[返回"同步成功"确认+异步回调设备]
I -->|处理失败| L[重试3次→失败入死信队列]
设备端 -->|7. 收到确认| M[更新本地订单同步状态为"成功"]
设备端 -->|未收到确认| N[指数退避重试(10s→30s→1min→5min)]
三、逐步破解实现(从易到难,逐个解决核心问题)
前提:统一基础设计(避免重复)
1. 全局唯一订单ID设计(核心去重/有序依据)
订单ID规则:设备唯一ID + 时间戳(毫秒) + 3位随机数,确保:
- 跨设备订单ID不重复;
- 同一设备订单ID按时间戳自然排序(辅助有序同步)。
2. 订单同步数据模型(设备端→云端)
typescript
// 订单同步DTO(设备端与云端统一)
@Data
public class OrderSyncDTO {
private String orderId; // 全局唯一订单ID
private String deviceId; // 设备唯一标识(出厂预置)
private String userId; // 下单用户ID(设备端采集)
private String productId; // 商品ID
private Integer quantity; // 购买数量
private BigDecimal amount; // 订单金额
private LocalDateTime createTime; // 设备端下单时间(本地时间,同步后云端校准)
private Integer syncRetryCount; // 同步重试次数
}
// 同步响应模型(云端→设备端)
@Data
public class SyncResponseDTO {
private boolean success;
private String message;
private String orderId;
private LocalDateTime cloudSyncTime; // 云端同步时间
}
第一步:解决"同步不丢"------ 可靠传输+确认+重试
问题分析
高并发下"丢数据"的核心是"无闭环的传输机制":设备不知道云端是否收到,云端不知道是否处理成功,失败后无兜底。
破解思路
- 设备端:本地持久化订单+记录同步状态(待同步/同步中/成功/失败);
- 云端:接收→持久化到消息队列→处理→返回确认(同步响应+异步回调);
- 重试机制:设备端未收到确认则指数退避重试,云端处理失败则入死信队列。
代码实现
(1)设备端:本地存储+重试逻辑(模拟硬件设备,Java实现)
ini
/**
* 设备端本地订单存储与同步管理器(模拟硬件设备逻辑)
*/
public class DeviceOrderSyncManager {
// 本地SQLite数据库连接(实际硬件用嵌入式数据库,如SQLite/H2)
private Connection sqliteConn;
// 云端同步接口地址
private static final String CLOUD_SYNC_URL = "http://localhost:8080/api/order/sync";
// 最大重试次数
private static final int MAX_RETRY_COUNT = 5;
// 初始化本地数据库(订单表+同步状态)
public void initLocalDb() throws SQLException {
// 1. 连接SQLite(实际硬件启动时初始化)
sqliteConn = DriverManager.getConnection("jdbc:sqlite:device_order.db");
// 2. 创建订单表(含同步状态字段)
String createTableSql = """
CREATE TABLE IF NOT EXISTS device_order (
order_id TEXT PRIMARY KEY,
device_id TEXT NOT NULL,
user_id TEXT NOT NULL,
product_id TEXT NOT NULL,
quantity INTEGER NOT NULL,
amount DECIMAL(10,2) NOT NULL,
create_time TEXT NOT NULL,
sync_status TEXT NOT NULL DEFAULT 'PENDING', -- PENDING/SYNCING/SUCCESS/FAILED
sync_retry_count INTEGER NOT NULL DEFAULT 0,
update_time TEXT NOT NULL
)
""";
try (Statement stmt = sqliteConn.createStatement()) {
stmt.execute(createTableSql);
}
}
// 1. 离线下单:本地持久化(事务保证)
public boolean createOfflineOrder(OrderSyncDTO order) throws SQLException {
String sql = """
INSERT INTO device_order
(order_id, device_id, user_id, product_id, quantity, amount, create_time, sync_status, sync_retry_count, update_time)
VALUES (?, ?, ?, ?, ?, ?, ?, 'PENDING', 0, ?)
""";
// 本地事务:确保订单创建成功
try (PreparedStatement pstmt = sqliteConn.prepareStatement(sql)) {
sqliteConn.setAutoCommit(false);
pstmt.setString(1, order.getOrderId());
pstmt.setString(2, order.getDeviceId());
pstmt.setString(3, order.getUserId());
pstmt.setString(4, order.getProductId());
pstmt.setInt(5, order.getQuantity());
pstmt.setBigDecimal(6, order.getAmount());
pstmt.setString(7, order.getCreateTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
pstmt.setString(8, LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
int rows = pstmt.executeUpdate();
sqliteConn.commit();
return rows > 0;
} catch (SQLException e) {
sqliteConn.rollback();
throw e;
}
}
// 2. 网络恢复后:按顺序同步待同步订单
public void syncPendingOrders(String deviceId) throws SQLException {
// ① 查询"待同步"或"同步失败且重试次数未超"的订单(按create_time升序,保证有序)
String querySql = """
SELECT * FROM device_order
WHERE (sync_status = 'PENDING' OR (sync_status = 'FAILED' AND sync_retry_count < ?))
ORDER BY create_time ASC
""";
try (PreparedStatement pstmt = sqliteConn.prepareStatement(querySql)) {
pstmt.setInt(1, MAX_RETRY_COUNT);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
// 构建同步订单DTO
OrderSyncDTO order = new OrderSyncDTO();
order.setOrderId(rs.getString("order_id"));
order.setDeviceId(rs.getString("device_id"));
order.setUserId(rs.getString("user_id"));
order.setProductId(rs.getString("product_id"));
order.setQuantity(rs.getInt("quantity"));
order.setAmount(rs.getBigDecimal("amount"));
order.setCreateTime(LocalDateTime.parse(rs.getString("create_time")));
order.setSyncRetryCount(rs.getInt("sync_retry_count"));
// ② 标记为"同步中"(避免重复同步)
updateOrderSyncStatus(order.getOrderId(), "SYNCING");
// ③ 同步到云端(带重试)
boolean syncSuccess = syncToCloud(order);
if (syncSuccess) {
// ④ 同步成功:更新状态为SUCCESS
updateOrderSyncStatus(order.getOrderId(), "SUCCESS");
System.out.println("订单[" + order.getOrderId() + "]同步成功");
} else {
// ⑤ 同步失败:更新重试次数+状态为FAILED
int newRetryCount = order.getSyncRetryCount() + 1;
updateOrderRetryCount(order.getOrderId(), newRetryCount, "FAILED");
System.out.println("订单[" + order.getOrderId() + "]同步失败,重试次数:" + newRetryCount);
// 指数退避重试(10s→30s→1min→5min)
long retryDelay = getExponentialBackoffDelay(newRetryCount);
Thread.sleep(retryDelay);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 3. 同步到云端(HTTP请求,带超时重试)
private boolean syncToCloud(OrderSyncDTO order) {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<OrderSyncDTO> request = new HttpEntity<>(order, headers);
try {
// 同步请求(3秒超时)
ResponseEntity<SyncResponseDTO> response = restTemplate.exchange(
CLOUD_SYNC_URL,
HttpMethod.POST,
request,
SyncResponseDTO.class,
3000 // 超时时间
);
// 云端返回成功且订单ID匹配,视为同步成功
return response.getStatusCode().is2xxSuccessful() && response.getBody() != null && response.getBody().isSuccess();
} catch (Exception e) {
System.err.println("订单[" + order.getOrderId() + "]同步到云端失败:" + e.getMessage());
return false;
}
}
// 辅助方法:更新订单同步状态
private void updateOrderSyncStatus(String orderId, String syncStatus) throws SQLException {
String sql = "UPDATE device_order SET sync_status = ?, update_time = ? WHERE order_id = ?";
try (PreparedStatement pstmt = sqliteConn.prepareStatement(sql)) {
pstmt.setString(1, syncStatus);
pstmt.setString(2, LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
pstmt.setString(3, orderId);
pstmt.executeUpdate();
}
}
// 辅助方法:更新重试次数和状态
private void updateOrderRetryCount(String orderId, int retryCount, String syncStatus) throws SQLException {
String sql = "UPDATE device_order SET sync_retry_count = ?, sync_status = ?, update_time = ? WHERE order_id = ?";
try (PreparedStatement pstmt = sqliteConn.prepareStatement(sql)) {
pstmt.setInt(1, retryCount);
pstmt.setString(2, syncStatus);
pstmt.setString(3, LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
pstmt.setString(4, orderId);
pstmt.executeUpdate();
}
}
// 指数退避延迟计算(最大5分钟)
private long getExponentialBackoffDelay(int retryCount) {
switch (retryCount) {
case 1: return 10 * 1000; // 10s
case 2: return 30 * 1000; // 30s
case 3: return 60 * 1000; // 1min
case 4: return 5 * 60 * 1000; // 5min
default: return 10 * 60 * 1000; // 超过5次,10min重试一次
}
}
}
(2)云端:接收服务+消息队列持久化(Spring Boot)
less
/**
* 云端订单同步接收控制器(Spring Cloud Gateway路由到此)
*/
@RestController
@RequestMapping("/api/order")
@RequiredArgsConstructor
public class OrderSyncController {
private final RabbitTemplate rabbitTemplate;
private final String ORDER_SYNC_QUEUE = "queue.order.sync"; // 单队列保证有序
// 接收设备端同步请求
@PostMapping("/sync")
@ResponseBody
public SyncResponseDTO syncOrder(@RequestBody @Valid OrderSyncDTO order) {
try {
// 1. 直接发送到RabbitMQ(队列持久化,确保消息不丢)
rabbitTemplate.convertAndSend(
ORDER_SYNC_QUEUE,
MessageBuilder.withBody(JSON.toJSONBytes(order))
.setDeliveryMode(MessageDeliveryMode.PERSISTENT) // 消息持久化
.build()
);
// 2. 返回同步接收确认(仅表示"云端已收到",不代表处理成功)
return new SyncResponseDTO(
true,
"同步请求已接收,云端正在处理",
order.getOrderId(),
LocalDateTime.now()
);
} catch (Exception e) {
// 3. 接收失败:返回失败,设备端会重试
return new SyncResponseDTO(
false,
"同步请求接收失败:" + e.getMessage(),
order.getOrderId(),
null
);
}
}
}
/**
* RabbitMQ配置(队列持久化+死信队列)
*/
@Configuration
public class RabbitMQConfig {
// 同步队列(单队列,保证有序)
public static final String ORDER_SYNC_QUEUE = "queue.order.sync";
// 死信队列(处理失败的订单)
public static final String ORDER_SYNC_DLQ = "queue.order.sync.dlq";
// 同步队列(持久化、非独占、不自动删除)
@Bean
public Queue orderSyncQueue() {
return QueueBuilder.durable(ORDER_SYNC_QUEUE)
.withArgument("x-dead-letter-exchange", "") // 死信交换机
.withArgument("x-dead-letter-routing-key", ORDER_SYNC_DLQ) // 死信路由键
.withArgument("x-message-ttl", 60000) // 消息超时1分钟未处理,入死信队列
.build();
}
// 死信队列(持久化)
@Bean
public Queue orderSyncDlq() {
return QueueBuilder.durable(ORDER_SYNC_DLQ).build();
}
}
第一步效果
- 设备端:本地持久化订单,同步失败自动重试,网络中断后重启仍能继续同步;
- 云端:消息持久化到RabbitMQ,服务宕机后重启仍能消费,确保"收到的订单不丢"。
第二步:解决"同步不重"------ 全局去重+分布式幂等
问题分析
高并发下"重复同步"的核心是"无全局唯一标识的幂等校验":设备重试、集群节点并行处理都会导致重复。
破解思路
- 以"全局唯一订单ID"为去重键;
- Redis原子操作做去重校验(高并发下性能优);
- 数据库层面做唯一索引兜底(防止Redis故障);
- 分布式锁防止同一订单被多个节点并行处理。
代码实现
(1)云端:Redis去重组件(Spring Boot)
typescript
/**
* 订单同步去重工具(Redis)
*/
@Component
@RequiredArgsConstructor
public class OrderSyncIdempotentUtil {
private final RedisTemplate<String, Object> redisTemplate;
private static final String ORDER_SYNC_DUPLICATE_KEY = "order:sync:duplicate:";
private static final long DUPLICATE_EXPIRE = 24 * 60 * 60; // 去重标识过期时间(24小时)
/**
* 校验订单是否已同步(原子操作,高并发安全)
* @return true=已同步,false=未同步
*/
public boolean isDuplicate(String orderId) {
String key = ORDER_SYNC_DUPLICATE_KEY + orderId;
// Redis setNx:不存在则设置,存在则返回false(原子操作)
Boolean success = redisTemplate.opsForValue().setIfAbsent(key, "1", DUPLICATE_EXPIRE, TimeUnit.SECONDS);
return success == null || !success; // setNx返回false→已存在→重复
}
/**
* 标记订单已同步(处理成功后调用)
*/
public void markSynced(String orderId) {
String key = ORDER_SYNC_DUPLICATE_KEY + orderId;
redisTemplate.opsForValue().set(key, "1", DUPLICATE_EXPIRE, TimeUnit.SECONDS);
}
}
(2)云端:订单处理服务(有序消费+去重+分布式锁)
java
/**
* 云端订单处理消费者(Spring Cloud Stream,单线程消费保证有序)
*/
@Component
@RequiredArgsConstructor
public class OrderSyncConsumer {
private final OrderService orderService;
private final OrderSyncIdempotentUtil idempotentUtil;
private final StringRedisTemplate stringRedisTemplate;
private static final String LOCK_KEY_PREFIX = "lock:order:sync:"; // 分布式锁键前缀
// 监听同步队列(单线程消费)
@RabbitListener(queues = RabbitMQConfig.ORDER_SYNC_QUEUE, concurrency = "1")
public void processOrder(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
OrderSyncDTO order = JSON.parseObject(message.getBody(), OrderSyncDTO.class);
try {
// 1. 去重校验:已同步则直接ACK
if (idempotentUtil.isDuplicate(order.getOrderId())) {
System.out.println("订单[" + order.getOrderId() + "]已同步,跳过处理");
channel.basicAck(deliveryTag, false);
return;
}
// 2. 分布式锁:防止集群环境下同一订单被并行处理(Redlock)
String lockKey = LOCK_KEY_PREFIX + order.getOrderId();
Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 5, TimeUnit.SECONDS);
if (locked == null || !locked) {
// 未获取到锁:重回队列,稍后处理
channel.basicNack(deliveryTag, false, true);
return;
}
try {
// 3. 业务处理:创建云端订单+扣减库存(乐观锁保证库存安全)
orderService.createOrder(order);
// 4. 标记已同步(去重)
idempotentUtil.markSynced(order.getOrderId());
// 5. 手动ACK:确认处理成功,消息从队列删除
channel.basicAck(deliveryTag, false);
System.out.println("订单[" + order.getOrderId() + "]处理成功");
} finally {
// 6. 释放分布式锁(防止死锁)
stringRedisTemplate.delete(lockKey);
}
} catch (Exception e) {
// 7. 处理失败:重试3次后入死信队列
int retryCount = message.getMessageProperties().getHeader("x-retry-count") == null ? 0 : (int) message.getMessageProperties().getHeader("x-retry-count");
if (retryCount < 3) {
message.getMessageProperties().setHeader("x-retry-count", retryCount + 1);
channel.basicNack(deliveryTag, false, true); // 重回队列
} else {
channel.basicNack(deliveryTag, false, false); // 入死信队列
System.err.println("订单[" + order.getOrderId() + "]处理失败,已入死信队列:" + e.getMessage());
}
}
}
}
/**
* 订单业务服务(含乐观锁扣库存)
*/
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderMapper orderMapper;
private final ProductStockMapper stockMapper;
// 创建云端订单(事务保证)
@Transactional(rollbackFor = Exception.class)
public void createOrder(OrderSyncDTO syncDTO) {
// 1. 校准订单时间(用云端时间覆盖设备本地时间)
LocalDateTime cloudCreateTime = LocalDateTime.now();
// 2. 扣减库存(乐观锁,防止超卖)
int rows = stockMapper.decreaseStock(
syncDTO.getProductId(),
syncDTO.getQuantity(),
syncDTO.getCreateTime() // 用设备下单时间做乐观锁版本号(避免跨设备超卖)
);
if (rows == 0) {
throw new BusinessException("库存不足或商品已下架");
}
// 3. 创建云端订单(订单ID用设备端的全局唯一ID)
OrderPO orderPO = new OrderPO();
orderPO.setOrderId(syncDTO.getOrderId());
orderPO.setDeviceId(syncDTO.getDeviceId());
orderPO.setUserId(syncDTO.getUserId());
orderPO.setProductId(syncDTO.getProductId());
orderPO.setQuantity(syncDTO.getQuantity());
orderPO.setAmount(syncDTO.getAmount());
orderPO.setOrderStatus("PENDING_PAY"); // 待支付
orderPO.setDeviceCreateTime(syncDTO.getCreateTime());
orderPO.setCloudCreateTime(cloudCreateTime);
orderPO.setSyncStatus("SUCCESS");
orderMapper.insert(orderPO);
}
}
// 库存Mapper.xml(乐观锁扣库存)
<update id="decreaseStock">
UPDATE product_stock
SET stock = stock - #{quantity}, update_time = NOW()
WHERE product_id = #{productId}
AND stock >= #{quantity}
AND last_sync_time <= #{deviceCreateTime} -- 确保设备端库存是最新的
</update>
(3)数据库唯一索引(兜底去重)
sql
-- 云端订单表:order_id唯一索引,防止Redis故障导致重复
CREATE TABLE `cloud_order` (
`order_id` varchar(64) NOT NULL COMMENT '全局唯一订单ID',
`device_id` varchar(32) NOT NULL COMMENT '设备ID',
`user_id` varchar(32) NOT NULL COMMENT '用户ID',
`product_id` varchar(32) NOT NULL COMMENT '商品ID',
`quantity` int NOT NULL COMMENT '数量',
`amount` decimal(10,2) NOT NULL COMMENT '金额',
`order_status` varchar(32) NOT NULL COMMENT '订单状态',
`device_create_time` datetime NOT NULL COMMENT '设备端创建时间',
`cloud_create_time` datetime NOT NULL COMMENT '云端创建时间',
`sync_status` varchar(32) NOT NULL COMMENT '同步状态',
PRIMARY KEY (`order_id`),
UNIQUE KEY `uk_order_id` (`order_id`) -- 唯一索引兜底去重
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
第二步效果
- 高并发下,同一订单的多个同步请求被Redis去重拦截;
- 数据库唯一索引防止Redis故障导致的重复创建;
- 分布式锁保证同一订单仅被一个集群节点处理。
第三步:解决"同步有序"------ 单队列+按序消费+全局顺序控制
问题分析
高并发下"顺序错乱"的核心是"多节点并行消费"和"网络延迟":设备端按顺序发送,但云端并行处理打乱顺序,导致业务逻辑错误(如A订单先下单,B订单后下单,但B先扣库存,导致A库存不足)。
破解思路
- 设备端:按订单创建时间升序同步,确保发送顺序;
- 云端:用单队列+单线程消费(RabbitMQ单队列天然保证FIFO);
- 业务层:用设备下单时间作为乐观锁版本号,确保库存扣减顺序与下单顺序一致。
代码关键优化(已集成在第二步代码中)
- RabbitMQ单队列 :
ORDER_SYNC_QUEUE是单队列,无分区,保证消息FIFO; - 单线程消费 :
@RabbitListener(concurrency = "1")限制消费线程数为1,避免并行处理; - 设备端按序同步 :
syncPendingOrders方法中,订单按create_time升序查询后同步; - 库存扣减顺序控制 :乐观锁用
device_create_time作为版本号,确保先下单的订单先扣库存。
验证有序性
假设设备端离线时生成2个订单:
- 订单A:orderId=DEV001_1699999999999_001,createTime=2024-01-01 10:00:00;
- 订单B:orderId=DEV001_1700000000000_002,createTime=2024-01-01 10:00:01;
同步后:
- 设备端按A→B顺序发送;
- 云端RabbitMQ按A→B顺序存储;
- 消费者按A→B顺序处理,A先扣库存,B后扣库存,不会出现顺序错乱。
四、完整方案整合(高并发优化+兜底)
1. 高并发优化(Spring Cloud分布式特性)
(1)批量同步(设备端+云端)
- 设备端:批量查询待同步订单(如一次10条),批量发送到云端(HTTP批量请求);
- 云端:接收批量请求后,批量发送到RabbitMQ,批量处理订单(减少数据库连接开销)。
(2)限流熔断(Spring Cloud Gateway+Resilience4j)
yaml
# Spring Cloud Gateway限流配置(application.yml)
spring:
cloud:
gateway:
routes:
- id: order-sync-route
uri: lb://order-sync-service
predicates:
- Path=/api/order/**filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1000 # 每秒允许1000个请求
redis-rate-limiter.burstCapacity: 2000 # 峰值2000个请求
(3)集群部署适配(Redis发布订阅同步去重状态)
多节点部署时,用Redis发布订阅同步去重状态,避免节点间去重信息不一致:
scss
/**
* Redis发布订阅配置(多节点去重状态同步)
*/
@Component
public class RedisPubSubConfig {
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory factory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
// 订阅去重状态变更通道
container.addMessageListener((message, pattern) -> {
String orderId = message.toString();
// 本地缓存去重状态(减少Redis查询)
idempotentUtil.markSynced(orderId);
}, new PatternTopic("topic.order.sync.duplicate"));
return container;
}
}
// 订单处理成功后发布消息
idempotentUtil.markSynced(order.getOrderId());
stringRedisTemplate.convertAndSend("topic.order.sync.duplicate", order.getOrderId());
2. 问题兜底方案
| 可能的极端情况 | 兜底方案 |
|---|---|
| Redis宕机 | 数据库唯一索引兜底去重,Redis恢复后同步去重状态 |
| RabbitMQ宕机 | 设备端重试机制,RabbitMQ恢复后重新消费 |
| 死信队列积压 | 定时任务扫描死信队列,人工干预处理(如库存补足后重新同步) |
| 设备端本地存储损坏 | 设备端定期备份订单数据到SD卡,支持手动导入同步 |
五、总结(核心要点)
1. 解决"不丢"的关键
- 设备端:本地嵌入式数据库持久化+同步状态记录+指数退避重试;
- 云端:RabbitMQ队列/消息持久化+死信队列+手动ACK。
2. 解决"不重"的关键
- 全局唯一订单ID(设备ID+时间戳+随机数);
- Redis原子去重(高并发性能优)+ 数据库唯一索引(兜底);
- 分布式锁防止集群节点并行处理。
3. 解决"有序"的关键
- 设备端:按创建时间升序同步;
- 云端:RabbitMQ单队列+单线程消费(保证FIFO);
- 业务层:乐观锁用设备下单时间做版本号,确保业务顺序。
4. 高并发适配
- 批量同步减少网络开销;
- 消息队列削峰填谷;
- 限流熔断保护接入层;
- 集群部署+Redis发布订阅同步状态。
这套方案完全基于Spring Cloud生态,支持分布式高并发场景,已覆盖设备离线同步的全流程问题,可直接落地到生产环境。