防止重复提交可以通过前端拦截校验和后端拦截校验的方式,在此对后端拦截进行重复提交判断进行尝试。
后端拦截的实现思路是在方法执行之前,先判断此业务是否已经执行过,如果执行过则不再执行,否则就正常执行。我们将请求的业务 ID 存储在内存中,并且通过添加互斥锁来保证多线程下的程序执行安全,将数据存储在内存中,最简单的方法就是使用 HashMap 存储,或者是使用 Guava Cache 也是同样的效果,但很显然 HashMap 可以更快的实现功能,所以我们先来实现一个 HashMap 的防重(防止重复)版本。
升级方法是加上双重检测锁DCL;再升级方法是采用LRUMap实现;
代码示例:
方式一:HashMap
@RestController
public class DemoController1 {
//缓存ID集合
private Map<String,Integer> map = new HashMap<>();
@RequestMapping("addUser1")
public String addUser(String id) {
synchronized (this.getClass()) {
//重复请求判断
if (map.containsKey(id)) {
//重复请求
System.out.println("请勿重复提交..." + id);
return "请求失败";
}
//存储ID
map.put(id,1);
}
//业务代码
System.out.println("添加用户ID:" + id);
return "请求成功";
//此方式的问题是HashMap是无限增长的,占用内存会越来越大;需要添加可以清除过期的数据
}
}
方式二:固定大小数组
@RestController
public class DemoController2 {
//ID集合,大小为100
private static String[] cache = new String[100];
//请求计数器
private static Integer counter = 0;
@RequestMapping("addUser2")
public String addUser(String id) {
synchronized (this.getClass()) {
//重复请求判断
if (Arrays.asList(cache).contains(id)) {
//重复请求
System.out.println("请勿重复提交..." + id);
return "请求失败";
}
//记录ID
if (counter >= cache.length) {
counter = 0;
}
cache[counter] = id;
//下标后移一位
counter++;
}
//业务代码
System.out.println("添加用户ID:" + id);
return "请求成功";
//此方式的问题是HashMap是无限增长的,占用内存会越来越大;需要添加可以清除过期的数据
//此方式解决了HashMap未清除过期数据的问题
}
}
方式三:方式一的升级
//缓存ID集合
private Map<String,Integer> map = new HashMap<>();
@RequestMapping("addUser3")
public String addUser(String id) {
//重复请求判断
if (map.containsKey(id)) {
//重复请求
System.out.println("请勿重复提交..." + id);
return "请求失败";
}
synchronized (this.getClass()) {
//重复请求判断
if (map.containsKey(id)) {
//重复请求
System.out.println("请勿重复提交..." + id);
return "请求失败";
}
//存储ID
map.put(id,1);
}
//业务代码
System.out.println("添加用户ID:" + id);
return "请求成功";
//此方式的问题是HashMap是无限增长的,占用内存会越来越大;需要添加可以清除过期的数据
//添加了双重检测机制提升代码执行效率,注意DCL使用重复提交频繁的业务场景,相反并不适用
}
方式四:方式二的升级
//ID集合,大小为100
private static String[] cache = new String[100];
//请求计数器
private static Integer counter = 0;
@RequestMapping("addUser4")
public String addUser(String id) {
if (Arrays.asList(cache).contains(id)) {
//重复请求
System.out.println("请勿重复提交..." + id);
return "请求失败";
}
synchronized (this.getClass()) {
//重复请求判断
if (Arrays.asList(cache).contains(id)) {
//重复请求
System.out.println("请勿重复提交..." + id);
return "请求失败";
}
//记录ID
if (counter >= cache.length) {
counter = 0;
}
cache[counter] = id;
//下标后移一位
counter++;
}
//业务代码
System.out.println("添加用户ID:" + id);
return "请求成功";
//此方式的问题是HashMap是无限增长的,占用内存会越来越大;需要添加可以清除过期的数据
//此方式解决了HashMap未清除过期数据的问题,添加了双重检测机制提升代码执行效率,注意DCL使用重复提交频繁的业务场景,相反并不适用
}
方式五:LRUMap
//最大容量100个,根据LRU算法淘汰数据的Map集合
private LRUMap<String,Integer> lruMap = new LRUMap<>(100);
@RequestMapping("addUser5")
public String addUser(String id) {
if (lruMap.containsKey(id)) {
//重复请求
System.out.println("请勿重复提交..." + id);
return "请求失败";
}
synchronized (this.getClass()) {
if (lruMap.containsKey(id)) {
//重复请求
System.out.println("请勿重复提交..." + id);
return "请求失败";
}
//存储ID
lruMap.put(id,1);
}
//业务代码
System.out.println("添加用户ID:" + id);
return "请求成功";
//此方式的问题是HashMap是无限增长的,占用内存会越来越大;需要添加可以清除过期的数据
//此方式解决了HashMap未清除过期数据的问题,通过LRU算法清除最不常使用的数据
}
方式六:方式五的工具化封装
public class IdempotentUtil {
//根据LRU算法淘汰最近最少使用的数据的Map集合,最大容量100个
private static LRUMap<String,Integer> lruMap = new LRUMap<>(100);
/**
* 幂等性判断
*/
public static boolean check(String id, Object lockClass) {
synchronized (lockClass) {
//重复请求判断
if (lruMap.containsKey(id)) {
//重复请求
System.out.println("请勿重复提交..." + id);
return false;
}
//非重复请求,存储ID
lruMap.put(id,1);
}
return true;
}
}
@RequestMapping("addUser6")
public String addUser(String id) {
//调用幂等性工具判断
if (!IdempotentUtil.check(id,this.getClass())) {
return "请求失败";
}
//业务代码
System.out.println("添加用户ID:" + id);
return "请求成功";
}
后面还可以通过自定义注解+反射+拦截器实现校验。
后端拦截与缓存优化:实战防止重复提交的几种策略
本文探讨了如何通过后端拦截机制,结合HashMap、固定大小数组和LRUMap等数据结构,实现业务请求的防重复提交。从基本的HashMap到更高效的LRUMap,作者详细介绍了每种方法的优缺点及适用场景,同时提到了自定义工具封装和注解拦截的高级解决方案。
3774

被折叠的 条评论
为什么被折叠?



