md5会重复吗_如何优雅地处理重复请求(并发请求)

点击上方“服务端思维”,选择“设为星标”

回复”669“获取独家整理的精选资料集

回复”加群“加入全国服务端高端社群「后端圈」

810a78674525a28bb072c816b897a808.png

利用唯一请求编号去重

你可能会想到的是,只要请求有唯一的请求编号,那么就能借用Redis做这个去重——只要这个唯一请求编号在redis存在,证明处理过,那么就认为是重复的

代码大概如下:

String KEY = "REQ12343456788";//请求唯一编号
long expireTime = 1000;// 1000毫秒过期,1000ms内的重复请求会认为重复
long expireAt = System.currentTimeMillis() + expireTime;
String val = "expireAt@" + expireAt;

//redis key还存在的话要就认为请求是重复的
Boolean firstSet = stringRedisTemplate.execute((RedisCallback) 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;
}

业务参数去重

上面的方案能解决具备唯一请求编号的场景,例如每次写请求之前都是服务端返回一个唯一编号给客户端,客户端带着这个请求号做请求,服务端即可完成去重拦截。

但是,很多的场景下,请求并不会带这样的唯一编号!那么我们能否针对请求的参数作为一个请求的标识呢?

先考虑简单的场景,假设请求参数只有一个字段reqParam,我们可以利用以下标识去判断这个请求是否重复。用户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理论上可能会重复,但是去重通常是短时间窗口内的去重(例如一秒),一个短时间内同一个用户同样的接口能拼出不同的参数导致一样的MD5几乎是不可能的。

继续优化,考虑剔除部分时间因子

上面的问题其实已经是一个很不错的解决方案了,但是实际投入使用的时候可能发现有些问题:某些请求用户短时间内重复的点击了(例如1000毫秒发送了三次请求),但绕过了上面的去重判断(不同的KEY值)。

原因是这些请求参数的字段里面,是带时间字段的,这个字段标记用户请求的时间,服务端可以借此丢弃掉一些老的请求(例如5秒前)。如下面的例子,请求的其他参数是一样的,除了请求时间相差了一秒: 

//两个请求一样,但是请求时间差一秒
String 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 {

/**
*
* @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 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;
}
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;
}
}

下面是一些测试日志: 

public static void main(String[] args) {
//两个请求一样,但是请求时间差一秒
String req = "{\n" +
"\"requestTime\" :\"20190101120001\",\n" +
"\"requestValue\" :\"1000\",\n" +
"\"requestKey\" :\"key\"\n" +
"}";

String req2 = "{\n" +
"\"requestTime\" :\"20190101120002\",\n" +
"\"requestValue\" :\"1000\",\n" +
"\"requestKey\" :\"key\"\n" +
"}";

//全参数比对,所以两个参数MD5不同
String dedupMD5 = new ReqDedupHelper().dedupParamMD5(req);
String dedupMD52 = new ReqDedupHelper().dedupParamMD5(req2);
System.out.println("req1MD5 = "+ dedupMD5+" , req2MD5="+dedupMD52);

//去除时间参数比对,MD5相同
String dedupMD53 = new ReqDedupHelper().dedupParamMD5(req,"requestTime");
String dedupMD54 = new ReqDedupHelper().dedupParamMD5(req2,"requestTime");
System.out.println("req1MD5 = "+ dedupMD53+" , req2MD5="+dedupMD54);

}

日志输出:

req1MD5 = 9E054D36439EBDD0604C5E65EB5C8267 , req2MD5=A2D20BAC78551C4CA09BEF97FE468A3F
req1MD5 = C2A36FED15128E9E878583CAAAFEFDE9 , req2MD5=C2A36FED15128E9E878583CAAAFEFDE9

日志说明:

  • 一开始两个参数由于requestTime是不同的,所以求去重参数摘要的时候可以发现两个值是不一样的

  • 第二次调用的时候,去除了requestTime再求摘要(第二个参数中传入了”requestTime”),则发现两个摘要是一样的,符合预期。

总结

至此,我们可以得到完整的去重解决方案,如下:

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) 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;
}

— 本文结束 —

7555b900bc4a95529386c87990f9367d.gif

● 漫谈设计模式在 Spring 框架中的良好实践

● 颠覆微服务认知:深入思考微服务的七个主流观点

● 人人都是 API 设计者

● 一文讲透微服务下如何保证事务的一致性

● 要黑盒测试微服务内部服务间调用,我该如何实现?

cc5adf213df2c4c60df0e8a38a05a6a5.png

关注我,回复 「加群」 加入各种主题讨论群。

对「服务端思维」有期待,请在文末点个在看

喜欢这篇文章,欢迎转发、分享朋友圈

bc00ec81aa6dfe1ba4a7d960b69b5608.png在看点这里37a67f67eac9b6cb931979ada17c2071.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/551831.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机二级excel数据有效性,原来Excel数据有效性还可以这样做——制作二级下拉菜单...

很多时候,在进行设置数据有效性时,我们希望可以根据前一单元格的内容,使用动态的数据选项。如下图为一个员工信息表,现希望在F列制作下拉列表,下拉列表的内容根据E列输入的省份变化而变化,如在E2单元格中输…

win7系统安装信息服务器不可用怎么办,Win7系统下iis服务器应用程序不可用怎么办?...

Win7系统用户在使用电脑系统时都有自带可以方便用户们搭建网站的iis服务器。不过也有Win7系统用户反映在电脑系统里添加或删除组件时iis服务器无法添加,还弹跳出了应用程序不可用的提示窗口,这使用户非常苦恼,那么Win7系统下iis服务器应用程序…

++ 多核cpu 并行_一文读懂什么是多核并行计算(三)

导读:面向应用工程师的商业软件咨询、自研软件定制开发服务的仿真公众号,点击关注进入菜单,查看更多精彩内容。(三)如何实现多核并行计算呢?了解了多核、多Machine、多Rack后,我可以看一下软件(程序)是如何对这些资源进…

服务器放行6in4协议,最简单的接入IPv6网络的方法 – 6in4隧道

很多大学的校园网启用了IPv6,并且建设了很多IPv6专享的资源。毕业离校,由于现在的网络运营商绝大部分尚未支持IPv6,所以那些资源也就离我们远去了。机缘巧合之下,我知道了,原来我们可以这样接入IPv6网络。目前接入IPv6…

梦幻群侠传5帮派修炼_梦幻西游:2020年十大更新回顾 法连不秒空和连续战斗修复...

今年梦幻西游有过很多重大的维护更新,其中不少更新都对玩家产生中重大的影响,比如说法术连击第二下不会秒空气,以及副本的迭代等等,今天就来盘点下2020年梦幻西游的十大更新!一、各类副本优化迭代今年优化了多个副本,如…

前端图片上坐标连线_前端图形学(十三)——弹跳运动的深入之傲娇的小球

欢迎来到【畅哥聊技术】前端图形学相关技术文章,更多精彩内容持续更新中,敬请关注。前面我们说到了小球的弹跳运动,通过一个方向的加速度和摩擦力去影响小球的动画,其目标点也是一个固定不变的,似乎有些单调。那么我们今天继续小球…

服务器微信了早上好,微信早晨好问候语句动态图片 早上好发给朋友的微信早安问候语简短...

原标题:微信早晨好问候语句动态图片 早上好发给朋友的微信早安问候语简短嘀嘀嘀嘀,我的短信到啦。用关心方式,要你多注意休息;用体贴方式,要你轻松而快乐;用祝福方式,要你一切都过的好&#xff…

c语言调用createthread线程的头文件_易语言API多线程总汇

【thread】 即,线程,是进程中某一顺序的控制流,在单个程序中同时运行多个线程完成不同工作,称为多线程。易语言多线程理解:进程是一个可执行程序,由私有虚拟地址空间、代码、数据和其它操作系统资源组成&am…

oracle varchar默认长度_面试官:如何精确计算mysql数据库索引长度?

概述我们知道MySQL Innodb 对于索引长度的限制为 767 字节,并且UTF8mb4字符集是4字节字符集,则 767字节 / 4字节每字符 191字符(默认索引最大长度),所以在varchar(255)或char(255) 类型字段上创建索引会失败,提示最大索引长度为7…

android things 系统镜像文件_开始菜单搬家!Win 10X 系统 UI 全部重做,明年初就能用上...

不知道老伙计们还记不记得,小淙曾经报道过三次,关于微软新系统 Windows 10X 的消息。当时很多老伙计感觉太遥远,或者觉得它难以激起波澜。但现在看来微软布局已久,是铁了心要搞新系统了。因为 Windows 10X 系统已经准备好交付&…

通达信公式大全_通达信MACD金叉的选股公式大全

公式来源于网络&#xff0c;我只是用其中一个&#xff0c;一起复制来了&#xff0c;有需要的自取吧。1、0轴上方第一次金叉选股公式&#xff1a;DIFF:EMA(CLOSE,12) - EMA(CLOSE,26);DEA : EMA(DIFF,9);MACD : 2*(DIFF-DEA);xg:cross(diff,dea) and dea>-1.0 and dea<0.5…

mysql 时间差_后端从mysql取值返回0时区时间数据的问题

近日搞一个B/S项目&#xff0c;前端页面时间字段总是显示格林威冶时间&#xff0c;也就是0时区的时间&#xff0c;比北京时间差了8个小时。打开后台的数据库&#xff0c;在workbench中查询&#xff0c;结果显示的时间格式正常&#xff0c;为当前北京时间。该时间字段是在往数据…

abap视图字段限制_【第八章】视图

上一级目录&#xff1a;Mosh_完全掌握SQL课程_学习笔记 其它相关&#xff1a;数据概要【第八章】视图Views (时长18分钟)1. 创建视图Creating Views (5:36)小结就是创建虚拟表&#xff0c;自动化一些重复性的查询模块&#xff0c;简化各种复杂操作&#xff08;包括复杂的子查询…

生活质量衡量系统_数据质量与数据质量八个维度指标

数据质量与数据质量八个维度指标数据的质量直接影响着数据的价值&#xff0c;并且直接影响着数据分析的结果以及我们以此做出的决策的质量。质量不高的数据不仅仅是数据本身的问题&#xff0c;还会影响着企业经营管理决策&#xff1b;错误的数据还不如没有数据&#xff0c;因为…

linux的内置的账户_6 款面向 Linux 用户的开源绘图应用程序

既然你是一名 Linux 用户&#xff0c;为什么不关注一下开源绘图应用程序呢&#xff1f;-- Ankush Das(作者)小时候&#xff0c;当我开始使用计算机(在 Windows XP 中)时&#xff0c;我最喜欢的应用程序是微软的“画图”。我能在它上面涂鸦数个小时。出乎意料&#xff0c;孩子们…

ieda ts文件报错_使用TS开发微信小程序(1):环境搭建——VSCode+TS

前言现在接到小程序需要改版的需求&#xff0c;由于使用Ionic的经验&#xff0c;希望以后能够统一开发语言降低开发成本&#xff0c;所以想使用TypeScript进行开发。开发前准备工作先是看官网&#xff0c;在微信小程序的官方开发文档中找到TypeScript相关的支持介绍。微信官网描…

cad统计多条线段总长度插件_超级实用CAD技巧应用汇总!技巧大全、插件合集、快捷键合集等...

超级实用CAD技巧应用汇总&#xff01;技巧大全、插件合集、快捷键合集等各位朋友&#xff0c;CAD福利来啦&#xff01;超级实用CAD技巧应用汇总&#xff0c;千万不能错过&#xff01;有技巧大全、插件合集、快捷键合集、字体大全、常用图库大全、常见问题及解决办法、版本转换&…

局部放大_Origin教程|巧用ZOOM功能做数据对比和快速绘制局部放大图

微信公众号&#xff1a;有宝物的柜子编辑&#xff1a;落水无波2020-06-28 原创有时候在分析一些数据时&#xff0c;既需要观察整体又需要局部观测&#xff0c;那么怎么才能做到同时查看呢&#xff0c;就类似上面这样。这样就很容易的去发现有没有峰位偏移&#xff0c;同时查看与…

操作多台_一支热电偶能否连接多台显示仪表

一支热电偶能否连接多台显示仪表&#xff0c;这个问题常有人提出&#xff0c;因为随着DCS系统的应用及对管理工作的要求&#xff0c;需要对一个信号在多处显示也是常有的事。一支热电偶能否连接多台显示仪表或DCS系统板卡&#xff0c;连接后能否保证测量精度&#xff0c;这是人…

个性签名设计软件_佩服!我用Python设计了一个签名软件

临近年末&#xff0c;大家都忙着签发礼品&#xff0c;写的一手好的签名&#xff0c;会让大家更有成就感&#xff0c;今天&#xff0c;小安就带领大家来设计一个基于tkinter爬虫的签名设计软件&#xff0c;方便大家设计签名。要设计这款软件&#xff0c;就需要了解tkinter与爬虫…