幂等性问题 —— 如何防止重复创建订单

幂等性:对接口的多余重复调用的结果和单次调用的结果一致。

一、需要幂等性的场景

在这里插入图片描述

二、幂等性实现方案

核心思想:首先要根据业务判断什么是重复相同的请求,然后相同的请求都携带或生成一个唯一的识别码,这样在服务端就可以进行判别处理

1. 基于token机制实现

在允许一次独立的接口调用前先向服务端请求生成一个唯一token并存放在redis,当发起接口请求调用时携带上这个token,服务端进行校验,请求成功就删除这个token,后续的重复请求就无效了。

例如在防止订单重复提交时,除了在前端设置请求加载蒙版外,还可以在进入提交订单页面时先向服务端请求一个token,这样在同一个订单页面的重复点击请求就无效了。

对 redis 中是否存在 token 以及删除的代码逻辑建议用 Lua 脚本实现,保证原子性

2. 基于MySQL唯一索引实现

相同的请求对应同一个唯一字段值,比如将订单号作为唯一索引,那么相同的请求只有第一个能Insert成功,其它的由于唯一索引冲突都会阻塞等待记录的X锁释放,拿到锁后插入由于索引已经存在故插入失败。

3. 使用单体redis的setnx

从请求数据中获取或者生成一个唯一值,然后使用redis的setnx将其设置到redis中,设置成功则消费请求,设置失败则为重复请求不进行处理。

4. 基于redis分布式锁+双重检查实现

从请求数据中获取或者生成一个唯一值code,然后检查这个请求有没有处理过,没有则针对这个code尝试在redis集群中获取分布式锁,加锁成功再开启事务操作,事务提交后再释放分布式锁。并且在操作前先检查该请求有没有处理过,没有才进行处理,否则直接结束事务。

和DCL单例思想类似

参考:

  1. https://zhuanlan.zhihu.com/p/345512692
  2. https://www.bilibili.com/video/BV1TX4y1z7Ax
  3. https://www.bilibili.com/video/BV1uv4y1S7tE
### 实现 RocketMQ 消息幂等性处理方案 为了确保消息系统的稳定性,在 RocketMQ 中实现消息幂等性至关重要[^2]。具体来说,可以通过以下几种方式来达成这一目标: #### 1. 数据库唯一键约束 利用数据库表中的唯一索引来防止重复记录的插入。当接收到一条新消息时,尝试将其对应的业务数据写入带有唯一约束条件的字段中;如果违反了该约束,则说明此条消息已经被成功处理过。 ```sql CREATE TABLE order ( id BIGINT AUTO_INCREMENT PRIMARY KEY, message_id VARCHAR(255) NOT NULL UNIQUE, -- 唯一标识符用于幂等控制 status INT DEFAULT 0 COMMENT '订单状态' ); ``` #### 2. Redis 分布式锁机制 借助像Redis这样的内存存储服务提供者创建全局唯一的key-value对作为锁标志位。每次接收到来自于RocketMQ的新消息之前先检查是否存在相应的lock key;若不存在则设置它并继续执行后续操作流程直到完成整个事务过程后再释放掉这个锁资源;反之则直接跳过当前这条已经存在过的请求不做任何改变动作即可达到避免多次提交的效果。 ```java String lockKey = "order:" + messageId; RLock redisLock = redissonClient.getLock(lockKey); try { boolean isLocked = redisLock.tryLock(); if (isLocked){ // 执行业务逻辑... // 更新订单状态或其他相关操作 // 提交事务或确认消费 } } finally { redisLock.unlock(); // 确保无论如何都会解锁 } ``` #### 3. 应用层日志记录与查重校验 在应用程序内部维护一份专门的日志文件用来保存每笔交易产生的事件详情以及对应的时间戳信息等内容。每当有新的通知到达时就查询一次历史档案看是否有匹配项存在——如果有那就证明这是第二次及以上次传过来相同的指令因而可以直接忽略不予理会;否则的话再按照正常程序往下走直至结束为止。 ```python import logging logging.basicConfig(filename='message_log.log', level=logging.INFO) def process_message(message_id): with open('processed_messages.txt') as f: processed_ids = set(f.read().splitlines()) if message_id not in processed_ids: try: # 处理实际业务... # 将已处理的消息 ID 记录下来 with open('processed_messages.txt', 'a') as f: f.write(f"{message_id}\n") logging.info(f"Processed message {message_id}") # 返回成功的响应给 MQ 或其他组件 except Exception as e: logging.error(f"Failed to process message {message_id}: {e}") raise e else: logging.warning(f"Ignoring duplicate message {message_id}") ``` 以上三种方法都可以有效帮助开发者们解决RocketMQ环境中可能出现的信息冗余问题,并且可以根据具体的项目需求灵活选用其中一种或是组合起来共同作用以获得更好的性能表现和用户体验效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值