最近做了一个类似计算器的程序,要求用户可以看到之前的计算结果,这就需要把计算参数和结果分别存储起来,但是这样会遇到用户一直用同样的数据做计算,后台就会一直在存相同的数据,导致数据冗余和浪费I/O资源,这时候就要做请求去重.我这里用到的redis去重.
通常我们传到后台的大多数都是json格式,中间会有时间字段,这样会干扰我们去重,所以我们就要利用工具类把干扰去重的字段去掉,如下
//去除指定字段APi
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);
log.debug("md5deDupParam = {}, excludeKeys = {} {}", md5deDupParam, Arrays.deepToString(excludeKeys), paramTreeMapJSON);
return md5deDupParam;
}
//MD5加密
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;
}
我这里用了测试类去测试
@Test
public static void main(String[] args) {
//两个请求一样,但是请求时间差一秒
String req = "{\n" +
"\"requestTime\" :\"2022102810001\",\n" +
"\"requestValue\" :\"1000\",\n" +
"\"requestKey\" :\"key\"\n" +
"}";
String req2 = "{\n" +
"\"requestTime\" :\"2022102810002\",\n" +
"\"requestValue\" :\"1000\",\n" +
"\"requestKey\" :\"key\"\n" +
"}";
//全参数比对,所以两个参数MD5不同
String dedupMD5 = new TestRedis().dedupParamMD5(req);
String dedupMD52 = new TestRedis().dedupParamMD5(req2);
System.out.println("req1MD5 = "+ dedupMD5+" , req2MD5="+dedupMD52);
//去除时间参数比对,MD5相同
String dedupMD53 = new TestRedis().dedupParamMD5(req,"requestTime");
String dedupMD54 = new TestRedis().dedupParamMD5(req2,"requestTime");
System.out.println("req1MD5 = "+ dedupMD53+" , req2MD5="+dedupMD54);
}
接下来不如正题–redis去重
在我们在写操作之前去做判断
String dedupMD5 = new ReqDedupHelper().dedupParamMD5(req,"CreateTime");//计算请求参数摘要,其中去除里面请求时间的干扰
String KEY = "dedup:U=" + userId + "M=" + method + "P=" + dedupMD5;
long expireTime = 3600000;// 3600000毫秒过期,一小时内的重复请求会认为重复
long expireAt = System.currentTimeMillis() + expireTime;
String val = "expireAt@" + expireAt;
// 直接SETNX不支持带过期时间,所以设置+过期不是原子操作,多线程高并发情况下会出问题,后面相同请求可能会误以为需要去重,所以这里使用底层API,保证SETNX+过期时间是原子操作
Boolean firstSet = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> connection.set(KEY.getBytes(), val.getBytes(), Expiration.milliseconds(expireTime),
RedisStringCommands.SetOption.SET_IF_ABSENT));
这里把业务代码去除了,后面根据返回Boolean去判断要不要写入数据库.