1. 程式人生 > 其它 >後端處理重複請求

後端處理重複請求

對於一些請求,可能會出現多次點選的情況,如果是查詢類的請求還好,對資料不會造成影響,但是插入或者更新類的操作就會造成資料的更改,可能會導致很嚴重的後果。

利用唯一請求編號

客戶端在每次請求之前,先訪問一下伺服器獲取唯一請求編號,然後帶著這個編號請求伺服器,服務端使用redis進行查重,只要這個編號在redis存在,即認為此請求為重複請求

點選檢視程式碼
    String KEY = "12345678";//請求唯一編號
    long expireTime =  1000;// 1000毫秒過期,1000ms內的重複請求會認為重複
    long expireAt = System.currentTimeMillis() + expireTime;
    String val = "expireAt@" + expireAt;

    //redis key還存在的話要就認為請求是重複的
    Boolean firstSet = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> connection.set(KEY.getBytes(), val.getBytes(), Expiration.milliseconds(expireTime), RedisStringCommands.SetOption.SET_IF_ABSENT));

    final boolean isConsiderDup;
    if (firstSet != null && firstSet) {// 第一次訪問
        isConsiderDup = false;
    } else {// redis值已存在,認為是重複了
        isConsiderDup = true;
    }

利用業務引數做唯一標識

一般來說,我們的引數都是JSON格式的資料,可以對引數做一個引數的摘要,去除時間相關或者重複請求時有極小差別的引數,然後對字串進行MD5,將使用者的ID和方法名以及MD5作為唯一標識

String KEY = userId + method + MD5;

請求去重的工具類程式碼

點選檢視程式碼
public class ReqDedupHelper {

    /**
     *
     * @param reqJSON 請求的引數,這裡通常是JSON
     * @param excludeKeys 請求引數裡面要去除哪些欄位再求摘要
     * @return 去除引數的MD5摘要
     */
    public String dedupParamMD5(final String reqJSON, String... excludeKeys) {
        String decreptParam = reqJSON;

        TreeMap paramTreeMap = JSON.parseObject(decreptParam, TreeMap.class);
        if (excludeKeys!=null) {
            List<String> dedupExcludeKeys = Arrays.asList(excludeKeys);
            if (!dedupExcludeKeys.isEmpty()) {
                for (String dedupExcludeKey : dedupExcludeKeys) {
                    paramTreeMap.remove(dedupExcludeKey);
                }
            }
        }
        String paramTreeMapJSON = JSON.toJSONString(paramTreeMap);
        String md5deDupParam = jdkMD5(paramTreeMapJSON);
        return md5deDupParam;
    }

    private static String jdkMD5(String src) {
        String res = null;
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            byte[] mdBytes = messageDigest.digest(src.getBytes());
            res = DatatypeConverter.printHexBinary(mdBytes);
        } catch (Exception e) {
            log.error("",e);
        }
        return res;
    }
}

測試方法

點選檢視程式碼
    Long userId= 1L;//使用者
    String method = "pay";//介面名
    String dedupMD5 = new ReqDedupHelper().dedupParamMD5(req,"requestTime");//計算請求引數摘要,其中剔除裡面請求時間的干擾
    String KEY = "dedup:U=" + userId + "M=" + method + "P=" + dedupMD5;

    long expireTime =  1000;// 1000毫秒過期,1000ms內的重複請求會認為重複
    long expireAt = System.currentTimeMillis() + expireTime;
    String val = "expireAt@" + expireAt;

    // NOTE:直接SETNX不支援帶過期時間,所以設定+過期不是原子操作,極端情況下可能設定了就不過期了,後面相同請求可能會誤以為需要去重,所以這裡使用底層API,保證SETNX+過期時間是原子操作
    Boolean firstSet = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> connection.set(KEY.getBytes(), val.getBytes(), Expiration.milliseconds(expireTime),
        RedisStringCommands.SetOption.SET_IF_ABSENT));

    final boolean isConsiderDup;
    if (firstSet != null && firstSet) {
        isConsiderDup = false;
    } else {
        isConsiderDup = true;
    }

摘自這裡