本篇内容介绍了“怎么解决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并发请求下数据插入重复问题”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。