前言
感觉最近脑子老是比较迷糊,记不住东西。因此,正好马上周末了,可以抽空写点东西,记录下最近的一些学习心得。
概念
幂等广义上一般指以相同参数调用同一个接口多次,对系统内部产生的影响是一致的。比如说进行支付时,如果一次扣款操作因为某种原因调用了两次,那么理论上应该只生效一次,否则就会出现一定的风险;
如何做幂等
回到刚才的场景,幂等是需要保证在对同一请求进行多次处理时不影响系统的正常运行逻辑,即当第N次请求过来时,系统要能知道,这个业务我们已经处理过了,相同的请求我们忽略掉就好了。
OK,那么设想一下,如果每次请求过来我们会根据它的请求参数或者其它特征生成一个唯一的标识,并把它存储起来,当下次请求再来时我们先去查询一下是否已经存在相同的标识记录,如果存在,那么忽略该次请求,否则先存储标识,然后再进行业务处理。这样其实就可以实现调用的幂等性控制,实际情况中我们可能会需要在数据库中创建一张幂等表来存储相关的标识。
简单示例:
# 创建一张幂等表
create table idempotent_ctrl(
id varchar(64) primary key
);
表中只有一个字段,就是主键(只是为了演示)。
/**
*
* @param bizNo
* @param eventNo
* @return
*/
public Boolean doSomething(String bizNo, String eventNo){
if(IdempotentComponent.checkIdempotent(bizNo, eventNo)){
// TODO
}
}
参数bizNo和eventNo可以保证业务的唯一性
OK,现在如果要来保证doSomething接口的幂等性呢,可以进行如下的设计:
public class IdempotentComponent {
private TransactionTemplate transactionTemplate;
private static final String IDPT_PREFIX = "IDPT";
private static final String IDPT_SPLIT = "_";
public static Boolean checkIdempotent(String bizNo, String eventNo) {
final String idempotentNo = genUniqueSeq(bizNo, eventNo);
return transactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus transactionStatus) {
try {
// 插入幂等表
IdptDao.save(idempotentNo);
} catch (DuplicateKeyException e) {
throw new BizException("idempotent failed");
}
return true;
}
});
}
/**
* 生成唯一的幂等记录
* 示例:IDPT_<bizNo>_<eventNo>
*
* @param bizNo
* @param eventNo
* @return
*/
private String genUniqueSeq(String bizNo, String eventNo) {
return IDPT_PREFIX + IDPT_SPLIT + bizNo + IDPT_SPLIT + eventNo;
}
}
当然,原理很简单,但实际上真正实现起来是有一定难度的,比如说当前很多系统都是分布式的架构,数据库可能也是分库分表的,因此我们必须能够保证相同的标识必须能够落在同一张表内,否则依然无法保证接口的幂等性。当然,这些必须要根据业务来进行分析。