消息幂等性处理实现

消息幂等性处理实现

1. 基于数据库实现幂等性
@Service
@Slf4j
public class MessageProcessor {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void processMessageWithDB(String messageId, String content) {
        // 1. 查询消息是否已处理
        String sql = "SELECT COUNT(*) FROM message_record WHERE message_id = ?";
        int count = jdbcTemplate.queryForObject(sql, Integer.class, messageId);
        if (count > 0) {
            log.info("Message already processed, messageId: {}", messageId);
            return;
        }
        // 2. 处理业务逻辑
        try {
            // 执行业务处理
            processBusinessLogic(content);
            // 3. 记录消息处理状态
            String insertSql = "INSERT INTO message_record (message_id, status, create_time) VALUES (?, ?, ?)";
            jdbcTemplate.update(insertSql, messageId, "SUCCESS", new Date());
        } catch (Exception e) {
            // 记录失败状态
            String insertSql = "INSERT INTO message_record (message_id, status, create_time, error_msg) VALUES (?, ?, ?, ?)";
            jdbcTemplate.update(insertSql, messageId, "FAILED", new Date(), e.getMessage());
            throw e;
        }
    }
}
2. 基于Redis实现幂等性
@Service
@Slf4j
public class RedisMessageProcessor {
    @Autowired
    private StringRedisTemplate redisTemplate;
    private static final String MESSAGE_CONSUMED_KEY_PREFIX = "mq:consumed:";
    private static final long EXPIRE_TIME = 24 * 60 * 60; // 24小时过期

    public void processMessageWithRedis(String messageId, String content) {
        String consumeKey = MESSAGE_CONSUMED_KEY_PREFIX + messageId;
        // 1. 使用Redis的setNX命令实现幂等性检查
        Boolean setSuccess = redisTemplate.opsForValue()
                .setIfAbsent(consumeKey, "1", EXPIRE_TIME, TimeUnit.SECONDS);
        if (Boolean.FALSE.equals(setSuccess)) {
            log.info("Message already processed, messageId: {}", messageId);
            return;
        }
        try {
            // 2. 处理业务逻辑
            processBusinessLogic(content);
        } catch (Exception e) {
            // 3. 处理失败时,删除Redis标记
            redisTemplate.delete(consumeKey);
            throw e;
        }
    }

    private void processBusinessLogic(String content) {

    }
}
3. 基于业务唯一标识实现幂等性
@Service
public class BusinessIdempotentProcessor {
    @Autowired
    private OrderService orderService;

    public void processOrder(OrderMessage orderMessage) {
        // 1. 从消息中提取业务唯一标识
        String orderNo = orderMessage.getOrderNo();
        // 2. 查询订单是否已存在
        if (orderService.isOrderExists(orderNo)) {
            log.info("Order already exists, orderNo: {}", orderNo);
            return;
        }
        // 3. 使用数据库唯一索引保证幂等性
        try {
            Order order = new Order();
            order.setOrderNo(orderNo); // orderNo字段有唯一索引
            order.setAmount(orderMessage.getAmount());
            order.setStatus(OrderStatus.CREATED);
            orderService.createOrder(order);
        } catch (DuplicateKeyException e) {
            log.info("Order already processed, orderNo: {}", orderNo);
        }
    }
}
4. 分布式锁实现幂等性
@Service
public class DistributedLockProcessor {
    @Autowired
    private RedissonClient redissonClient;

    public void processMessageWithLock(String messageId, String content) {
        String lockKey = "mq:lock:" + messageId;
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 1. 尝试获取分布式锁
            boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
            if (!locked) {
                log.warn("Failed to acquire lock for messageId: {}", messageId);
                return;
            }
            // 2. 检查消息是否已处理
            if (isMessageProcessed(messageId)) {
                return;
            }
            // 3. 处理业务逻辑
            processBusinessLogic(content);
            // 4. 记录处理状态
            markMessageAsProcessed(messageId);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Processing interrupted", e);
        } finally {
            // 5. 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}
5. 幂等性处理最佳实践
  1. 选择合适的幂等性实现方案:
    • 对于高并发场景,推荐使用Redis或分布式锁
    • 对于对数据一致性要求高的场景,推荐使用数据库唯一索引
    • 对于有业务唯一标识的场景,推荐使用业务唯一标识
### MQ 消息队列中的幂等性处理 为了确保消息队列(MQ)中消息幂等性,即防止重复消费带来的副作用,在设计系统时需考虑多种技术手段。当前流行的几种MQ如RocketMQ、Kafka、RabbitMQ、ActiveMQ等都采用至少一次交付机制(at least once delivery)[^1],这意味着每条消息会被成功发送给订阅者至少一次,但也可能多次。 #### 使用 Redis 缓存作为去重工具 一种常见的做法是在接收端引入外部存储服务——例如Redis缓存——用于记录已处理过的消息ID或其他唯一标识字段。当接收到新消息时先查询此缓存;如果存在,则认为该消息已被处理过而忽略之;反之则正常执行业务逻辑并更新缓存状态[^3]。 ```java // Java伪代码展示如何利用Redis实现简单的幂等性检查 public void processMessage(String messageId, Message message){ Jedis jedis = new Jedis("localhost"); String key = "processed:" + messageId; if (!jedis.exists(key)){ try { // 执行实际业务操作... // 设置key有效期为一天 jedis.setex(key, 86400, "true"); } finally { jedis.close(); } } } ``` #### 数据库层面设置唯一键约束 对于某些特定类型的业务数据来说,可以在数据库表结构上直接设定相应的唯一索引来达到相同的效果。每当尝试插入一条新的交易记录之前都会自动触发数据库级别的验证流程,以此杜绝因网络波动等原因造成的重复提交现象发生。 ```sql CREATE TABLE orders ( id INT NOT NULL AUTO_INCREMENT, order_no VARCHAR(50), ... PRIMARY KEY (id), UNIQUE INDEX idx_order_unique(order_no) ); ``` #### 应用层内嵌入幂等令牌(Token) 除了依赖第三方组件外,还可以在应用程序内部构建一套完整的幂等控制系统。通常情况下会涉及到生成唯一的幂等Token,并将其附带至每次请求当中。服务器端在接受到客户端发来的指令之后便会依据这个特殊参数来进行判断是否存在未完成的任务实例正在等待确认或是已经完成了相应的工作流节点。 ```python import uuid def generate_idempotent_token(): return str(uuid.uuid4()) class IdempotencyHandlerFactory: handlers = {} @staticmethod def register_handler(event_type, handler_class): IdempotencyHandlerFactory.handlers[event_type] = handler_class @staticmethod def get_handler(event_type): return IdempotencyHandlerFactory.handlers.get(event_type) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰糖心书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值