⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7085944825085689864 「llsydn」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

幂等性

今天我们来谈谈什么是幂等性

引用百度百科的解析如下:

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。

在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的.更复杂的操作幂等保证是利用唯一交易号(流水号)实现。

这解析,确实有点了,大家话看看就行了!!!(●'◡'●)

那对于我们程序员来说,我们关心的更多是下面这些问题:

什么地方,什么场景下需要用到幂等?

幂等,我们需要怎么做,如何实现幂等呢?

如何实现幂等呢

什么场景下需要用到幂等

  • 前端表单重复提交问题
  • 用户订单支付问题
  • 银行业务办理取号问题
  • 用户恶意进行调接口问题
  • 接口超时重复提交问题
  • MQ消息进行重复消费
  • ...

当然了,还有很多场景会用到幂等,这里咱们就不一一列举出来了。

那我们要如何设计一个幂等功能呢,而且还是代码非侵入式

代码非侵入式的意思,就是,我们的业务逻辑代码,不需要处理幂等校验的逻辑。

业务功能不处理?那交给谁处理呢?别着急,听哥们一一道来。^_^

业务功能不处理?

这里,要实现代码非侵入式的幂等校验,我们就要使用到切面编程了(@Aspect

幂等的实现原理

在系统中一些接口需要增加幂等处理,幂等的概念是一个业务请求只能执行一次。类似银行业务办理,首先需要取一个号,然后用户使用这个号去柜台办理业务。这个号只能使用一次,如果过期或者已办理这个号就无效了。

我们的幂等也是使用这种原理。

  • 1.首先客户端调用通过我们的系统获取一个号,我们称之为幂等号,这个号已经存在我们的系统中。
  • 2.客户端使用这个号,调用我们的接口。
  • 3.我们系统判断这个号在我们的系统中已经存在,如果存在则允许业务办理,如果不存在,则表示这是一个非法的号,我们直接抛出异常。
  • 4.当业务处理完成,我们会将这个号从我们的系统中删除掉。

好了,这实现步骤,也是十分清晰了呀!!!^_^

那么我们下面就来看代码如何实现了

幂等的代码实现

  • 定义一个幂等处理接口

public interface Idempotence {
/**
* 检查是否存在幂等号
* @param idempotenceId 幂等号
* @return 是否存在
*/
boolean check(String idempotenceId);

/**
* 记录幂等号
* @param idempotenceId 幂等号
*/
void record(String idempotenceId);

/**
* 记录幂等号
* @param idempotenceId 幂等号
* @param time 过期时间
*/
void record(String idempotenceId, Integer time);

/**
* 删除幂等号
* @param idempotenceId 幂等号
*/
void delete(String idempotenceId);

}

  • 定义一个幂等处理接口实现类

@Component
public class RedisIdempotence implements Idempotence {
@Autowired
private RedisRepository redisRepository;

@Override
public boolean check(String idempotenceId) {
return redisRepository.exists(idempotenceId);
}

@Override
public void record(String idempotenceId) {
redisRepository.set(idempotenceId,"1");
}

@Override
public void record(String idempotenceId,Integer time) {
redisRepository.setExpire(idempotenceId,"1",time);
}

@Override
public void delete(String idempotenceId) {
redisRepository.del(idempotenceId);
}
}

这个实现类,咱们就用redis存储这个幂等号 实现4个方法:

检查是否存在幂等号

记录幂等号

记录幂等号(带过期时间)

删除幂等号

  • 幂等工具类

@Component
public class IdempotenceUtil {
@Autowired
private RedisRepository redisRepository;
/**
* 生成幂等号
* @return
*/
public String generateId() {
String uuid = UUID.randomUUID().toString();
String uId=Base64Util.encode(uuid).toLowerCase();
redisRepository.setExpire(uId,"1",1800);
return uId;
}

/**
* 从Header里面获取幂等号
* @return
*/
public String getHeaderIdempotenceId(){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String idempotenceId=request.getHeader("idempotenceId");
return idempotenceId;
}
}

这个工具类,提供两个方法。

1.生成一个幂等号,咱们就用uuid

2.从Header里面获取幂等号

  • 定义一个注解

/**
* 接口增加幂等性
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IdempotenceRequired {

}

  • 切面

@Aspect
@Slf4j
@Component
public class IdempotenceSupportAdvice {
@Autowired
private Idempotence idempotence;
@Autowired
IdempotenceUtil idempotenceUtil;

/**
* 拦截有@IdempotenceRequired 注解 的方法。
*/
@Pointcut("@annotation(xxx.xxx.IdempotenceRequired)")
public void idempotenceMethod(){}

@AfterThrowing(value = "idempotenceMethod()()",throwing = "e")
public void afterThrowing(Throwable e){
if(!(e instanceof IdempotencyException)) {
// 从HTTP header中获取幂等号idempotenceId
String idempotenceId = idempotenceUtil.getHeaderIdempotenceId();
idempotence.record(idempotenceId, 1800);
}
}

@Around(value = "idempotenceMethod()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 从HTTP header中获取幂等号idempotenceId
String idempotenceId = idempotenceUtil.getHeaderIdempotenceId();
if(StringUtils.isEmpty(idempotenceId)){
//不存在幂等号则不进行额外操作
return joinPoint.proceed();
}
// 前置操作 幂等号是否存在
boolean existed = idempotence.check(idempotenceId);
if (!existed) {
throw new IdempotencyException("{success:false,message:"操作重复,请重新输入幂等号重试!",data:-2}");
}
//删除幂等号
idempotence.delete(idempotenceId);
Object result = joinPoint.proceed();

return result;
}
}

  • 定义个controller

@RequestMapping("/idempotence")
public class IdempotenceController {
/**
* 生成幂等号
* @return
*/
@GetMapping("/generateId")
public JsonResult generateId(){
IdempotenceUtil idempotenceUtil=SpringUtil.getBean(IdempotenceUtil.class);
String uId=idempotenceUtil.generateId();
return JsonResult.success("成功生成!").setData(uId);
}
}

好了,实现的代码,就是这些了,理解起来也是比较简单,没有过多复杂的逻辑。

接下来,就是如何使用的问题了,

使用的问题

这个使用,也是十分的简单啦!!!

幂等的使用

「服务端:」

不是所有的方法都需要切面拦截 ,只有 IdempotenceRequired 注解的方法才会被拦截。

例如下面接口:

@IdempotenceRequired
@PostMapping("/getUsers")
public JsonResult getUsers(){

//执行正常业务逻辑
...
}

在开发幂等接口时,只需要在方法上简单增加一个 IdempotenceRequired 注解即可。

这基本上就是代码非侵入式了呀!!!

代码非侵入式

「客户端:」

服务端处理好后,在客户端访问接口的时候需要执行以下步骤:

  • 需要先获取幂等号
  • 然后将幂等号添加到请求头中

  • 1.获取幂等号http://服务地址/idempotence/generateIdhttp://xn--zfry9hnb732h/idempotence/generateId

获取幂等号

  • 2.请求调用

往header中添加幂等号

往header中添加幂等号

好了,到这里幂等的实现,就已经完成了!!!^_^

那我们就可以愉快的编写代码了!!!^_^

文章目录
  1. 1. 幂等性
  2. 2. 什么场景下需要用到幂等
  3. 3. 幂等的实现原理
  4. 4. 幂等的代码实现
    1. 4.1. 幂等的使用