阅读 526

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

现在遇到一个项目,移动设备存储数据,然后一起上传,那就出现了许多重复数据,这篇文章主要给大家介绍了关于java并发请求下数据插入重复问题的解决方法,需要的朋友可以参考下

目录
  • 前言

  • 分布式锁工具类

  • 在过滤器实现请求拦截

  • 总结

前言

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

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

解决方案说明:

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

分布式锁工具类


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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);
    }
     
}

在过滤器实现请求拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
 * 过滤频繁请求
 */
@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并发请求下数据插入重复问题的解决方法的文章就介绍到这了

原文链接:https://juejin.cn/post/7028457019052916767


    文章分类
    代码人生
    版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
    相关推荐