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并发请求数据插入重复内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java并发访问重复请求过滤问题

    问题描述 前段时间遇到个问题,自己内部系统调用出现重复请求导致数据混乱. 发生条件:接受到一个请求,该请求没有执行完成又接受到相同请求,导致数据错误(如果是前一个请求执行完成,马上又接受相同请求不会有问题) 问题分析:是由于数据库的脏读导致 问题解决思路 1.加一把大大的锁 (是最简单的实现方式,但是性能堪忧,而且会阻塞请求) 2.实现请求拦截 (可以共用,但是怎么去实现却是一个问题,怎么用一个优雅的方式实现,并且方便复用) 3.修改实现 (会对原有代码做改动,存在风险,最主要的是不能共用) 最

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

    目录 前言 分布式锁工具类 在过滤器实现请求拦截 总结 前言 前段时间发现数据库里经常会存在两条相同的用户数据,导致数据查询异常.查了原因,发现前端微信小程序在授权登录时,有时会出现同时发送了两条一模一样的请求(也就是常说的并发).虽然后端代码有做防重复的判断,但是避免不了并发时候的重复性操作.于是就开始考虑并发的解决方案,解决方案有很多,从拦截请求到数据库层面都可以入手. 我们采用了对请求报文生成摘要信息+Redis分布式锁的方案.运行了一段时间,功能很可靠,代码也很简洁.于是上来做下记录以便

  • 详解java解决分布式环境中高并发环境下数据插入重复问题

    java 解决分布式环境中 高并发环境下数据插入重复问题 前言 原因:服务器同时接受到的重复请求 现象:数据重复插入 / 修改操作 解决方案 : 分布式锁 对请求报文生成 摘要信息 + redis 实现分布式锁 工具类 分布式锁的应用 package com.nursling.web.filter.context; import com.nursling.nosql.redis.RedisUtil; import com.nursling.sign.SignType; import com.nu

  • java向mysql插入数据乱码问题的解决方法

    遇到java向mysql插入数据乱码问题,如何解决? MySQL默认编码是latin1 mysql> show variables like 'character%'; +--------------------------+--------------------------+ | Variable_name | Value | +--------------------------+--------------------------+ | character_set_client | la

  • Python并发请求下限制QPS(每秒查询率)的实现代码

      前两天有一个需求,需要访问某API服务器请求数据,该服务器限制了QPS=2(哈哈应该都知道是哪个服务器了吧_(:з」∠)_),因为QPS很小所以就使用阻塞式请求.后来开通了服务,QPS提高到了20,阻塞式请求满足不了这个QPS了,于是使用了GRequests来并发请求数据,但这里又遇到了一个问题:并发太快,服务器通过发送错误码拒绝了很多数据的响应,造成了资源的浪费.   故在此记录以下几种 节流(Throttle) 方法:   以下均假设有如下包和数据前提: import grequests

  • Mac OS X 下 IntelliJ IDEA、jEdit 等 Java 程序中文标点输入无效的完美解决方法

    Mac OS X 下基于 Java 的程序(如 IntelliJ IDEA.jEdit 等)会出现中文标点输入无效的问题,在中文输入法状态,可以输入中文字,但输入中文标点最后上去的是英文标点.查阅了相关资料,原来这是 Java 自己的 bug.从 Java 8u51 版本开始就出现了这个 bug,一直到现在最新的 Java 8u72 仍然如此,但是老版本 Java 8u45 是没有这个问题的.所以,可以采取变通的方法,在 Mac OS X 上同时装一个老版本的 JDK 8u45,不会影响已经安装

  • Java内存各部分OOM出现原因及解决方法(必看)

    一,jvm内存区域 1,程序计数器 一块很小的内存空间,作用是当前线程所执行的字节码的行号指示器. 2,java栈 与程序计数器一样,java栈(虚拟机栈)也是线程私有的,其生命周期与线程相同.通常存放基本数据类型,对象引用(一个指向对象起始地址的引用指针或一个代表对象的句柄),reeturnAddress类型(指向一条字节码指令的地址) 栈区域有两种异常类型:如果线程请求的栈深度大于虚拟机所允许的深度,将抛StrackOverflowError异常:如果虚拟机栈可以动态扩展(大部分虚拟机都可动

  • Java多线程执行处理业务时间太久解决方法代码示例

    背景:在政府开发了一个应用系统,主要功能是让企业填写企业资质信息,然后通过给定的公式,统计这一系列的信息,以得分的形式展示给政府领导查看.目前有1300家企业填报.由于得分是实时显示的,所以导致统计功能很慢. 代码运行流程: 1.查出1300企业信息 2.遍历1300企业信息,ji计算每家企业得分信息.每家预计时间为0.3秒.合计390秒.导致页面请求超时 3.导出(用jxl jar) 解决方案: 由于处理业务的,所以需要能有返回值的线程.用:Callable 直接上代码 1.调用线程的代码 L

  • 微信小程序首页数据初始化失败的解决方法

    一. 问题描述 用户首次后再次进入小程序时,我们通常需要通过获取用户openid或unionid用作唯一标示与后台进行数据交流,初始化用户信息.当我们通过第三方服务器跟微信建立请求时,微信需要用户确认是否公开信息.如图1,从console可以看到,在请求的同时,我们的首页index已经加载完成,图中初始化数据显示为空.无论我们将请求信息写在app.js的onload中或者index.js中,当我们点击确认后,请求信息才执行success方法,将第三方服务器返回的数据处理,这样的因需要用户点击而产

  • Go 并发实现协程同步的多种解决方法

    go 简洁的并发 多核处理器越来越普及.有没有一种简单的办法,能够让我们写的软件释放多核的威力?是有的.随着Golang, Erlang, Scala等为并发设计的程序语言的兴起,新的并发模式逐渐清晰.正如过程式编程和面向对象一样,一个好的编程模式有一个极其简洁的内核,还有在此之上丰富的外延.可以解决现实世界中各种各样的问题.本文以GO语言为例,解释其中内核.外延. 前言 Java 中有一系列的线程同步的方法,go 里面有 goroutine(协程),先看下下面的代码执行的结果是什么呢? pac

  • flyway实现java 自动升级SQL脚本的问题及解决方法

    为什么要用Flyway 在日常开发中,我们经常会遇到下面的问题: 自己写的SQL忘了在所有环境执行: 别人写的SQL我们不能确定是否都在所有环境执行过了: 有人修改了已经执行过的SQL,期望再次执行: 需要新增环境做数据迁移: 每次发版需要手动控制先发DB版本,再发布应用版本; 其它场景... 由于项目需求的变化,或者前期设计缺陷,导致在后期需要修改数据库,这应该是一个比较常见的事情,如果项目还没上线,你可能把表删除了重新创建,但是如果项目已经上线了,就不能这样简单粗暴了,每次运维部署项目,还得

随机推荐