java并發(fā)請求下數(shù)據(jù)插入重復(fù)問題的解決方法
前言
前段時間發(fā)現(xiàn)數(shù)據(jù)庫里經(jīng)常會存在兩條相同的用戶數(shù)據(jù),導(dǎo)致數(shù)據(jù)查詢異常。查了原因,發(fā)現(xiàn)前端微信小程序在授權(quán)登錄時,有時會出現(xiàn)同時發(fā)送了兩條一模一樣的請求(也就是常說的并發(fā))。雖然后端代碼有做防重復(fù)的判斷,但是避免不了并發(fā)時候的重復(fù)性操作。于是就開始考慮并發(fā)的解決方案,解決方案有很多,從攔截請求到數(shù)據(jù)庫層面都可以入手。
我們采用了對請求報文生成摘要信息+Redis分布式鎖的方案。運(yùn)行了一段時間,功能很可靠,代碼也很簡潔。于是上來做下記錄以便后續(xù)參考。
解決方案說明:
系統(tǒng)架構(gòu)用的Spring boot,定義一個Filter過濾器對請求進(jìn)行過濾,然后對請求報文生成摘要信息并設(shè)置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中的請求內(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);
}
}
在過濾器實現(xiàn)請求攔截
/**
* 過濾頻繁請求
*/
@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ā)請求【" + 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) { // 捕獲到異常 進(jìn)行異常過濾
log.error("", e);
myResp.getWriter().write(JSON.toJSONString(ApiRs.asError("服務(wù)器繁忙,請重試")));
} 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("稍安勿躁,不要頻繁請求")));
}
}
總結(jié)
到此這篇關(guān)于java并發(fā)請求下數(shù)據(jù)插入重復(fù)問題的解決方法的文章就介紹到這了,更多相關(guān)java并發(fā)請求數(shù)據(jù)插入重復(fù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于jd-gui啟動報This?program?requires?Java?1.8+的錯誤問題及解決方法
最近,在Mac使用上JD-GUI啟動時總是報錯,接下來通過本文給大家介紹關(guān)于jd-gui啟動報this?program?requires?Java?1.8+的錯誤問題及解決方法,需要的朋友可以參考下2022-05-05
Java Big Number操作BigInteger及BigDecimal類詳解
這篇文章主要為大家介紹了Java Big Number操作BigInteger及BigDecimal類詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
關(guān)于Java中增強(qiáng)for循環(huán)使用的注意事項
for循環(huán)語句是java循環(huán)語句中最常用的循環(huán)語句,一般用在循環(huán)次數(shù)已知的情況下使用,這篇文章主要給大家介紹了關(guān)于Java中增強(qiáng)for循環(huán)使用的注意事項,需要的朋友可以參考下2021-06-06
Java使用異或運(yùn)算實現(xiàn)簡單的加密解密算法實例代碼
這篇文章主要介紹了Java使用異或運(yùn)算實現(xiàn)簡單的加密解密算法實例代碼,具有一定借鑒價值,需要的朋友可以參考下。2017-12-12
Spring Cloud Gateway自定義異常處理Exception Handler的方法小結(jié)
這篇文章主要介紹了Spring Cloud Gateway自定義異常處理Exception Handler的方法,本文通過兩種方法結(jié)合實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08

