spring层面处理网络抖动导致的重复写入数据,实现请求的幂等性

栏目: Java · 发布时间: 5年前

内容简介:在用户使用网站创建新闻时,由于网络波动,导致发送了多个创建新闻的请求,使得系统中存在了冗余的数据。由于我的应用是单例的,所以采用guava来做缓存。

问题描述

在用户使用网站创建新闻时,由于网络波动,导致发送了多个创建新闻的请求,使得系统中存在了冗余的数据。

问题分析

  1. 看过很多文章,大部分思路都是:如果同一用户在很短时间内发送了重复的post请求,那么后台只处理第一个请求,后面的请求则过滤掉。
  2. 具体实现方式则是添加一个springmvc的拦截器,当一个post请求过来时,首先去缓存中查询短时间内有没有相同请求到来,如果没有,则把该请求放入缓存,并将请求传递下去;如果有,则不再执行后续操作。
  3. 但是这种方式也会面临一个问题:如果两个请求同时到来,同时读缓存,这样两个请求都不会读到记录,导致重复执行了两个请求。
  4. 于是我们想到给缓存加锁,这样就可以避免3中出现的情况。但是众所周知,普通的加锁是很影响效率的,我们也不能舍弃效率来保证幂等性。为了提升加锁后的效率,我采用了DCL(双重检查锁,没有学过的小伙伴可以去查一查)来进行缓存的加锁读写。

代码

由于我的应用是单例的,所以采用guava来做缓存。

import com.csdc.cett.exception.RequestException;
import com.csdc.cett.util.MD5;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Enumeration;
import java.util.concurrent.TimeUnit;

/**
 * 处理重复请求,保持请求的幂等性
 * 使用双重检查锁定(DCL)
 *
 * @author ksyzz
 * @since <pre>2019/06/20</pre>
 */
@Component
public class RequestInterceptor implements HandlerInterceptor {

    private static volatile Cache<String, String> loadingCache = CacheBuilder.newBuilder()
            .maximumSize(500)
            .expireAfterWrite(5, TimeUnit.SECONDS)
            .build();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 只需要保持用户登录后的POST请求的幂等性,此处要排除文件分片上传
        if ("POST".equals(request.getMethod()) && request.getHeader("Auth-Token") != null && !request.getRequestURI().contains("/upload/files")) {
            // 要求:用户5秒内不能重复提交相同url,相同参数的请求
            // 存储方式为 md5(URI+Auth-Token+RequestParams+InputStream)
            StringBuilder md5 = new StringBuilder();
            // Auth-Token为用户身份标识
            String token = request.getHeader("Auth-Token");
            md5.append(request.getRequestURI());
            md5.append(token);
            Enumeration<String> parameterNames = request.getParameterNames();
            while (parameterNames.hasMoreElements()) {
                String key = parameterNames.nextElement();
                String parameter = request.getParameter(key);
                md5.append("&").append(key).append("=").append(parameter);
            }
            String cacheKey = MD5.getHashString(md5.toString());

            // 校验是否已经提交过请求

            if (loadingCache.getIfPresent(cacheKey) == null) {
                synchronized (loadingCache) {
                    if (loadingCache.getIfPresent(cacheKey) == null) {
                        // 此处value可不设置
                        loadingCache.put(cacheKey, "");
                        return true;
                    }
                }
            }
            throw new RequestException("请勿重复提交");
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

超简单!一学就懂的互联网金融

超简单!一学就懂的互联网金融

视觉图文 / 人民邮电出版社 / 2015-2-1 / 45.00元

零基础、全图解,通过130多个精辟的知识点、220多张通俗易懂的逻辑图表,让您一书在手,即可彻底看懂、玩转互联网金融从菜鸟成为达人,从新手成为互联网金融高手! 本书主要特色:最简洁的版式+最直观的图解+最实用的内容。 本书细节特色:10章专题内容详解+80多个特别提醒奉献+130多个知识点讲解+220多张图片全程图解,深度剖析互联网金融的精华之处,帮助读者在最短的时间内掌握互联网金融知......一起来看看 《超简单!一学就懂的互联网金融》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

html转js在线工具
html转js在线工具

html转js在线工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试