一意のリクエスト番号を使用して重複を削除します
リクエストに一意のリクエスト番号がある限り、Redisを借りてこの重複排除を行うことができます。一意のリクエスト番号がredisに存在し、処理されたことを証明する限り、重複と見なされます。
コードはおおまかに次のとおりです。
String KEY = "REQ12343456788"; //一意の番号を要求する longexpireTime = 1000; // 1000ミリ秒が経過した後、1000ミリ秒以内に繰り返される要求は、繰り返される 長いexpireAt = System.currentTimeMillis()+ expireTime; String val = "expireAt @" + expireAt; // redisキーがまだ存在する場合は、リクエストが重複していると見なします Boolean firstSet = stringRedisTemplate.execute((RedisCallback <Boolean>)connection-> connection.set(KEY.getBytes()、val.getBytes()、Expiration。ミリ秒(expireTime)、RedisStringCommands.SetOption.SET_IF_ABSENT)); 最終ブールisConsiderDup; if(firstSet!= null && firstSet){//最初の訪問 isConsiderDup = false; } else {// redis値はすでに存在し、重複していると見なされます isConsiderDup trueに=; }
ビジネスパラメータの重複排除
上記のソリューションでは、一意のリクエスト番号を使用してシナリオを解決できます。たとえば、各書き込みリクエストの前に、サーバーはクライアントに一意の番号を返し、クライアントはこのリクエスト番号を使用してリクエストを行い、サーバーはインターセプト解除を完了することができます。
ただし、多くのシナリオでは、リクエストにはそのような一意の番号は含まれていません。では、要求されたパラメーターを要求IDとして使用できますか?
最初に単純なシナリオを考えてみましょう。リクエストパラメータにreqParamという1つのフィールドしかない場合、次の識別子を使用して、リクエストが繰り返されるかどうかを判断できます。ユーザーID:インターフェース名:リクエストパラメーター
String KEY = "dedup:U =" + userId + "M =" + method + "P =" + reqParam;
次に、同じユーザーが同じインターフェイスにアクセスし、同じreqParamが付属している場合、それを重複として見つけることができます。
しかし、問題は、通常、インターフェイスがそれほど単純ではないことです。現在の主流では、パラメータは通常JSONです。では、このようなシーンでは、どうすればよいのでしょうか。
パラメータ識別子としてリクエストパラメータの概要を計算します
リクエストパラメータ(JSON)をKEYの昇順で並べ替えてから、それらをKEY値として文字列に結合するとしますか?ただし、これは非常に長い可能性があるため、この文字列のMD5要約をパラメーターとして要求し、この要約を使用してreqParamの位置を置き換えることを検討できます。
String KEY = "dedup:U =" + userId + "M =" + method + "P =" + reqParamMD5;
このようにして、リクエストの一意の識別子がマークされます!
注:MD5は理論的には繰り返される可能性がありますが、重複排除は通常、短い時間枠(たとえば、1秒)内で行われます。同じユーザーインターフェイスが短い時間で異なるパラメーターを綴ることができるため、同じMD5になります。可能。
最適化を続け、いくつかの時間的要因を取り除くことを検討してください
上記の問題は実際には非常に優れた解決策ですが、実際に使用すると、いくつかの問題が見つかる可能性があります。一部のリクエストユーザーは短時間で繰り返しクリックします(たとえば、1000ミリ秒で3つのリクエストが送信されます)が、バイパスされます。上記の重複排除の判断(異なるKEY値)。
その理由は、これらのリクエストパラメータのフィールドに時間フィールドがあるためです。このフィールドはユーザーのリクエストの時刻を示し、サーバーはこれを使用して古いリクエストを破棄できます(たとえば、5秒前)。次の例のように、要求時間が1秒異なることを除いて、要求の他のパラメーターは同じです。
//两TT请求一種、しかし是请求時間差一秒 文字列req = "{\ n" + "\" requestTime \ ":\" 20190101120001 \ "、\ n" + "\" requestValue \ ":\" 1000 \ " 、\ n "+ " \ "requestKey \":\ "key \" \ n "+ "} "; String req2 = "{\ n" + "\" requestTime \ ":\" 20190101120002 \ "、\ n" + "\" requestValue \ ":\" 1000 \ "、\ n" + "\" requestKey \ ": \ "key \" \ n "+ "} ";
この種のリクエストの場合、後続の繰り返しリクエストをブロックする必要がある場合もあります。したがって、ビジネスパラメータの要約を求める前に、このような時間フィールドを削除する必要があります。同様のフィールドは、GPSの緯度と経度のフィールドである可能性があります(繰り返される要求の間には非常に小さな違いがある場合があります)。
重複排除ツールクラス、Java実装を要求する
public class ReqDedupHelper { / ** * * @paramreqJSON要求されたパラメーター。通常はJSONです * @ paramexcludeKeys要約を求める前に要求パラメーターのどのフィールドを削除する必要があります * @ 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); 文字列md5deDupParam = jdkMD5(paramTreeMapJSON); log.debug( "md5deDupParam = {}、excludeKeys = {} {}"、md5deDupParam、Arrays.deepToString(excludeKeys)、paramTreeMapJSON); md5deDupParamを返します。 } private static String jdkMD5(String src){ String res = null; { MessageDigest messageDigest = MessageDigest.getInstance( "MD5");を試してください。 byte [] mdBytes = messageDigest.digest(src.getBytes()); res = DatatypeConverter.printHexBinary(mdBytes); } catch(例外e){ log.error( ""、e); } return res; } }
テストログは次のとおりです。
public static void main(String [] args){ //两TT请求一種、しかし是请求時間差一秒 String req = "{\ n" + "\" requestTime \ ":\" 20190101120001 \ "、\ n" + " \ "requestValue \":\ "1000 \"、\ n "+ " \ "requestKey \":\ "key \" \ n "+ "} "; String req2 = "{\ n" + "\" requestTime \ ":\" 20190101120002 \ "、\ n" + "\" requestValue \ ":\" 1000 \ "、\ n" + "\" requestKey \ ": \ "key \" \ n "+ "} "; System.out.println( "req1MD5 =" + dedupMD5 + "、req2MD5 =" + dedupMD52); 文字列dedupMD53 = new ReqDedupHelper()。dedupParamMD5(req、 "requestTime"); 文字列dedupMD54 = new ReqDedupHelper()。dedupParamMD5(req2、 "requestTime"); System.out.println( "req1MD5 =" + dedupMD53 + "、req2MD5 =" + dedupMD54); }
ログ出力:
req1MD5 = 9E054D36439EBDD0604C5E65EB5C8267、req2MD5 = A2D20BAC78551C4CA09BEF97FE468A3F req1MD5 = C2A36FED15128E878EF583CAAAF2AAF2DE58E9E878EQEF2AAF2DE5 = req1MD5 = C2A36FED15128E878EF583CAAAF2AAF2DE58E9E878EQEF2AAF2DE5
ログの説明:
- 最初は、requestTimeのために2つのパラメーターが異なるため、重複排除パラメーターの概要を検索すると、2つの値が異なることがわかります。
- 2番目の呼び出しでは、requestTimeが削除され、要約が要求され( "requestTime"が2番目のパラメーターで渡されました)、2つの要約が同じであることがわかり、期待どおりでした。
総括する
これまでのところ、次のように完全な重複排除ソリューションを取得できます。
String userId= "12345678";//用户 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; }
最后
感谢大家看到这里,文章有不足,欢迎大家指出;如果你觉得写得不错,那就给我一个赞吧。
也欢迎大家关注我的公众号:程序员麦冬,每天更新行业资讯!