一、幂等性基础概念
1. 幂等性定义
-
数学定义:f(x) = f(f(x))
-
计算机定义:对同一接口的多次相同请求与一次请求产生相同效果
-
核心要求:重复请求不会产生副作用
2. HTTP方法的幂等性
http
复制
下载
幂等方法:
GET ✅ 幂等(读取操作)
PUT ✅ 幂等(全量更新)
DELETE ✅ 幂等(删除不存在资源应返回相同结果)
POST ❌ 非幂等(创建资源)
PATCH ❌ 非幂等(部分更新)
二、业务场景分类
1. 天然幂等场景
java
复制
下载
// 查询类操作
GET /api/users/{id}
GET /api/orders?status=paid
// 删除操作(删除不存在资源应返回成功)
DELETE /api/users/{id}
// 全量更新
PUT /api/users/{id}
2. 需要保证幂等场景
java
复制
下载
// 创建类
POST /api/orders // 下单
POST /api/payments // 支付
POST /api/coupons/claim // 领券
// 更新类
PATCH /api/orders/{id} // 更新订单状态
POST /api/orders/{id}/cancel // 取消订单
POST /api/inventory/deduct // 扣减库存
// 特殊操作
POST /api/transfers // 转账
POST /api/withdrawals // 提现
三、技术解决方案对比
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Token机制 | 客户端先获取token,请求时携带 | 实现简单,用户体验好 | 需要前后端配合 | 表单提交、用户交互 |
| 唯一索引 | 数据库层面唯一约束 | 绝对可靠,数据库保障 | 只能用于创建操作 | 数据创建、防重复 |
| 乐观锁 | 版本号或状态机控制 | 并发性能好 | 需要设计版本字段 | 库存扣减、余额变更 |
| 分布式锁 | 全局锁控制并发 | 强一致性保证 | 性能开销较大 | 高并发扣减、抢购 |
| 全局ID | 请求ID+幂等表 | 通用性强,可追溯 | 实现相对复杂 | 分布式系统、异步消息 |
四、详细实现方案
方案1:Token机制(最常用)
1.1 基本流程
图表
代码
下载
全屏
是
否
客户端
- 获取Token
服务端生成Token并存储
返回Token
- 提交请求携带Token
服务端验证并删除Token
Token存在?
执行业务
返回重复提交错误
返回成功
1.2 服务端实现
java
复制
下载
@Component
public class TokenService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 生成Token
public String createToken(String businessKey) {
String token = UUID.randomUUID().toString();
String key = buildTokenKey(businessKey, token);
// 存储Token,5分钟过期
redisTemplate.opsForValue().set(key, "1", 5, TimeUnit.MINUTES);
return token;
}
// 验证并删除Token
public boolean verifyAndDeleteToken(String businessKey, String token) {
String key = buildTokenKey(businessKey, token);
// 使用Lua脚本保证原子性
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(key),
"1"
);
return result != null && result == 1;
}
private String buildTokenKey(String businessKey, String token) {
return String.format("token:%s:%s", businessKey, token);
}
}
1.3 接口实现
java
复制
下载
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private TokenService tokenService;
// 获取Token
@GetMapping("/token")
public ApiResponse<String> getOrderToken() {
String userId = getCurrentUserId();
String token = tokenService.createToken(userId);
return ApiResponse.success(token);
}
// 提交订单(幂等)
@PostMapping
public ApiResponse<OrderDTO> createOrder(
@RequestBody OrderCreateRequest request,
@RequestHeader("X-Order-Token") String token) {
String userId = getCurrentUserId();
// 1. 验证Token
if (!tokenService.verifyAndDeleteToken(userId, token)) {
return ApiResponse.error("重复提交或Token已过期");
}
// 2. 执行业务逻辑
OrderDTO order = orderService.createOrder(request);
return ApiResponse.success(order);
}
}
方案2:数据库唯一索引
2.1 防重表设计
sql
复制
下载
-- 创建幂等记录表
CREATE TABLE idempotent_record (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
biz_type VARCHAR(64) NOT NULL COMMENT '业务类型',
biz_id VARCHAR(128) NOT NULL COMMENT '业务唯一ID',
request_id VARCHAR(64) COMMENT '请求ID',
status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0-处理中,1-成功,2-失败',
result TEXT COMMENT '处理结果',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_biz (biz_type, biz_id),
INDEX idx_request_id (request_id)
) ENGINE=InnoDB COMMENT='幂等记录表';
2.2 业务实现
java
复制
下载
@Service
@Transactional
public class IdempotentService {
@Autowired
private IdempotentRecordMapper recordMapper;
/**
* 幂等执行
* @param bizType 业务类型
* @param bizId 业务唯一ID
* @param processor 业务处理器
* @return 执行结果
*/
public <T> T execute(String bizType, String bizId, Supplier<T> processor) {
// 1. 尝试插入幂等记录
IdempotentRecord record = new IdempotentRecord();
record.setBizType(bizType);
record.setBizId(bizId);
record.setStatus(0); // 处理中
try {
recordMapper.insert(record);
} catch (DuplicateKeyException e) {
// 2. 记录已存在,查询结果
IdempotentRecord existing = recordMapper.selectByBiz(bizType, bizId);
if (existing.getStatus() == 1) {
// 已成功,返回缓存结果
return JSON.parseObject(existing.getResult(), new TypeReference<T>(){});
} else if (existing.getStatus() == 0) {
// 正在处理中,抛出异常或等待
throw new BusinessException("请求正在处理中");
} else {
// 之前失败,可以重试(需考虑业务是否允许重试)
throw new BusinessException("上次处理失败,请重试");
}
}
try {
// 3. 执行业务逻辑
T result = processor.get();
// 4. 更新为成功状态
record.setStatus(1);
record.setResult(JSON.toJSONString(result));
recordMapper.updateById(record);
return result;
} catch (Exception e) {
// 5. 更新为失败状态
record.setStatus(2);
recordMapper.updateById(record);
throw e;
}
}
}
2.3 使用示例
java
复制
下载
@Service
public class OrderService {
@Autowired
private IdempotentService idempotentService;
public OrderDTO createOrder(OrderCreateRequest request) {
String userId = request.getUserId();
String orderNo = generateOrderNo(); // 生成唯一订单号
// 使用订单号作为幂等键
return idempotentService.execute(
"ORDER_CREATE",
orderNo,
() -> {
// 实际的订单创建逻辑
Order order = new Order();
order.setOrderNo(orderNo);
order.setUserId(userId);
// ... 其他业务逻辑
// 保存订单
orderMapper.insert(order);
// 扣减库存
inventoryService.deduct(request.getItems());
// 发送消息
messageService.sendOrderCreated(order);
return convertToDTO(order);
}
);
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
方案3:乐观锁机制
3.1 基于版本号
java
复制
下载
// 实体类
@Data
@TableName("product_stock")
public class ProductStock {
@TableId(type = IdType.AUTO)
private Long id;
private String productCode;
private Integer stock;
@Version // MyBatis Plus乐观锁注解
private Integer version;
private LocalDateTime updateTime;
}
// Mapper
public interface ProductStockMapper extends BaseMapper<ProductStock> {
// 乐观锁更新库存
@Update("UPDATE product_stock SET stock = stock - #{quantity}, " +
"version = version + 1, update_time = NOW() " +
"WHERE id = #{id} AND version = #{version} " +
"AND stock >= #{quantity}")
int deductStockWithVersion(@Param("id") Long id,
@Param("quantity") Integer quantity,
@Param("version") Integer version);
}
// 业务服务
@Service
public class InventoryService {
@Autowired
private ProductStockMapper stockMapper;
@Transactional
public boolean deductStock(Long productId, Integer quantity) {
// 1. 查询当前库存和版本号
ProductStock stock = stockMapper.selectById(productId);
if (stock.getStock() < quantity) {
throw new BusinessException("库存不足");
}
// 2. 乐观锁更新
int rows = stockMapper.deductStockWithVersion(
productId, quantity, stock.getVersion());
if (rows == 0) {
// 3. 更新失败,重试或抛异常
throw new ConcurrentException("库存扣减冲突,请重试");
}
return true;
}
}
3.2 基于状态机
java
复制
下载
// 订单状态枚举
public enum OrderStatus {
INIT(0, "待支付"),
PAYING(1, "支付中"),
PAID(2, "已支付"),
CANCELLED(-1, "已取消");
private final int code;
private final String desc;
// 状态转换验证
public static boolean canChange(OrderStatus from, OrderStatus to) {
Map<OrderStatus, Set<OrderStatus>> allowedTransitions = Map.of(
INIT, Set.of(PAYING, CANCELLED),
PAYING, Set.of(PAID, CANCELLED),
PAID, Set.of(CANCELLED) // 已支付只能取消
);
return allowedTransitions.getOrDefault(from, Collections.emptySet())
.contains(to);
}
}
// Mapper
public interface OrderMapper extends BaseMapper<Order> {
@Update("UPDATE orders SET status = #{newStatus} " +
"WHERE id = #{id} AND status = #{oldStatus}")
int updateStatus(@Param("id") Long id,
@Param("oldStatus") Integer oldStatus,
@Param("newStatus") Integer newStatus);
}
// 业务服务
@Service
public class OrderService {
public boolean cancelOrder(Long orderId) {
// 乐观锁更新状态
int rows = orderMapper.updateStatus(
orderId,
OrderStatus.PAYING.getCode(), // 只有支付中才能取消
OrderStatus.CANCELLED.getCode()
);
if (rows == 0) {
// 可能订单已支付或已取消
Order order = orderMapper.selectById(orderId);
if (order.getStatus() == OrderStatus.CANCELLED.getCode()) {
return true; // 幂等:已取消的订单再次取消返回成功
}
throw new BusinessException("订单状态不允许取消");
}
return true;
}
}
方案4:分布式锁
4.1 Redis分布式锁实现
java
复制
下载
@Component
public class RedisDistributedLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String LOCK_PREFIX = "lock:";
private static final String LOCK_VALUE = "1";
private static final long DEFAULT_EXPIRE = 30; // 秒
private static final long DEFAULT_WAIT = 5; // 秒
private static final long RETRY_INTERVAL = 100; // 毫秒
/**
* 尝试获取锁(支持重试)
*/
public boolean tryLock(String key, long expireSeconds, long waitSeconds)
throws InterruptedException {
String lockKey = LOCK_PREFIX + key;
long endTime = System.currentTimeMillis() + waitSeconds * 1000;
while (System.currentTimeMillis() < endTime) {
// 使用SET NX EX命令原子性加锁
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, LOCK_VALUE, expireSeconds, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(success)) {
return true;
}
// 等待后重试
Thread.sleep(RETRY_INTERVAL);
}
return false;
}
/**
* 释放锁
*/
public boolean unlock(String key) {
String lockKey = LOCK_PREFIX + key;
return Boolean.TRUE.equals(redisTemplate.delete(lockKey));
}
/**
* Lua脚本保证原子性的锁续期
*/
public boolean renewLock(String key, long expireSeconds) {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('expire', KEYS[1], ARGV[2]) " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(LOCK_PREFIX + key),
LOCK_VALUE,
String.valueOf(expireSeconds)
);
return result != null && result == 1;
}
}
4.2 业务层使用
java
复制
下载
@Service
public class PaymentService {
@Autowired
private RedisDistributedLock distributedLock;
private static final String PAYMENT_LOCK_KEY = "payment:";
@Transactional
public PaymentResult processPayment(PaymentRequest request) {
String lockKey = PAYMENT_LOCK_KEY + request.getOrderNo();
try {
// 1. 获取分布式锁
if (!distributedLock.tryLock(lockKey, 30, 5)) {
throw new BusinessException("支付处理中,请勿重复操作");
}
// 2. 检查幂等性(先查后处理)
Payment existing = paymentMapper.selectByOrderNo(request.getOrderNo());
if (existing != null) {
// 幂等返回:如果已支付成功,返回相同结果
return convertToResult(existing);
}
// 3. 执行业务逻辑
Payment payment = createPayment(request);
paymentMapper.insert(payment);
// 4. 调用支付网关
boolean success = paymentGateway.pay(payment);
if (success) {
payment.setStatus(PaymentStatus.SUCCESS);
// 更新订单状态
orderService.markAsPaid(request.getOrderNo());
} else {
payment.setStatus(PaymentStatus.FAILED);
}
paymentMapper.updateById(payment);
return convertToResult(payment);
} catch (Exception e) {
throw new BusinessException("支付处理失败", e);
} finally {
// 5. 释放锁
distributedLock.unlock(lockKey);
}
}
}
方案5:全局唯一ID + 幂等表(分布式)
5.1 架构设计
java
复制
下载
// 幂等记录实体
@Data
@TableName("distributed_idempotent")
public class DistributedIdempotent {
@TableId(type = IdType.INPUT)
private String requestId; // 全局唯一请求ID
private String serviceName; // 服务名
private String bizType; // 业务类型
private String bizKey; // 业务唯一键
private Integer status; // 0-处理中, 1-成功, 2-失败
private String result; // 处理结果(JSON)
private Integer retryCount; // 重试次数
private String errorMsg; // 错误信息
private LocalDateTime createTime;
private LocalDateTime updateTime;
@TableField(exist = false)
private Map<String, Object> context; // 上下文信息
}
// 幂等管理器
@Component
public class DistributedIdempotentManager {
@Autowired
private DistributedIdempotentMapper idempotentMapper;
/**
* 生成全局请求ID
*/
public String generateRequestId(String serviceName, String bizType) {
// 雪花算法生成ID
Snowflake snowflake = new Snowflake(1, 1);
long id = snowflake.nextId();
return String.format("%s:%s:%s",
serviceName, bizType, id);
}
/**
* 幂等执行
*/
public <T> T execute(String requestId, String bizKey,
Supplier<T> processor, Class<T> resultType) {
// 1. 查询是否已处理
DistributedIdempotent record =
idempotentMapper.selectById(requestId);
if (record != null) {
return handleExistingRecord(record, resultType);
}
// 2. 创建处理中记录
record = new DistributedIdempotent();
record.setRequestId(requestId);
record.setStatus(0);
record.setBizKey(bizKey);
record.setCreateTime(LocalDateTime.now());
try {
idempotentMapper.insert(record);
} catch (DuplicateKeyException e) {
// 并发插入,重新查询
record = idempotentMapper.selectById(requestId);
return handleExistingRecord(record, resultType);
}
try {
// 3. 执行业务逻辑
T result = processor.get();
// 4. 更新为成功
record.setStatus(1);
record.setResult(JSON.toJSONString(result));
record.setUpdateTime(LocalDateTime.now());
idempotentMapper.updateById(record);
return result;
} catch (Exception e) {
// 5. 更新为失败
record.setStatus(2);
record.setErrorMsg(e.getMessage());
record.setRetryCount(record.getRetryCount() + 1);
record.setUpdateTime(LocalDateTime.now());
idempotentMapper.updateById(record);
throw e;
}
}
private <T> T handleExistingRecord(DistributedIdempotent record,
Class<T> resultType) {
if (record.getStatus() == 1) {
// 已成功,返回结果
return JSON.parseObject(record.getResult(), resultType);
} else if (record.getStatus() == 0) {
// 处理中,根据业务决定等待或抛异常
throw new BusinessException("请求正在处理中");
} else {
// 失败,根据重试策略决定
if (record.getRetryCount() < 3) {
throw new RetryableException("上次处理失败,可重试");
} else {
throw new BusinessException("处理失败次数过多");
}
}
}
}
5.2 消息队列幂等消费
java
复制
下载
@Component
@Slf4j
public class MessageConsumer {
@Autowired
private DistributedIdempotentManager idempotentManager;
@RabbitListener(queues = "order.create.queue")
public void handleOrderCreate(OrderCreateMessage message) {
String requestId = message.getRequestId();
String orderNo = message.getOrderNo();
// 使用消息ID作为幂等键
idempotentManager.execute(
requestId,
orderNo,
() -> {
// 业务处理逻辑
return orderService.processOrderCreate(message);
},
OrderResult.class
);
}
}
五、多级幂等保障体系
5.1 分层防御架构
java
复制
下载
// 三层幂等保障
@Component
public class MultiLayerIdempotentService {
// 第一层:请求层(防重复提交)
public class RequestLayer {
private final TokenService tokenService;
public ApiResponse processWithToken(OrderRequest request, String token) {
if (!tokenService.verifyToken(token)) {
return ApiResponse.error("重复提交");
}
// 继续处理
}
}
// 第二层:业务层(防并发重复)
public class BusinessLayer {
private final RedisDistributedLock lockService;
@Transactional
public OrderDTO createOrderWithLock(OrderRequest request) {
String lockKey = "order:create:" + request.getUserId();
if (!lockService.tryLock(lockKey, 10, 3)) {
throw new BusinessException("正在处理中");
}
try {
// 业务逻辑 + 数据库唯一约束
return orderService.createOrder(request);
} finally {
lockService.unlock(lockKey);
}
}
}
// 第三层:数据层(最终保障)
public class DataLayer {
// 依赖数据库唯一索引
// 依赖乐观锁
// 依赖状态机约束
}
}
5.2 幂等键设计原则
java
复制
下载
class IdempotentKeyDesign {
// 幂等键组成要素
class KeyComponents {
// 1. 业务标识
String businessType; // ORDER_CREATE, PAYMENT
// 2. 业务唯一标识
String businessKey; // orderNo, paymentNo
// 3. 操作标识(可选)
String operation; // CREATE, UPDATE, CANCEL
// 4. 用户标识(可选)
String userId; // user123
// 组合方式
String key = String.format("%s:%s:%s:%s",
businessType, userId, operation, businessKey);
}
// 不同场景的幂等键设计
Map<String, String> keyExamples = Map.of(
// 支付业务:用户+订单号
"payment", "PAYMENT:user123:CREATE:ORDER202312345678",
// 库存扣减:商品ID+订单号
"inventory", "INVENTORY:DEDUCT:PROD001:ORDER202312345678",
// 优惠券领取:用户+优惠券ID
"coupon", "COUPON:CLAIM:user123:COUPON20231234",
// 消息消费:消息ID+消费者组
"message", "MESSAGE:CONSUME:group1:MSG202312345678"
);
}
六、特殊场景处理
6.1 前端重复提交
javascript
复制
下载
// 前端防重复提交
class FrontendIdempotent {
// 1. 按钮禁用
submitOrder() {
this.submitting = true;
// 发送请求
// 完成后重置状态
}
// 2. 请求队列
requestQueue = [];
asyncRequest(request) {
// 相同请求去重
if (this.requestQueue.includes(request.id)) {
return Promise.reject('重复请求');
}
this.requestQueue.push(request.id);
// 发送请求
// 完成后移除
}
// 3. Token机制
async submitWithToken() {
// 先获取Token
const token = await api.getToken();
// 携带Token提交
return api.createOrder({data}, {headers: {'X-Token': token}});
}
}
6.2 网络超时重试
java
复制
下载
// 客户端重试策略
@Component
public class RetryableHttpClient {
private final RestTemplate restTemplate;
private final IdempotentStorage storage;
// 重试时保证幂等
public <T> T postWithRetry(String url, Object request,
Class<T> responseType, String requestId) {
// 1. 生成请求ID
if (requestId == null) {
requestId = UUID.randomUUID().toString();
}
// 2. 检查是否已成功
IdempotentRecord record = storage.get(requestId);
if (record != null && record.isSuccess()) {
return record.getResult();
}
// 3. 设置幂等头
HttpHeaders headers = new HttpHeaders();
headers.set("X-Request-ID", requestId);
headers.set("X-Idempotency-Key", requestId);
HttpEntity<Object> entity = new HttpEntity<>(request, headers);
// 4. 重试逻辑
int maxRetries = 3;
for (int i = 0; i < maxRetries; i++) {
try {
ResponseEntity<T> response = restTemplate.postForEntity(
url, entity, responseType);
if (response.getStatusCode().is2xxSuccessful()) {
// 5. 存储成功结果
storage.saveSuccess(requestId, response.getBody());
return response.getBody();
}
} catch (Exception e) {
if (i == maxRetries - 1) {
throw e;
}
// 指数退避
Thread.sleep(100 * (long) Math.pow(2, i));
}
}
throw new RuntimeException("重试失败");
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
6.3 消息队列幂等消费
java
复制
下载
// RocketMQ幂等消费
@Component
@RocketMQMessageListener(
topic = "ORDER_TOPIC",
consumerGroup = "ORDER_CONSUMER_GROUP"
)
public class OrderMessageConsumer implements RocketMQListener<MessageExt> {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public void onMessage(MessageExt message) {
String msgId = message.getMsgId();
String keys = message.getKeys(); // 业务键
// 1. Redis原子性判断
String redisKey = "mq:consumed:" + msgId;
Boolean notConsumed = redisTemplate.opsForValue()
.setIfAbsent(redisKey, "1", 7, TimeUnit.DAYS);
if (Boolean.FALSE.equals(notConsumed)) {
log.info("消息已消费,msgId: {}", msgId);
return; // 幂等返回
}
try {
// 2. 业务处理
OrderMessage orderMessage = JSON.parseObject(
message.getBody(), OrderMessage.class);
orderService.processOrderMessage(orderMessage);
} catch (Exception e) {
// 3. 处理失败,删除Redis记录允许重试
redisTemplate.delete(redisKey);
throw e;
}
}
}
七、监控与治理
7.1 幂等监控指标
java
复制
下载
@Component
@Slf4j
public class IdempotentMonitor {
private final MeterRegistry meterRegistry;
// 记录幂等相关指标
public void recordMetrics(String bizType, String result) {
// 计数器
Counter.builder("idempotent.requests.total")
.tag("biz_type", bizType)
.tag("result", result)
.register(meterRegistry)
.increment();
// 处理时长直方图
Timer.builder("idempotent.process.duration")
.tag("biz_type", bizType)
.register(meterRegistry);
}
// 告警规则
@Scheduled(fixedDelay = 60000) // 每分钟检查
public void checkIdempotentAlerts() {
// 1. 重复请求率过高
double duplicateRate = calculateDuplicateRate();
if (duplicateRate > 0.1) { // 10%重复率
alertService.sendAlert("幂等重复率过高: " + duplicateRate);
}
// 2. 幂等冲突频繁
long conflictCount = getConflictCountLastMinute();
if (conflictCount > 100) {
alertService.sendAlert("幂等冲突频繁: " + conflictCount);
}
}
}
7.2 幂等配置管理
yaml
复制
下载
# application-idempotent.yml
idempotent:
config:
# Token配置
token:
enabled: true
expire-seconds: 300 # 5分钟
prefix: "idempotent:token"
# 分布式锁配置
lock:
enabled: true
prefix: "idempotent:lock"
expire-seconds: 30
wait-seconds: 5
# 幂等记录配置
record:
enabled: true
table-name: "sys_idempotent_record"
clean-enabled: true
clean-days: 30 # 保留30天
# 业务配置
business:
order.create:
strategy: "token+lock+unique" # 组合策略
timeout: 5000 # 5秒超时
retry-times: 3 # 重试3次
payment.process:
strategy: "requestId+record"
timeout: 10000
inventory.deduct:
strategy: "optimistic_lock"
version-field: "version"
八、总结与选择建议
8.1 方案选择矩阵
| 场景特征 | 推荐方案 | 原因 |
|---|---|---|
| 表单提交 | Token机制 | 简单易用,用户体验好 |
| 创建业务 | 唯一索引 | 数据库保障,绝对可靠 |
| 更新业务 | 乐观锁 | 并发性能好,无锁竞争 |
| 高并发扣减 | 分布式锁+乐观锁 | 强一致+高性能 |
| 分布式系统 | 全局ID+幂等表 | 可追溯,通用性强 |
| 消息消费 | Redis+唯一键 | 快速判断,避免重复 |
8.2 最佳实践组合
java
复制
下载
// 生产环境推荐组合
public class ProductionIdempotentStrategy {
// 三级保障体系
public ApiResponse process(OrderRequest request) {
// 1. 前端:Token防重复提交 ✓
// 2. 网关:请求ID去重 ✓
// 3. 业务层:分布式锁+乐观锁 ✓
// 4. 数据层:唯一索引+状态机 ✓
// 5. 监控:全链路跟踪 ✓
return doProcess(request);
}
// 金融级幂等要求
public PaymentResult processPayment(PaymentRequest request) {
// 必须包含以下所有措施:
// 1. 请求全局唯一ID
// 2. 数据库唯一约束
// 3. 分布式锁
// 4. 状态机校验
// 5. 对账机制
// 6. 人工核查入口
return paymentService.process(request);
}
}
8.3 幂等性检查清单
markdown
复制
下载
✅ 是否识别了所有非幂等接口?
✅ 是否为关键业务接口设计了幂等?
✅ 幂等键设计是否唯一且有意义?
✅ 是否考虑了并发场景?
✅ 是否有超时重试机制?
✅ 是否有防重复提交措施?
✅ 数据库约束是否生效?
✅ 异常情况是否妥善处理?
✅ 是否有监控和告警?
✅ 是否有降级方案?
✅ 是否有数据对账?
✅ 是否经过充分测试?
记住:没有完美的幂等方案,只有最适合业务场景的方案。根据业务重要性、并发程度、数据一致性要求等因素,选择合适的组合方案。