Java实现10万,并发去重,优雅地处理重复请求!
对于一些用户请求,在某些情况下是可能重复发送的,如果是查询类操作并无大碍,但其中有些是涉及写入操作的,一旦重复了,可能会导致很严重的后果,例如交易的接口如果重复请求可能会重复下单。
重复的场景有可能是:
-
黑客拦截了请求,重放
-
前端/客户端因为某些原因请求重复发送了,或者用户在很短的时间内重复点击
-
网关重发
-
….
本文讨论的是如何在服务端优雅地统一处理这种情况,如何禁止用户重复点击等客户端操作不在本文的讨论范畴。
利用唯一请求编号去重
可能会想到的是,只要请求有唯一的请求编号,那么就能借用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<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;}
业务参数去重
上面的方案能解决具备唯一请求编号的场景,例如每次写请求之前都是服务端返回一个唯一编号给客户端,客户端带着这个请求号做请求,服务端即可完成去重拦截。
但是,很多的场景下,请求并不会带这样的唯一编号!那么我们能否针对请求的参数作为一个请求的标识呢?
先考虑简单的场景,假设请求参数只有一个字段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<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;}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=A2D20BAC78551C4CA09BEF97FE468A3Freq1MD5 = C2A36FED15128E9E878583CAAAFEFDE9 , req2MD5=C2A36FED15128E9E878583CAAAFEFDE9
日志说明:
-
一开始两个参数由于requestTime是不同的,所以求去重参数摘要的时候可以发现两个值是不一样的。
-
另外,关于Java并发问题,公众号Java精选,回复java面试,获取更多面试题资料,支持在线随时随地刷题。
-
第二次调用的时候,去除了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<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;}
相关文章:
Java实现10万,并发去重,优雅地处理重复请求!
对于一些用户请求,在某些情况下是可能重复发送的,如果是查询类操作并无大碍,但其中有些是涉及写入操作的,一旦重复了,可能会导致很严重的后果,例如交易的接口如果重复请求可能会重复下单。 重复的场景有可…...
《深入解析 C#》—— C# 3 部分
文章目录 第三章 C#3:LINQ及相关特性3.1 自动实现属性(*)3.2 隐式类型 var(*)3.3 对象和集合初始化3.3.1 对象初始化器3.3.2 集合初始化器 3.4 匿名类型3.4.1 基本语法和行为3.4.2 编译器生成类型3.4.3 匿名类型的局限…...
Redis 的5种数据类型的基本命令
目录 String的基本命令 1. SET 2. GET 3. GETSET 4. STRLEN 5. APPEND 6. SETRANGE 7. GETRANGE 8. SETEX 9. SETNX 10. MSET 11. MGET 12. INCR 13. DECR 14. INCRBY 15. DECRBY 16. INCRBYFLOAT Map的基本命令 1. HSET 2. HGET 3. HMSET 4. HMGET 5. …...
【Liunx-后端开发软件安装】Liunx安装nginx
【Liunx-后端开发软件安装】Liunx安装nginx 使用安装包安装 一、简介 nginx,这个家伙可不是你厨房里的那位大厨,它可是互联网世界的“煎饼果子摊主”。想象一下,在熙熙攘攘的网络大街上,nginx挥舞着它的锅铲——哦不,是…...
力扣Lc20--- 202.快乐数(java版)-2024年3月20日
1.题目 2.知识点 (1)while (seen.contains(n) false) { // 循环体 } 与 !seen.contains(n) 等同 (2) 当传入数字 19 给 isHappy(19) 方法时,下面是每一行代码的执行过程: 初始化一个空的 HashSet&#…...
机器学习----交叉熵(Cross Entropy)如何做损失函数
目录 一.概念引入 1.损失函数 2.均值平方差损失函数 3.交叉熵损失函数 3.1信息量 3.2信息熵 3.3相对熵 二.交叉熵损失函数的原理及推导过程 表达式 二分类 联立 取对数 补充 三.交叉熵函数的代码实现 一.概念引入 1.损失函数 损失函数是指一种将一个事件&#x…...
Linux docker3--数据卷-nginx配置示例
一、因为docker部署服务都是以最小的代价部署,所以通常在容器内部很多依赖和命令无法执行。进入容器修改配置的操作也比较麻烦。本例介绍的数据卷作用就是将容器内的配置和宿主机文件打通,之后修改宿主机的配置文件就相当于修改了docker进程的配置文件&a…...
力扣454. 四数相加 II
思路:把四个数组拆成两对,两个分别相加,记录第一对的相加结果进map里,再把第二对数组 0-nums2-nums4 去map里面找出现了几次,这题不用对重复的四元组去重,所以出现多次都有效。 class Solution {public int…...
vulnstack1 渗透分析 红日靶场(一)
环境搭建 ip段设置 kali (coleak):192.168.145.139 Windows 7 (stu1):192.168.10.181、192.168.145.140 Winserver 2008 (owa):192.168.10.180 Win2k3 (root-tvi862ubeh):192.168.10.182复制 kali可以访问win7,但不能…...
外包干了6天,技术明显进步。。。
我是一名大专生,自19年通过校招进入湖南某软件公司以来,便扎根于功能测试岗位,一晃便是近四年的光阴。今年8月,我如梦初醒,意识到长时间待在舒适的环境中,已让我变得不思进取,技术停滞不前。更令…...
比较好的知识点
2023年Java超全面试题及答案解析---https://blog.csdn.net/qq_42301302/article/details/128785274 7分钟带你细致解析4个Java算法必刷题---https://blog.csdn.net/hcxy2022/article/details/127963797 50道JAVA基础算法编程题【内含分析、程序答案】---https://blog.csdn.net/…...
抖音开放平台的订单类API接口调用测试指南(内含详细步骤)
一、什么是抖音开放平台 抖音开放平台基于抖音母体,提供抖音服务基础设施和创新行业解决方案的平台。同时满足各类各类机构、创作者及服务商对于内容获取、分享的个性化需求,我们诚邀各个行业、不同阶段的合作伙伴与我们一起,共建内容良性生…...
HiveSQL一本通 - 案例实操
文章目录 0.HiveSQL一本通使用说明6.综合案例练习之基础查询6.1 环境准备创建数据表数据准备加载数据 6.2 简单查询练习1.查询姓名中带“山”的学生名单2.查询姓“王”老师的个数3.检索课程编号为“04”且分数小于60的学生的分数信息,结果按分数降序排列4.查询数学成…...
Axure RP 8中文---快速原型设计工具,一站式解决方案
Axure RP 8是一款专业的快速原型设计工具,以其直观易用的界面和丰富的功能受到广大用户的青睐。它支持用户通过拖放操作快速创建交互式原型,包括线框图、流程图等,并具备高保真度的设计能力。Axure RP 8还提供了团队协作和共享功能࿰…...
Available platform plugins are: minimal, offscreen, webgl, windows.
我在运行pyqt5开发的代码时,报错: This application failed to start because no Qt platform plugin could be initialized, Reinstalling the application may fix this problem. Available platform plugins are: minimal, offscreen, webgl, windows…...
创意无限,风险有度:2024愚人节海外网红营销策略解析
2024年愚人节即将到来,这个充满趣味与惊喜的节日,既是人们展示幽默与创意的舞台,也是品牌进行营销活动的绝佳时机。在这个特殊的日子里,通过海外网红营销来推广品牌或产品,无疑是一种富有创意的营销策略,但…...
深入理解 Session、Cookie 和 Token:网络安全和身份验证的重要概念
深入理解 Session、Cookie 和 Token:网络安全和身份验证的重要概念 在当今数字化的世界中,网络安全和身份验证是至关重要的议题。为了实现这些目标,我们常常使用诸如 Session、Cookie 和 Token 等概念。这些概念在 Web 开发、网络通信和安全…...
镜像站汇总
软件镜像站 查看linux版本,常见有centos, ubuntu, Debian cat /etc/os-release去清华软件源帮助页面,查找对应源设置方案(需要结合具体的系统版本),常用: Debian https://mirrors.tuna.tsinghua.edu.cn/help/debian/ 需要选则系…...
设计模式之抽象工厂模式解析
抽象工厂模式 1)问题 工厂方法模式中的每个工厂只生产一类产品,会导致系统中存在大量的工厂类,增加系统的开销。 2)概述 a)产品族 和 产品等级结构 产品等级结构:产品的继承结构; 产品族&…...
【毕设级项目】基于ESP8266的家庭灯光与火情智能监测系统——文末源码及PPT
目录 系统介绍 硬件配置 硬件连接图 系统分析与总体设计 系统硬件设计 ESP8266 WIFI开发板 人体红外传感器模块 光敏电阻传感器模块 火焰传感器模块 可燃气体传感器模块 温湿度传感器模块 OLED显示屏模块 系统软件设计 温湿度检测模块 报警模块 OLED显示模块 …...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
