The Solution to the Problem of Data Insertion Repeated in java Concurrent Request
- 2021-12-12 04:13:09
- OfStack
Preface
Some time ago, it was found that there are often two identical user data in the database, which leads to abnormal data query. After checking the reasons, it is found that when the front-end WeChat applet authorizes login, it sometimes sends two requests with one model and one sample at the same time (which is often called concurrency). Although the back-end code has made anti-repetitive judgments, it cannot avoid repetitive operations when concurrent. So we started thinking about concurrent solutions. There are many solutions, from intercepting requests to the database level.
We adopt the scheme of generating digest information + Redis distributed lock for request message. After running for 1 period of time, the function is very reliable and the code is very concise. So I came up and made a record for subsequent reference.
Solution Description:
Spring boot is used in the system architecture. One Filter filter is defined to filter the request, and then the summary information is generated for the request message and Redis distributed lock is set. Determine whether it is the same 1 request by digest and lock.
Distributed lock tool class
public class ContextLJ {
private static final Integer JD = 0;
/**
* Lock Use redis For distributed projects Lock
* @param sign
* @param tiD
* @return
* @throws Exception
*/
public static boolean lock(String sign, String tiD) {
synchronized (JD) { // Lock
Cache<String> cache = CacheManager.getCommonCache(sign);
if(cache == null || StringUtils.isBlank(cache.getValue())) {
CacheManager.putCommonCacheInfo(sign, tiD, 10000);
return true;
}
return false;
}
}
/**
* Lock verification
* @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);
}
/**
* Remove the lock
* @param sign
* @param tiD
*/
public static void clent (String sign, String tiD){
if (checklock(sign, tiD)) {
CacheManager.clearOnly(sign);
}
}
/**
* Get a summary
* @param request
*/
public static String getSign(ServletRequest request){
// This tool is the request Content requested in Assemble into key=value&key=value2 Form of Source code on-line surface
String sign = null;
try {
Map<String, String> map = getRequstMap((HttpServletRequest) request);
// Generate summary
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);
}
}
Implement request interception in filters
/**
* Filter frequent requests
*/
@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); // Query the data dictionary or file and release it directly
return;
}
String sign = "sign_" + ContextLJ.getSign(request); // Generate summary
String tiD = RandomUtils.randomCode(3) + "_" + Thread.currentThread().getId(); // The identity of the current thread
try {
if (!ContextLJ.lock(sign, tiD)) {
Map<String,String> map = ContextLJ.getRequstMap((HttpServletRequest)request);
log.warn(" Discard the same concurrent request " " + sign+ " " " + tiD+" " "+JSON.toJSONString(map));
frequentlyError(myResp);
return;
}
if (!ContextLJ.checklock(sign, tiD)) {
Map<String,String> map = ContextLJ.getRequstMap((HttpServletRequest)request);
log.warn(" Lock verification failed " " + sign+ " " " + tiD+" " "+JSON.toJSONString(map));
frequentlyError(myResp);
return;
}
chain.doFilter(request, myResp); // Release
} catch (Exception e) { // Exception caught Perform anomaly filtering
log.error("", e);
myResp.getWriter().write(JSON.toJSONString(ApiRs.asError(" Server is busy, please try again ")));
} finally {
ContextLJ.clent(sign, tiD);
}
}
@Override
public void destroy() {
}
/**
* Frequent requests
*/
private void frequentlyError(ServletResponse myResp) throws IOException {
((HttpServletResponse) myResp).setHeader("Content-type", "text/html;charset=UTF-8");
myResp.getWriter().write(JSON.toJSONString(ApiRs.asError(" Be calm and don't ask frequently ")));
}
}