java并發(fā)請(qǐng)求下數(shù)據(jù)插入重復(fù)問(wèn)題的解決方法
前言
前段時(shí)間發(fā)現(xiàn)數(shù)據(jù)庫(kù)里經(jīng)常會(huì)存在兩條相同的用戶數(shù)據(jù),導(dǎo)致數(shù)據(jù)查詢異常。查了原因,發(fā)現(xiàn)前端微信小程序在授權(quán)登錄時(shí),有時(shí)會(huì)出現(xiàn)同時(shí)發(fā)送了兩條一模一樣的請(qǐng)求(也就是常說(shuō)的并發(fā))。雖然后端代碼有做防重復(fù)的判斷,但是避免不了并發(fā)時(shí)候的重復(fù)性操作。于是就開(kāi)始考慮并發(fā)的解決方案,解決方案有很多,從攔截請(qǐng)求到數(shù)據(jù)庫(kù)層面都可以入手。
我們采用了對(duì)請(qǐng)求報(bào)文生成摘要信息+Redis分布式鎖的方案。運(yùn)行了一段時(shí)間,功能很可靠,代碼也很簡(jiǎn)潔。于是上來(lái)做下記錄以便后續(xù)參考。
解決方案說(shuō)明:
系統(tǒng)架構(gòu)用的Spring boot,定義一個(gè)Filter過(guò)濾器對(duì)請(qǐng)求進(jìn)行過(guò)濾,然后對(duì)請(qǐng)求報(bào)文生成摘要信息并設(shè)置Redis分布式鎖。通過(guò)摘要和鎖判斷是否為同一請(qǐng)求。
分布式鎖工具類
public class ContextLJ { private static final Integer JD = 0; /** * 上鎖 使用redis 為分布式項(xiàng)目 加鎖 * @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; } } /** * 鎖驗(yàn)證 * @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中的請(qǐng)求內(nèi)容 拼裝成 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); } }
在過(guò)濾器實(shí)現(xiàn)請(qǐng)求攔截
/** * 過(guò)濾頻繁請(qǐng)求 */ @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); // 查詢數(shù)據(jù)字典或者文件,直接放行 return; } String sign = "sign_" + ContextLJ.getSign(request); // 生成摘要 String tiD = RandomUtils.randomCode(3) + "_" + Thread.currentThread().getId(); // 當(dāng)前線程的身份 try { if (!ContextLJ.lock(sign, tiD)) { Map<String,String> map = ContextLJ.getRequstMap((HttpServletRequest)request); log.warn("放棄相同并發(fā)請(qǐng)求【" + sign+ "】【" + tiD+"】"+JSON.toJSONString(map)); frequentlyError(myResp); return; } if (!ContextLJ.checklock(sign, tiD)) { Map<String,String> map = ContextLJ.getRequstMap((HttpServletRequest)request); log.warn("加鎖驗(yàn)證失敗 【" + sign+ "】【" + tiD+"】"+JSON.toJSONString(map)); frequentlyError(myResp); return; } chain.doFilter(request, myResp); // 放行 } catch (Exception e) { // 捕獲到異常 進(jìn)行異常過(guò)濾 log.error("", e); myResp.getWriter().write(JSON.toJSONString(ApiRs.asError("服務(wù)器繁忙,請(qǐng)重試"))); } finally { ContextLJ.clent(sign, tiD); } } @Override public void destroy() { } /** * 頻繁請(qǐng)求 */ private void frequentlyError(ServletResponse myResp) throws IOException { ((HttpServletResponse) myResp).setHeader("Content-type", "text/html;charset=UTF-8"); myResp.getWriter().write(JSON.toJSONString(ApiRs.asError("稍安勿躁,不要頻繁請(qǐng)求"))); } }
總結(jié)
到此這篇關(guān)于java并發(fā)請(qǐng)求下數(shù)據(jù)插入重復(fù)問(wèn)題的解決方法的文章就介紹到這了,更多相關(guān)java并發(fā)請(qǐng)求數(shù)據(jù)插入重復(fù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
jvm內(nèi)存溢出解決方法(jvm內(nèi)存溢出怎么解決)
jvm內(nèi)存溢出解決方法,詳細(xì)內(nèi)容看下面解釋2013-12-12Java實(shí)現(xiàn)批量發(fā)送帶附件的郵件代碼
大家好,本篇文章主要講的是Java實(shí)現(xiàn)批量發(fā)送帶附件的郵件代碼,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2022-01-01springboot如何使用AOP做訪問(wèn)請(qǐng)求日志
這篇文章主要介紹了springboot如何使用AOP做訪問(wèn)請(qǐng)求日志,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01JAVAEE項(xiàng)目結(jié)構(gòu)以及并發(fā)隨想
每個(gè)代碼里面的工具都是工具,API是你最需要理解的,哪個(gè)好,哪個(gè)不好,沒(méi)有準(zhǔn)確答案。 一切皆對(duì)象,對(duì)于Java來(lái)講是純粹的,代理是對(duì)象,反射是對(duì)象,對(duì)象是對(duì)象,基本數(shù)據(jù)類型不是對(duì)象。2016-04-04Java Process與Runtime()的使用及調(diào)用cmd命令阻塞的解決方案
這篇文章主要介紹了Java Process與Runtime()的使用及調(diào)用cmd命令阻塞的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Java對(duì)稱與非對(duì)稱加密算法原理詳細(xì)講解
對(duì)稱加密算法指加密和解密使用相同密鑰的加密算法。對(duì)稱加密算法用來(lái)對(duì)敏感數(shù)據(jù)等信息進(jìn)行加密,非對(duì)稱加密算法指加密和解密使用不同密鑰的加密算法,也稱為公私鑰加密2022-11-11Java ArrayList的基本概念和作用及動(dòng)態(tài)數(shù)組的機(jī)制與性能
在Java中,ArrayList是一個(gè)實(shí)現(xiàn)了List接口的動(dòng)態(tài)數(shù)組,它可以根據(jù)需要自動(dòng)增加大小,因此可以存儲(chǔ)任意數(shù)量的元素,這篇文章主要介紹了探秘Java ArrayList的基本概念和作用及動(dòng)態(tài)數(shù)組的機(jī)制與性能,需要的朋友可以參考下2023-12-12