您好,登錄后才能下訂單哦!
本篇內容介紹了“怎么解決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并發請求下數據插入重復問題”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。