java
主页 > 软件编程 > java >

springboot+redis实现订单过期(超时取消)功能的方法

2025-12-12 | 佚名 | 点击:

在Spring Boot中使用Redis实现订单过期(超时取消)功能,有多种成熟方案。以下是完整的实现方案:

一、Redis键过期回调方案(推荐)

1. 配置Redis监听器

1

2

3

4

5

6

7

8

9

10

11

@Configuration

public class RedisKeyExpirationConfig {

     

    @Bean

    public RedisMessageListenerContainer redisMessageListenerContainer(

            RedisConnectionFactory connectionFactory) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();

        container.setConnectionFactory(connectionFactory);

        return container;

    }

}

2. 监听键过期事件

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

@Component

@Slf4j

public class OrderExpirationListener {

     

    private static final String ORDER_KEY_PREFIX = "order:";

    private static final String ORDER_EXPIRE_KEY_PREFIX = "order:expire:";

     

    @Autowired

    private RedisTemplate<String, Object> redisTemplate;

     

    @Autowired

    private OrderService orderService;

     

    /**

     * 监听所有键过期事件

     */

    @EventListener

    public void handleKeyExpiredEvent(KeyExpiredEvent<String> event) {

        String expiredKey = new String(event.getSource());

         

        if (expiredKey.startsWith(ORDER_EXPIRE_KEY_PREFIX)) {

            String orderId = expiredKey.substring(ORDER_EXPIRE_KEY_PREFIX.length());

            handleOrderExpired(orderId);

        }

    }

     

    private void handleOrderExpired(String orderId) {

        log.info("检测到订单过期: {}", orderId);

         

        try {

            // 异步处理,避免阻塞Redis监听线程

            CompletableFuture.runAsync(() -> {

                boolean result = orderService.cancelExpiredOrder(orderId);

                if (result) {

                    log.info("订单 {} 已成功取消", orderId);

                } else {

                    log.warn("订单 {} 取消失败或已处理", orderId);

                }

            });

        } catch (Exception e) {

            log.error("处理订单过期异常: {}", orderId, e);

        }

    }

}

3. Redis配置(开启键空间通知)

在Redis配置文件redis.conf中开启键空间通知:

1

2

3

4

5

# 开启键空间通知

notify-keyspace-events Ex

 

# 或者开启所有事件

# notify-keyspace-events AKE

Spring Boot配置:

1

2

3

4

5

6

7

spring:

  redis:

    host: localhost

    port: 6379

    database: 0

    # 监听键过期事件

    listen-patterns: "__keyevent@*__:expired"

二、延时队列方案(Redisson实现)

1. 添加Redisson依赖

1

2

3

4

5

<dependency>

    <groupId>org.redisson</groupId>

    <artifactId>redisson-spring-boot-starter</artifactId>

    <version>3.23.5</version>

</dependency>

2. 使用Redisson延时队列

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

@Service

@Slf4j

public class OrderDelayQueueService {

     

    @Autowired

    private RedissonClient redissonClient;

     

    @Autowired

    private OrderService orderService;

     

    private static final String DELAY_QUEUE_NAME = "order:delay:queue";

    private static final String PROCESSING_SET = "order:delay:processing";

     

    /**

     * 添加订单到延时队列

     * @param orderId 订单ID

     * @param delayTime 延时时间(分钟)

     */

    public void addToDelayQueue(String orderId, long delayTime) {

        RBlockingQueue<String> blockingQueue = redissonClient

            .getBlockingQueue(DELAY_QUEUE_NAME);

        RDelayedQueue<String> delayedQueue = redissonClient

            .getDelayedQueue(blockingQueue);

         

        // 添加到延时队列

        delayedQueue.offer(orderId, delayTime, TimeUnit.MINUTES);

         

        log.info("订单 {} 已添加到延时队列,将在 {} 分钟后过期", orderId, delayTime);

    }

     

    /**

     * 启动延时队列消费者

     */

    @PostConstruct

    public void startDelayQueueConsumer() {

        new Thread(this::consumeDelayQueue, "delay-queue-consumer").start();

    }

     

    private void consumeDelayQueue() {

        RBlockingQueue<String> blockingQueue = redissonClient

            .getBlockingQueue(DELAY_QUEUE_NAME);

         

        while (true) {

            try {

                // 从队列中取出过期的订单

                String orderId = blockingQueue.take();

                 

                // 处理订单过期

                processExpiredOrder(orderId);

                 

            } catch (InterruptedException e) {

                Thread.currentThread().interrupt();

                log.error("延时队列消费线程被中断", e);

                break;

            } catch (Exception e) {

                log.error("处理延时队列消息异常", e);

            }

        }

    }

     

    /**

     * 处理过期订单

     */

    private void processExpiredOrder(String orderId) {

        RSet<String> processingSet = redissonClient.getSet(PROCESSING_SET);

         

        // 使用set防止重复处理

        if (processingSet.add(orderId)) {

            try {

                boolean result = orderService.cancelExpiredOrder(orderId);

                if (result) {

                    log.info("延时队列:订单 {} 已过期取消", orderId);

                } else {

                    log.info("延时队列:订单 {} 无需处理", orderId);

                }

            } finally {

                processingSet.remove(orderId);

            }

        }

    }

}

三、ZSet有序集合方案

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

@Service

@Slf4j

public class OrderExpireZSetService {

     

    @Autowired

    private StringRedisTemplate redisTemplate;

     

    private static final String ORDER_EXPIRE_ZSET = "order:expire:zset";

    private static final String ORDER_PROCESSING_SET = "order:processing:set";

     

    /**

     * 添加订单到过期集合

     * @param orderId 订单ID

     * @param expireTime 过期时间戳

     */

    public void addOrderToExpireSet(String orderId, long expireTime) {

        redisTemplate.opsForZSet().add(ORDER_EXPIRE_ZSET, orderId, expireTime);

        log.info("订单 {} 已添加到过期集合,过期时间: {}", orderId,

                 new Date(expireTime));

    }

     

    /**

     * 批量扫描过期订单

     */

    public void scanExpiredOrders() {

        long now = System.currentTimeMillis();

         

        // 获取已过期的订单

        Set<String> expiredOrders = redisTemplate.opsForZSet()

            .rangeByScore(ORDER_EXPIRE_ZSET, 0, now);

         

        if (expiredOrders != null && !expiredOrders.isEmpty()) {

            for (String orderId : expiredOrders) {

                processExpiredOrder(orderId);

            }

             

            // 移除已处理的订单

            redisTemplate.opsForZSet().removeRangeByScore(

                ORDER_EXPIRE_ZSET, 0, now);

        }

    }

     

    /**

     * 定时扫描任务

     */

    @Scheduled(fixedDelay = 30000) // 每30秒执行一次

    public void scheduledScan() {

        log.debug("开始扫描过期订单");

        scanExpiredOrders();

    }

     

    private void processExpiredOrder(String orderId) {

        // 使用setnx防止重复处理

        Boolean success = redisTemplate.opsForValue()

            .setIfAbsent(ORDER_PROCESSING_SET + ":" + orderId, "1", 5, TimeUnit.MINUTES);

         

        if (Boolean.TRUE.equals(success)) {

            try {

                // 处理订单过期逻辑

                handleOrderExpired(orderId);

            } finally {

                // 清理处理标记

                redisTemplate.delete(ORDER_PROCESSING_SET + ":" + orderId);

            }

        }

    }

     

    private void handleOrderExpired(String orderId) {

        // TODO: 实现订单过期处理逻辑

        log.info("处理过期订单: {}", orderId);

    }

}

四、完整订单服务实现

1. 订单状态枚举

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public enum OrderStatus {

     

    PENDING_PAYMENT(0, "待支付"),

    PAID(1, "已支付"),

    COMPLETED(2, "已完成"),

    CANCELLED(3, "已取消"),

    EXPIRED(4, "已过期");

     

    private final int code;

    private final String description;

     

    OrderStatus(int code, String description) {

        this.code = code;

        this.description = description;

    }

     

    // getter方法省略

}

2. 订单实体

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

@Data

@Entity

@Table(name = "t_order")

public class Order {

     

    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;

     

    @Column(unique = true)

    private String orderNo;

     

    private Long userId;

    private BigDecimal amount;

     

    @Enumerated(EnumType.ORDINAL)

    private OrderStatus status = OrderStatus.PENDING_PAYMENT;

     

    @Column(name = "expire_time")

    private LocalDateTime expireTime;

     

    @Column(name = "create_time")

    private LocalDateTime createTime = LocalDateTime.now();

     

    @Column(name = "update_time")

    private LocalDateTime updateTime = LocalDateTime.now();

     

    /**

     * 判断订单是否已过期

     */

    public boolean isExpired() {

        return LocalDateTime.now().isAfter(expireTime)

            && status == OrderStatus.PENDING_PAYMENT;

    }

}

3. 订单服务实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

@Service

@Slf4j

@Transactional

public class OrderService {

     

    @Autowired

    private OrderRepository orderRepository;

     

    @Autowired

    private StringRedisTemplate redisTemplate;

     

    @Autowired

    private OrderDelayQueueService delayQueueService;

     

    @Autowired

    private OrderExpireZSetService zSetService;

     

    @Autowired

    private ApplicationEventPublisher eventPublisher;

     

    private static final String ORDER_LOCK_PREFIX = "order:lock:";

    private static final String ORDER_EXPIRE_KEY_PREFIX = "order:expire:";

    private static final int ORDER_EXPIRE_MINUTES = 30; // 30分钟未支付过期

     

    /**

     * 创建订单

     */

    public Order createOrder(Long userId, BigDecimal amount) {

        Order order = new Order();

        order.setOrderNo(generateOrderNo());

        order.setUserId(userId);

        order.setAmount(amount);

        order.setStatus(OrderStatus.PENDING_PAYMENT);

        order.setExpireTime(LocalDateTime.now().plusMinutes(ORDER_EXPIRE_MINUTES));

         

        order = orderRepository.save(order);

         

        // 设置Redis过期

        setOrderExpire(order.getOrderNo());

         

        // 添加到延时队列

        delayQueueService.addToDelayQueue(order.getOrderNo(), ORDER_EXPIRE_MINUTES);

         

        // 添加到ZSet

        zSetService.addOrderToExpireSet(

            order.getOrderNo(),

            order.getExpireTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()

        );

         

        log.info("创建订单成功: {}, 过期时间: {}", order.getOrderNo(), order.getExpireTime());

        return order;

    }

     

    /**

     * 设置Redis键过期

     */

    private void setOrderExpire(String orderNo) {

        String key = ORDER_EXPIRE_KEY_PREFIX + orderNo;

        String value = String.valueOf(System.currentTimeMillis());

         

        // 设置30分钟后过期

        redisTemplate.opsForValue().set(

            key,

            value,

            ORDER_EXPIRE_MINUTES,

            TimeUnit.MINUTES

        );

         

        // 同时存储订单信息,用于过期时处理

        Map<String, String> orderInfo = new HashMap<>();

        orderInfo.put("orderNo", orderNo);

        orderInfo.put("userId", "1"); // 实际从订单获取

        orderInfo.put("amount", "100.00");

         

        redisTemplate.opsForHash().putAll(ORDER_KEY_PREFIX + orderNo, orderInfo);

    }

     

    /**

     * 支付成功处理

     */

    public boolean processPayment(String orderNo) {

        // 获取分布式锁

        String lockKey = ORDER_LOCK_PREFIX + orderNo;

        Boolean locked = redisTemplate.opsForValue()

            .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);

         

        if (Boolean.FALSE.equals(locked)) {

            throw new RuntimeException("订单处理中,请稍后");

        }

         

        try {

            Order order = orderRepository.findByOrderNo(orderNo)

                .orElseThrow(() -> new RuntimeException("订单不存在"));

             

            // 检查订单状态

            if (order.getStatus() != OrderStatus.PENDING_PAYMENT) {

                throw new RuntimeException("订单状态异常: " + order.getStatus());

            }

             

            // 检查是否过期

            if (order.isExpired()) {

                order.setStatus(OrderStatus.EXPIRED);

                orderRepository.save(order);

                throw new RuntimeException("订单已过期");

            }

             

            // 更新订单状态

            order.setStatus(OrderStatus.PAID);

            order.setUpdateTime(LocalDateTime.now());

            orderRepository.save(order);

             

            // 移除过期设置

            removeOrderExpire(orderNo);

             

            // 发布支付成功事件

            eventPublisher.publishEvent(new OrderPaidEvent(this, order));

             

            log.info("订单支付成功: {}", orderNo);

            return true;

             

        } finally {

            // 释放锁

            redisTemplate.delete(lockKey);

        }

    }

     

    /**

     * 处理过期订单

     */

    public boolean cancelExpiredOrder(String orderNo) {

        String lockKey = ORDER_LOCK_PREFIX + orderNo;

        Boolean locked = redisTemplate.opsForValue()

            .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);

         

        if (Boolean.FALSE.equals(locked)) {

            return false;

        }

         

        try {

            Order order = orderRepository.findByOrderNo(orderNo)

                .orElseThrow(() -> new RuntimeException("订单不存在"));

             

            // 双重检查:订单是否仍为待支付状态

            if (order.getStatus() != OrderStatus.PENDING_PAYMENT) {

                log.info("订单 {} 状态已变更为 {},跳过取消", orderNo, order.getStatus());

                return false;

            }

             

            // 检查是否真的过期

            if (!order.isExpired()) {

                log.info("订单 {} 未过期,跳过取消", orderNo);

                return false;

            }

             

            // 更新订单状态

            order.setStatus(OrderStatus.EXPIRED);

            order.setUpdateTime(LocalDateTime.now());

            orderRepository.save(order);

             

            // 释放库存等业务逻辑

            releaseStock(order);

             

            // 发送通知

            sendExpireNotification(order);

             

            log.info("订单 {} 已过期取消", orderNo);

            return true;

             

        } finally {

            redisTemplate.delete(lockKey);

        }

    }

     

    /**

     * 移除订单过期设置

     */

    private void removeOrderExpire(String orderNo) {

        // 删除过期key

        redisTemplate.delete(ORDER_EXPIRE_KEY_PREFIX + orderNo);

         

        // 从延时队列移除

        // 注意:Redisson延时队列不支持直接移除,需要其他方式

         

        // 从ZSet移除

        redisTemplate.opsForZSet().remove(ORDER_EXPIRE_ZSET, orderNo);

         

        // 删除订单缓存

        redisTemplate.delete(ORDER_KEY_PREFIX + orderNo);

    }

     

    /**

     * 生成订单号

     */

    private String generateOrderNo() {

        // 时间戳 + 随机数

        return "ORD" +

               System.currentTimeMillis() +

               String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));

    }

     

    private void releaseStock(Order order) {

        // 释放库存逻辑

        log.info("释放订单 {} 的库存", order.getOrderNo());

    }

     

    private void sendExpireNotification(Order order) {

        // 发送通知逻辑

        log.info("发送订单 {} 过期通知", order.getOrderNo());

    }

}

4. 订单支付事件

1

2

3

4

5

6

7

8

9

10

11

12

13

public class OrderPaidEvent extends ApplicationEvent {

     

    private final Order order;

     

    public OrderPaidEvent(Object source, Order order) {

        super(source);

        this.order = order;

    }

     

    public Order getOrder() {

        return order;

    }

}

五、多级过期策略(增强版)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

@Component

@Slf4j

public class OrderExpireManager {

     

    @Autowired

    private OrderService orderService;

     

    @Autowired

    private StringRedisTemplate redisTemplate;

     

    private static final String ORDER_EXPIRE_ZSET = "order:expire:zset";

    private static final String ORDER_EXPIRE_DELAY_QUEUE = "order:expire:delay:queue";

     

    /**

     * 三级过期检测策略

     */

    public void startExpireMonitor() {

        // 1. Redis键过期事件(实时)

        // 2. 定时任务扫描(兜底)

        // 3. 延时队列(精确控制)

         

        new Thread(this::monitorDelayQueue, "order-expire-monitor").start();

        new Thread(this::scheduledScan, "order-expire-scanner").start();

    }

     

    /**

     * 监控延时队列

     */

    private void monitorDelayQueue() {

        while (!Thread.currentThread().isInterrupted()) {

            try {

                // 从延时队列获取订单

                String orderId = redisTemplate.opsForList()

                    .rightPop(ORDER_EXPIRE_DELAY_QUEUE, 1, TimeUnit.SECONDS);

                 

                if (orderId != null) {

                    processExpiredOrder(orderId);

                }

            } catch (Exception e) {

                log.error("监控延时队列异常", e);

            }

        }

    }

     

    /**

     * 定时扫描

     */

    private void scheduledScan() {

        while (!Thread.currentThread().isInterrupted()) {

            try {

                scanExpiredOrders();

                Thread.sleep(30000); // 30秒扫描一次

            } catch (InterruptedException e) {

                Thread.currentThread().interrupt();

                break;

            } catch (Exception e) {

                log.error("定时扫描异常", e);

            }

        }

    }

     

    /**

     * 扫描过期订单

     */

    private void scanExpiredOrders() {

        long now = System.currentTimeMillis();

        Set<String> expiredOrders = redisTemplate.opsForZSet()

            .rangeByScore(ORDER_EXPIRE_ZSET, 0, now);

         

        if (expiredOrders != null) {

            for (String orderId : expiredOrders) {

                processExpiredOrder(orderId);

            }

             

            // 移除已处理的订单

            redisTemplate.opsForZSet().removeRangeByScore(

                ORDER_EXPIRE_ZSET, 0, now);

        }

    }

     

    /**

     * 处理过期订单

     */

    private void processExpiredOrder(String orderId) {

        // 防重处理

        String lockKey = "order:expire:process:" + orderId;

        Boolean locked = redisTemplate.opsForValue()

            .setIfAbsent(lockKey, "1", 5, TimeUnit.MINUTES);

         

        if (Boolean.TRUE.equals(locked)) {

            try {

                boolean result = orderService.cancelExpiredOrder(orderId);

                if (result) {

                    log.info("成功处理过期订单: {}", orderId);

                }

            } finally {

                redisTemplate.delete(lockKey);

            }

        }

    }

}

六、配置类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

@Configuration

@EnableScheduling

public class OrderExpireConfig {

     

    @Bean

    public RedisTemplate<String, Object> redisTemplate(

            RedisConnectionFactory connectionFactory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();

        template.setConnectionFactory(connectionFactory);

         

        // 设置key和value的序列化方式

        template.setKeySerializer(new StringRedisSerializer());

        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());

         

        template.setHashKeySerializer(new StringRedisSerializer());

        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

         

        template.afterPropertiesSet();

        return template;

    }

     

    @Bean

    public OrderExpireManager orderExpireManager() {

        return new OrderExpireManager();

    }

     

    @PostConstruct

    public void init() {

        // 启动过期监控

        orderExpireManager().startExpireMonitor();

    }

}

七、API接口

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

@RestController

@RequestMapping("/api/orders")

@Slf4j

public class OrderController {

     

    @Autowired

    private OrderService orderService;

     

    @PostMapping("/create")

    public ApiResponse<Order> createOrder(@RequestBody CreateOrderRequest request) {

        Order order = orderService.createOrder(

            request.getUserId(),

            request.getAmount()

        );

        return ApiResponse.success(order);

    }

     

    @PostMapping("/{orderNo}/pay")

    public ApiResponse<Void> payOrder(@PathVariable String orderNo) {

        boolean success = orderService.processPayment(orderNo);

        if (success) {

            return ApiResponse.success("支付成功");

        } else {

            return ApiResponse.error("支付失败");

        }

    }

     

    @GetMapping("/{orderNo}/status")

    public ApiResponse<OrderStatus> getOrderStatus(@PathVariable String orderNo) {

        // 从Redis或数据库获取订单状态

        return ApiResponse.success(OrderStatus.PENDING_PAYMENT);

    }

}

八、测试类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

@SpringBootTest

@Slf4j

class OrderExpireTest {

     

    @Autowired

    private OrderService orderService;

     

    @Autowired

    private StringRedisTemplate redisTemplate;

     

    @Test

    void testOrderExpire() throws InterruptedException {

        // 创建订单

        Order order = orderService.createOrder(1L, new BigDecimal("100.00"));

         

        // 验证Redis中已设置过期

        String expireKey = "order:expire:" + order.getOrderNo();

        String value = redisTemplate.opsForValue().get(expireKey);

        assertNotNull(value);

         

        // 验证订单状态

        assertEquals(OrderStatus.PENDING_PAYMENT, order.getStatus());

         

        // 等待订单过期

        Thread.sleep(2000); // 实际应该等30分钟

         

        // 测试支付

        boolean paid = orderService.processPayment(order.getOrderNo());

        assertTrue(paid);

    }

     

    @Test

    void testConcurrentPay() throws InterruptedException {

        Order order = orderService.createOrder(2L, new BigDecimal("200.00"));

         

        ExecutorService executor = Executors.newFixedThreadPool(5);

        CountDownLatch latch = new CountDownLatch(5);

         

        AtomicInteger successCount = new AtomicInteger(0);

         

        for (int i = 0; i < 5; i++) {

            executor.submit(() -> {

                try {

                    boolean result = orderService.processPayment(order.getOrderNo());

                    if (result) {

                        successCount.incrementAndGet();

                    }

                } catch (Exception e) {

                    log.error("支付异常", e);

                } finally {

                    latch.countDown();

                }

            });

        }

         

        latch.await();

        executor.shutdown();

         

        // 只有一个支付成功

        assertEquals(1, successCount.get());

    }

}

九、监控和告警

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

@Component

@Slf4j

public class OrderExpireMonitor {

     

    @Autowired

    private StringRedisTemplate redisTemplate;

     

    @Scheduled(fixedRate = 60000) // 每分钟执行一次

    public void monitorExpireOrders() {

        String expireKeyPattern = "order:expire:*";

         

        // 统计待过期订单数量

        Set<String> keys = redisTemplate.keys(expireKeyPattern);

        long expireCount = keys != null ? keys.size() : 0;

         

        // 统计即将在5分钟内过期的订单

        long soonExpireCount = keys.stream()

            .map(key -> redisTemplate.getExpire(key, TimeUnit.SECONDS))

            .filter(ttl -> ttl != null && ttl > 0 && ttl <= 300)

            .count();

         

        // 记录监控日志

        log.info("订单过期监控 - 待过期订单: {}, 即将过期订单: {}",

                 expireCount, soonExpireCount);

         

        // 发送告警

        if (soonExpireCount > 100) {

            sendAlert("大量订单即将过期: " + soonExpireCount + " 个");

        }

    }

     

    private void sendAlert(String message) {

        // 发送告警到监控系统

        log.warn("订单过期告警: {}", message);

    }

}

总结建议

推荐方案

生产环境推荐组合方案:

方案对比:

注意事项:

性能优化:

这种实现可以确保订单过期功能的可靠性和实时性,适合电商等高并发场景。

原文链接:
相关文章
最新更新