java并发请求下数据插入重复问题的解决方法

 更新时间:2021年11月12日 12:38:32   作者:晓彬哟  
现在遇到一个项目,移动设备存储数据,然后一起上传,那就出现了许多重复数据,这篇文章主要给大家介绍了关于java并发请求下数据插入重复问题的解决方法,需要的朋友可以参考下

前言

前段时间发现数据库里经常会存在两条相同的用户数据,导致数据查询异常。查了原因,发现前端微信小程序在授权登录时,有时会出现同时发送了两条一模一样的请求(也就是常说的并发)。虽然后端代码有做防重复的判断,但是避免不了并发时候的重复性操作。于是就开始考虑并发的解决方案,解决方案有很多,从拦截请求到数据库层面都可以入手。

我们采用了对请求报文生成摘要信息+Redis分布式锁的方案。运行了一段时间,功能很可靠,代码也很简洁。于是上来做下记录以便后续参考。

解决方案说明:

系统架构用的Spring boot,定义一个Filter过滤器对请求进行过滤,然后对请求报文生成摘要信息并设置Redis分布式锁。通过摘要和锁判断是否为同一请求。

分布式锁工具类

public class ContextLJ {
	
	private static final Integer JD = 0;
	
	  /**
	   * 上锁 使用redis 为分布式项目 加锁
	   * @param sign
	   * @param tiD
	   * @return
	   * @throws Exception
	   */
	  public static boolean lock(String sign, String tiD) {
	    synchronized (JD) { // 加锁
	    	Cache<String> cache = CacheManager.getCommonCache(sign);
	    	if(cache == null || StringUtils.isBlank(cache.getValue())) {
	    		CacheManager.putCommonCacheInfo(sign, tiD, 10000);
	    		return true;
			}
	    	return false;
	    }
	 }
	 
	  /**
	   * 锁验证
	   * @param sign
	   * @param tiD
	   * @return
	   */
	  public static boolean checklock(String sign, String tiD){
		  Cache<String> cache = CacheManager.getCommonCache(sign);
		  String uTid = StringUtils.replace(cache.getValue(), "\"", "");
		  return tiD.equals(uTid);
	  }
	 
	  /**
	   * 去掉锁
	   * @param sign
	   * @param tiD
	   */
	  public static void clent (String sign, String tiD){
		    if (checklock(sign, tiD)) {
		    	CacheManager.clearOnly(sign);
		    }
	  }
	 
	  /**
	   * 获取摘要
	   * @param request
	   */
	  public static String getSign(ServletRequest request){
	    // 此工具是将 request中的请求内容 拼装成 key=value&key=value2 的形式 源码在线面
	    String sign = null;
	    try {
	    	Map<String, String> map =  getRequstMap((HttpServletRequest) request);
	    	// 生成摘要
	    	sign = buildRequest(map);
	    } catch (Exception e) {
	    	e.printStackTrace();
	    }
	    return sign;
	  }
	  
	  public static Map<String, String> getRequstMap(HttpServletRequest req) throws Exception{
 		    Map<String,String> params = new HashMap<String,String>();
 		    params.put("uri", req.getRequestURI());
		    Map<String, String[]> requestParams = req.getParameterMap();
		    for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
		      String name = (String) iter.next();
		      String[] values = (String[]) requestParams.get(name);
		      String valueStr = "";
		      for (int i = 0; i < values.length; i++) {
		        valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
		      }
		      params.put(name, valueStr);
		    }
		    return params;
	}
	  
	 private static String buildRequest(Map<String, String> map) {
		 List<String> signList = new ArrayList<>();
		 for(Entry<String, String> entry : map.entrySet()) {
			 signList.add(entry.getKey() + "=" + entry.getValue());
		 }
		 String sign = StringUtils.join(signList, "&");
		 return DigestUtils.md5Hex(sign);
	}
	
}

在过滤器实现请求拦截

/**
 * 过滤频繁请求
 */
@Slf4j
@Component
public class MyFilter implements Filter{
	
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse myResp, FilterChain chain) throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest) request;
		Boolean isDict = StringUtils.contains(req.getRequestURI(), "/dict/getDatas");
		Boolean isFile = StringUtils.contains(req.getRequestURI(), "/files/file");
		if(isDict || isFile) {
			chain.doFilter(request, myResp); // 查询数据字典或者文件,直接放行
			return;
		}
		String sign = "sign_" + ContextLJ.getSign(request); // 生成摘要
	    String tiD = RandomUtils.randomCode(3) + "_" + Thread.currentThread().getId(); // 当前线程的身份
	    try { 
	    	if (!ContextLJ.lock(sign, tiD)) {
	    		Map<String,String> map = ContextLJ.getRequstMap((HttpServletRequest)request);
	    		log.warn("放弃相同并发请求【" + sign+ "】【" + tiD+"】"+JSON.toJSONString(map));
	    		frequentlyError(myResp);
	    		return;
	    	}
	    	if (!ContextLJ.checklock(sign, tiD)) {
	    		Map<String,String> map = ContextLJ.getRequstMap((HttpServletRequest)request);
		    	  log.warn("加锁验证失败 【" + sign+ "】【" + tiD+"】"+JSON.toJSONString(map));
		    	  frequentlyError(myResp);
		    	  return;
	    	}
	    	chain.doFilter(request, myResp); // 放行
	    } catch (Exception e) { // 捕获到异常 进行异常过滤
		      log.error("", e);
		      myResp.getWriter().write(JSON.toJSONString(ApiRs.asError("服务器繁忙,请重试")));
	    } finally {
	    	ContextLJ.clent(sign, tiD);
	    }
	}

	@Override
	public void destroy() {
		
	}
	
	/**
	 * 频繁请求
	 */
	private void frequentlyError(ServletResponse myResp) throws IOException {
	  ((HttpServletResponse) myResp).setHeader("Content-type", "text/html;charset=UTF-8");
	  myResp.getWriter().write(JSON.toJSONString(ApiRs.asError("稍安勿躁,不要频繁请求")));
	}

}

总结

到此这篇关于java并发请求下数据插入重复问题的解决方法的文章就介绍到这了,更多相关java并发请求数据插入重复内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:

相关文章

  • SpringCloud Alibaba框架介绍

    SpringCloud Alibaba框架介绍

    spring cloud是一个基于springboot实现的微服务架构开发工具,目前主流的SpringCloud分为SpringCloud Netflix和阿里云开源的SpringCloud Alibaba两个系列,本文主要介绍SpringCloud Alibaba框架,感兴趣的朋友可以参考一下
    2023-04-04
  • 解决SpringBoot框架因post数据量过大没反应问题(踩坑)

    解决SpringBoot框架因post数据量过大没反应问题(踩坑)

    这篇文章主要介绍了解决SpringBoot框架因post数据量过大没反应问题(踩坑),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • 使用java NIO及高速缓冲区写入文件过程解析

    使用java NIO及高速缓冲区写入文件过程解析

    这篇文章主要介绍了使用java NIO及高速缓冲区写入文件过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • Java中的getClass()以及getName()方法使用

    Java中的getClass()以及getName()方法使用

    这篇文章主要介绍了Java中的getClass()以及getName()方法使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • 关于Spring Cloud健康检查的陷阱

    关于Spring Cloud健康检查的陷阱

    这篇文章主要介绍了关于Spring Cloud健康检查的陷阱,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • java中synchronized关键字的3种写法实例

    java中synchronized关键字的3种写法实例

    synchronized是Java中的关键字,是一种同步锁,下面这篇文章主要给大家介绍了关于java中synchronized关键字的3种写法,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2021-11-11
  • Java实现文件批量重命名,移动和删除

    Java实现文件批量重命名,移动和删除

    这篇文章主要为大家介绍了如何利用Java语言实现批量重命名,批量移动文件,批量删除tmp文件等功能,感兴趣的小伙伴可以跟随小编一起学习一下
    2022-08-08
  • SpringBoot基于redis自定义注解实现后端接口防重复提交校验

    SpringBoot基于redis自定义注解实现后端接口防重复提交校验

    本文主要介绍了SpringBoot基于redis自定义注解实现后端接口防重复提交校验,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • SpringBoot两种方式刷新配置信息

    SpringBoot两种方式刷新配置信息

    这篇文章主要介绍了SpringBoot两种方式刷新配置信息,一种是@​ConfigurationProperties​不能自动刷新,需要手动调用contextRefresher.refresh()方法来刷新配置,第二种方法可以尝试下,需要的朋友可以参考下
    2023-08-08
  • Java字符串 正则表达式详解

    Java字符串 正则表达式详解

    这篇文章主要介绍了java使用正则表达式查找包含的字符串功能,结合具体实例形式分析了java针对字符串匹配查找的相关实现技巧,需要的朋友可以参考下
    2021-09-09

最新评论