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