後端處理重複請求
阿新 • • 發佈:2022-03-16
對於一些請求,可能會出現多次點選的情況,如果是查詢類的請求還好,對資料不會造成影響,但是插入或者更新類的操作就會造成資料的更改,可能會導致很嚴重的後果。
利用唯一請求編號
客戶端在每次請求之前,先訪問一下伺服器獲取唯一請求編號,然後帶著這個編號請求伺服器,服務端使用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; }