spring層面處理網絡抖動致使的重複寫入數據,實現請求的冪等性

問題描述

在用戶使用網站建立新聞時,因爲網絡波動,致使發送了多個建立新聞的請求,使得系統中存在了冗餘的數據。java

問題分析

  1. 看過不少文章,大部分思路都是:若是同一用戶在很短期內發送了重複的post請求,那麼後臺只處理第一個請求,後面的請求則過濾掉。
  2. 具體實現方式則是添加一個springmvc的攔截器,當一個post請求過來時,首先去緩存中查詢短期內有沒有相同請求到來,若是沒有,則把該請求放入緩存,並將請求傳遞下去;若是有,則再也不執行後續操做。
  3. 可是這種方式也會面臨一個問題:若是兩個請求同時到來,同時讀緩存,這樣兩個請求都不會讀到記錄,致使重複執行了兩個請求。
  4. 因而咱們想到給緩存加鎖,這樣就能夠避免3中出現的狀況。可是衆所周知,普通的加鎖是很影響效率的,咱們也不能捨棄效率來保證冪等性。爲了提高加鎖後的效率,我採用了DCL(雙重檢查鎖,沒有學過的小夥伴能夠去查一查)來進行緩存的加鎖讀寫。

代碼

因爲個人應用是單例的,因此採用guava來作緩存。web

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 {

    }

}
相關文章
相關標籤/搜索