需求背景
私有云碧桂园生产环境用脚本调用创建接口,有重复调用请求,重复请求落到业务层并发执行,导致在门户会有数据被重复创建的现象。对此情况,需要有接口对接口实现幂等。
CMP项目现状
1.接口均符合restful接口规范,且post类请求的api的入参只有一个,且类型为DTO。
2.springBoot版本为2.0.4.RELEASE,对应spring-data-redis为2.0.9.RELEASE。redisson版本为3.10.1。
2.1 spring-data-redis版本过低,暂不支持setnx操作中携带ttl,如需确保原子操作得自行写lua脚本,或者升级版本至2.1以上。
2.2 redisson已支持分布式锁,且底层已尽可能确保了分布式锁的高可用。
业务功能设计
设计切面,在切面中获取方法/接口的入参,对入参进行重复校验。
功能实现问题以及处理方案
Q1.并不是所有接口都需要进行幂等,且需要方法幂等的接口难以用一个标准统一。
-- 自定义方法型注解,对注解进行aop。提供具有幂等功能的注解。
Q2.不同接口的幂等限制或时长不一。
-- 注解提供限制时长参数,供开发自定义。
Q3.参数体过于旁大,在与redis交互中占用太多网络资源和内存资
源。
-- 对参数体字符串进行信息摘要MD5处理,得到32位固定长度唯一标识。
Q4. Spring-data-redis 与 redisson的选择。
-- Spring-data-redis 需要版本升级或自行实现,对于支持java服务操作redis的setxn+ttl操作的高可用性有更多不确定的风险;
redisson已提供分布式锁的功能,底层原理就是对setnx+ttl的封装,底层已经尽可能的规避了此操作的高可用风险。只需要把tryLock()的等待时间设置为0,便可复用此功能来实现setnx+ttl操作。
所以此处选择用redisson来实现。
Q5. 单纯的根据报文体来做,只能实现基本的幂等,未跟业务结合,关键参数不变其他参数变了的情况还是需要业务层做处理。
-- 因为CMP的服务对外接口都是DTO统一入参,所以定义变量注解,只对有这个注解的字段做MD5。(此处刚好可以复用swagger的注解,达到减少注解复杂度的情况。尽可能少的改动原有代码)
核心代码实现
1.注解(Idempotence)

- aop(Aspect)
public void doBefore(JoinPoint joinPoint,Idempotence idempotence) throws CmpCheckException{
log.debug(">>>>>>>>>>>>>>>> begin idempotence >>>>>>>>>>>>>>>>>>");
Object[] datas = joinPoint.getArgs();
// 需要实现幂等的接口在cmp都是以dto的形式作为入参,所以此处可进行约定限制
if(datas==null || datas.length!=1){
log.error("幂等功能仅对单个入参的方法开放!");
return;
}
Field[] fields = datas[0].getClass().getDeclaredFields();
String stringKey="";
for(Field field:fields){
field.setAccessible(true);
try{
ApiModelProperty p =field.getAnnotation(ApiModelProperty.class);
if(p!=null && p.required()){
stringKey+=field.get(datas[0])+":";
}
}catch (Exception e){
log.error("反射异常! /n {}",e.getMessage());
}
}
//对入参进行md5之后作为key,用以缩减key的长度,减小网络传输和redis内存的压力
String md5s =MD5Utils.encodeByMD5(stringKey);
String key ="idempotence:"+md5s;
RLock lock = redissonClient.getLock(key);
Boolean b=true;
try {
b = lock.tryLock(0L,idempotence.idempotenceTime(),idempotence.timeUnit());
}catch (Exception e){
log.error(">>>>>>>>>> tryLock()处理异常 >>>>>>>>> /n {}",e.getMessage());
}
if(!b){
throw new CmpCheckException("请勿重复提交!","");
}
}
3.信息摘要算法 MD5 处理

功能测试

注:此处没有code,是因为次测试代码暂未结合cmp的异常码机制(涉及国际化),等之后正式下需求确定开发版本分支再在其上进行处理。