针对防重提交现阶段解决办法
需要注意点,本方法的解决偏向于防重复提交,不针对于对接口幂等性的处理等需要具体原子性的操作.
虽然也能用,但针对大流量情况下,交互、响应及性能上还是不能得到保证. 存在交互差 错误, 重复的情况
结论
先给代码吧,分为几种情况,目前阶段觉得好用的方法,前端防重可以自行百度
1. 项目为单体项目且为SpringBoot前后端分离未使用redis
PS: 有无redis 均能使用,最通用的一种办法
<!--POM文件 是否需要其他jar包暂未考证,因为项目中有封装好的jar包,如果有欢迎提出来修改-->
<!-- guava本地锁 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
类名: LocalLock
作用: 用于被aop拦截标识
注意是自定义注解
@interface LocalLock
@Target({ElementType.METHOD}) //作用于方法上
@Retention(RetentionPolicy.RUNTIME) //运行时
@Documented
@Inherited
public @interface LocalLock {
String key() default "";
//过期时间,使用本地缓存可以忽略,如果使用redis做缓存可以加上
int expire() default 5;
}
类名: LockMethodInterceptor
作用: 全局拦截自定义注解的内容
@Aspect
@Configuration
@Slf4j
public class LockMethodInterceptor {
//定义缓存,设置最大缓存数及过期日期 .maximumSize为最大访问数 .expireAfterWrite为过期时间 单位为秒可以自定义
private static final Cache<String,Object> CACHE = CacheBuilder.newBuilder().maximumSize(1000).expireAfterWrite(10, TimeUnit.SECONDS).build();
//这里设置你的切点 这里实现方式是实现了一个自定义注解 通过获取自定义注解注解的方法,来控制方法 自定义注解名为 LocalLock
@Around("execution(public * *(..)) && @annotation(com.sxdh.core.common.annotion.LocalLock)")
public Object interceptor(ProceedingJoinPoint joinPoint){
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LocalLock localLock = method.getAnnotation(LocalLock.class);
String key = getKey(localLock.key(),joinPoint.getArgs());
if(!StringUtils.isEmpty(key)){
//判断本地内存中是否以当前key 为 key 的 键值对
if(CACHE.getIfPresent(key) != null){
log.warn("进入切点");
//这里的return 可以根据自己的业务去书写,code msg data 自定义
return new ErrorTip(1,"请勿重复提交!!!");
}
//如果是第一次访问,则保存当前key值 过期时间依据CACHE的定义
CACHE.put(key,key);
}
try{
return joinPoint.proceed();
}catch (Throwable throwable){
throw new RuntimeException("服务器异常");
}finally {
}
}
// 这里其实是自定义的一种生成Key的方法 , i 指定参数的位置
/*
如 void findSomeThing(String arg1,String arg2){
...
}
arg[0] 就代表 arg1 , arg[1] 就代表 arg2
*/
private String getKey(String keyExpress, Object[] args){
for (int i = 0; i < args.length; i++) {
keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString());
}
return keyExpress;
}
}
/**
* 实现类
* 这里第一个参数 使我们要实现的业务,保证这个学员ID 同一时间只能有一个请求去修改
* 后面的参数 就是要修改的数据, 这里可以灵活配置,无所谓格式
* 因为什么呢, 这里的值其实是为了使key值唯一设置的,如果能简单设置唯一,那就使用简单的数据
* 不行的话,还有很多种几种解决方案
* 1. 如果是对象 可以json化 作为key值 替换 当然有点傻,但是直接
* 2. 如果是对象 将可以标识用户唯一的业务数据拿出来组装替换
* 2. 如果是对象 可以hashCode() 这个code值作为唯一标识 单例
* 3. 根据请求的token
* 4. 根据请求的request /session 等
* 方法很多 ,欢迎提出来
*/
@LocalLock(key = "localLock:traffic:add:arg[0]")
public Object update(Integer stuId,...) {
...
}
2. 项目为单体项目且SpringBoot前后端分离有使用redis
1. 使用 redis 缓存 替代 Gavua 或者共同使用 过期时间使用LocalLock的expire key 依旧
2. 使用 redis 的计数器,来控制,方法为: 以当前业务唯一字段设为key值 做计数器操作,
如果小于等于0 操作,如果大于0 则请求拦截
3. 项目为单体项目等其他情况
此种情况还是适用第一种情况,适用本地锁机制来解决,但是会存在几个问题需要说明
<!--当为MVC项目时,需要注意开启AOP的注册 在配置xml里加上-->
<aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>
此种情况使用拦截器,但是需要明确拦截器和请求的区别
/**
* 如果不想适用注解的模式或者觉得麻烦,又或者使用不了,可以考虑拦截器
* 继承 extends HandlerInterceptorAdapter
*/
public class IntercepterWebRequest extends HandlerInterceptorAdapter{
/**
*实现preHandle方法 针对于短时间内的同一客户端的请求去做拦截,同样可以适用本地锁的概念
*/
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
return false;
}
return true;
}
....
}
这里说明一个特殊的实现方式,是我的同事提出来,没有去做验证,感觉挺有道理的,
全局定义一个boolean 值 flag ,每当有一个请求打进来 如果此时flag为true 则把flag变为false
进行业务操作,后续的请求来的时候如果为false 就拒掉. 只有当前业务流程走完后,才把flag又设为true
,或者异常的时候设为true,有兴趣的可以做做验证
4. 项目为分布式项目
5. 题外话
可以看出,其实除了最后一个布尔值的方法,其他的重点都是在保证key值的唯一去限制请求的重复提交,
虽然也可以在数据库的层次上进行操作,不过一般情况不会去考虑
## 前言
看了一篇博文,个人感触针对与接口的幂等性处理其实原理大都相同,结果都显示为数据的重复或者异常情况 ,都是为了保证结果的一致性
以前在面试的时候,记得看过流量削尖的方法,有一种方法印象很深,使用令牌桶来控制请求的/或者水槽法
蘑菇街商家平台资金团队工程师 @木照
代理类的生成流程@乘风破浪的码农
先这样吧,后面再继续写