一、概述
(一)幂等定义
1. 数学领域的幂等定义
在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的.更复杂的操作幂等保证是利用唯一交易号(流水号)实现。
2. 编程领域的幂等定义
对于一个方法/接口/函数采用同一个数据请求执行任意次数(>=2)而对数据只产生0次或1次变更。则认为该接口/方法/函数具有幂等性
RFC 2616 HTTP 幂等性
(二)为什么需要幂等
早期编程领域主流的架构为单体架构, 接口的调用多为进程内调用.针对每一个接口的调用都存在明确的结果失败OR 成功, 随分布式架构流行越来越多的调用转变成跨网络调用, 受网络的稳定性影响调用的结果也变得不确定, 为避免引入繁琐的 write-check 编码模式.
(三)可能产生幂等性的场景
通常接口分为两类(查询和修改),对于查询类型的我们认为天然幂等,不管执行多少次只会对数据产生0次影响。而针对修改类型(新增,修改,删除)可能会根据请求数产生n次影响
1. 网络波动,可能会引起重复请求
2. 用户重复操作,用户在有意无意下触发多次请求
3. 自身应用使用了重试机制(rpc重试或业务重试等)
4. 页面重复刷新或提交
5. 用户双击提交按钮
二、项目介绍
项目构建工具maven
项目外成结构图
模块说明
1. idempotent-core: 幂等核心依赖包
- core包主要提供了幂等的拦截实现、计数器的创建销毁、基础配置、以及一些支持业务方个性化自定义处理的接口。
- 核心类 AbsIdempotentAuthorizationProcessor
/**
* 幂等处理抽象类
* @author gol
*/
public abstract class AbsIdempotentAuthorizationProcessor implements IdempotentAuthorizationProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(AbsIdempotentAuthorizationProcessor.class);
/**
* 创建幂等计数器
*
* @param idempotentKey 幂等key
* @param idempotentTime 幂等时间
* @param timeUnit 时间策略
* @return boolean
*/
protected abstract boolean createIdempotentTally(String idempotentKey, Long idempotentTime, TimeUnit timeUnit);
/**
* 幂等执行的入口
*
* @param idempotentKey 幂等key
* @param idempotentTime 幂等时间,默认毫秒级别
* @return boolean
*/
@Override
public boolean execute(String idempotentKey, Long idempotentTime) throws IdempotentException {
LOGGER.info("execute idempotent check, idempotentKey:{},idempotentTime:{}", idempotentKey, idempotentTime);
return this.execute(idempotentKey, idempotentTime, false);
}
/**
* 幂等执行的入口
*
* @param idempotentKey 幂等key
* @param idempotentTime 幂等时间,默认毫秒级别
* @param throwsException 是否抛出异常
* @return boolean
*/
@Override
public boolean execute(String idempotentKey, Long idempotentTime, boolean throwsException) throws IdempotentException {
boolean idempotentTally = createIdempotentTally(idempotentKey, idempotentTime, TimeUnit.MILLISECONDS);
if (!idempotentTally && throwsException) {
throw new IdempotentException(IdempotentConstant.IDEMPOTENT_FAIL_MSG);
}
return idempotentTally;
}
/**
* @param idempotentKey 幂等key
* @param idempotentTime 幂等时间
* @param timeUnit 时间策略,默认毫秒级别
* @return boolean
* @throws IdempotentException ex
*/
@Override
public boolean execute(String idempotentKey, Long idempotentTime, TimeUnit timeUnit) throws IdempotentException {
return this.execute(idempotentKey, idempotentTime, timeUnit, false);
}
/**
* @param idempotentKey 幂等key
* @param idempotentTime 幂等时间
* @param timeUnit timeUnit
* @param throwsException 是否抛出异常
* @return boolean
* @throws IdempotentException IdempotentException
*/
@Override
public boolean execute(String idempotentKey, Long idempotentTime, TimeUnit timeUnit, boolean throwsException) throws IdempotentException {
boolean idempotentTally = createIdempotentTally(idempotentKey, idempotentTime, null == timeUnit ? TimeUnit.MILLISECONDS : timeUnit);
if (!idempotentTally && throwsException) {
throw new IdempotentException(IdempotentConstant.IDEMPOTENT_FAIL_MSG);
}
return idempotentTally;
}
}
2、idempotent-reids: 基于redis实现的幂等方案
redis包主要实现了幂等的存储、默认bean的配置、以及序列化等
三、使用说明
1、引入方式:maven引入
<dependency>
<groupId>org.link.redis</groupId>
<artifactId>idempotent-redis</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2、使用方式:基于方法形式使用拦截
@Autowired
private IdempotentComponent idempotentComponent;
/**
* 幂等测试接口
*/
@GetMapping("insert/{token}")
public void insert(final HttpServletResponse response,@PathVariable String token) throws IOException {
PrintWriter writer = response.getWriter();
String key = token;
boolean check = idempotentComponent.idempotentCheck(key, 1L,TimeUnit.SECONDS,true);
if (!check) {
response.setStatus(500);
writer.print("insert fail");
log.info("insert fail...");
} else {
response.setStatus(200);
writer.print("token=" + token + " insert success");
log.info("token=" + token + " insert success");
}
writer.close();
}
四、幂等详细方法介绍
/**
* 幂等校验
*
* @param idempotentKey 幂等key
* @param idempotentTime 幂等时间,默认毫秒级别
* @return boolean
*/
public boolean idempotentCheck(String idempotentKey, Long idempotentTime) {
return idempotentAuthorizationProcessor.execute(idempotentKey, idempotentTime);
}
/**
* 幂等校验
*
* @param idempotentKey 幂等key
* @param idempotentTime 幂等时间,默认毫秒级别
* @param throwsException 是否抛出异常
* @return boolean
*/
public boolean idempotentCheck(String idempotentKey, Long idempotentTime, boolean throwsException) {
return idempotentAuthorizationProcessor.execute(idempotentKey, idempotentTime, throwsException);
}
/**
* 幂等校验
*
* @param idempotentKey 幂等key
* @param idempotentTime 幂等时间
* @param timeUnit 时间策略
* @return boolean
*/
public boolean idempotentCheck(String idempotentKey, Long idempotentTime, TimeUnit timeUnit) {
return idempotentAuthorizationProcessor.execute(idempotentKey, idempotentTime, timeUnit);
}
/**
* 幂等校验
*
* @param idempotentKey 幂等key
* @param idempotentTime 幂等时间
* @param timeUnit 时间策略
* @param throwsException 是否抛出异常
* @return boolean
*/
public boolean idempotentCheck(String idempotentKey, Long idempotentTime, TimeUnit timeUnit, boolean throwsException) {
return idempotentAuthorizationProcessor.execute(idempotentKey, idempotentTime, timeUnit, throwsException);
}
源码地址