返回顶部
分享到

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

java 来源:互联网 作者:佚名 发布时间:2025-12-12 22:42:56 人浏览
摘要

在Spring Boot中使用Redis实现订单过期(超时取消)功能,有多种成熟方案。以下是完整的实现方案: 一、Redis键过期回调方案(推荐) 1. 配置Redis监听器 1 2 3 4 5 6 7 8 9 10 11 @Configuration public clas

在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);

    }

}

总结建议

推荐方案

生产环境推荐组合方案:

  • 主方案:Redisson延时队列? + Redis键过期回调
  • 兜底方案:定时任务扫描ZSet
  • 防重处理:Redis分布式锁

方案对比:

  • Redis键过期回调:实时性最好,但可靠性依赖Redis配置
  • Redisson延时队列:功能强大,支持分布式,推荐使用
  • ZSet定时扫描:实现简单,但实时性较差
  • 多级策略:最可靠,但实现复杂

注意事项:

  • 一定要配置Redis的notify-keyspace-events Ex
  • 考虑网络分区和Redis故障的情况
  • 实现幂等性处理,防止重复取消
  • 添加监控和告警
  • 考虑持久化,防止重启后数据丢失

性能优化:

  • 使用批量处理过期订单
  • 异步处理过期逻辑
  • 合理设置扫描频率
  • 使用连接池

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


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计