当前位置: 首页 > news >正文

如何对第三方相同请求进行筛选过滤

文章目录

    • 问题背景
    • 处理思路
    • 注意事项
    • 代码实现

问题背景

公司内部多个系统共用一套用户体系库,对外(钉钉)我们是两个客户身份(这里是根据系统来的),例如当第三方服务向我们发起用户同步请求:是一个更新用户操作,它会同时发送一个 delete 和 insert 请求,这两个请求几乎是并发进来的,实际上应该是先发起的delete 再 insert, 实际情况可能和网络延迟也有关系,此时在我们系统中就无法保证这两个请求的顺序执行,即先 delete 处理完之后 再进行 insert 的数据处理(正常流程),又或者直接把一定时间内同一个用户的 delete 和 insert 操作合并为一个update操作(本质就是更新操作)。

还有一种情况是:第三方系统中添加或者 删除一个用户时,会以两个客户的身份去发送两个相同的用户同步请求,但同一个用户在我们系统内用户数据只有一份,对应的接口肯定也都是同一个,即相同的添加接口会在一瞬间被调用两次,删除即使执行两次的话也没什么问题,问题是添加 即使在添加前判断了用户账号是否存在 并发过来的情况下还是避免不了一些脏数据的产生,加锁的话对整体影响又特别大。

处理思路

根据userId(账号)为每个请求分配一个房间(单独的线程),如果是第一次进来那么就new一个房间(也就是类,里边会有一个单独的线程处理这个用户的行为),后边一定时间内相同的 userId 进来会找到对应已存在的房间,当设置的时间窗口到了之后,判断当前userId的同步行为有哪些,如果有 insert 和 delete,那么直接转为 update 操作。如果是两个insert行为,那么最后就只调用一次insert服务,如果是两个delete行为,那么就只调用一个delete服务。

注意事项

时间窗口的设定,如果时间设置过短,属于同一个操作的请求因为网络波动 请求到接口的时间会有一定间隔,如果你设置的时间间隔小于等待的时间,还是会把本就属于同一批次的操作 多次处理

测试过程:刚开始时间设置的1500ms,也就是当第一个userId进来后,等待1.5秒后根据这段时间内收集到的用户行为再去真正的处理,后来在测试中发现有些本就属于同一批次的请求还是会被处理多次,也就是时间调小了,改成2000ms,测试还是发现同样的问题。最后:采取的是根据最近一个的userId请求的时间 等待1500ms,即相同的userId的请求进来后 在当前时间再重新计算等待1500ms,时间到了之后没有发现新的用户行为即算是一个批次结束

ps:可以创建一个单独的服务来负责对请求进行合理的处理分发,处理之后再去调用对应的业务系统服务

代码实现

定义操作行为枚举

public enum OperationEnum {INSERT("insert"),DELETE("delete"),;private final String value;OperationEnum(String operation) {this.value = operation;}public String getValue() {return value;}
}

定义每个用户所属的房间,房间内存储用户的多个行为(insert、delete):

public class ActionRoomBean {//用于保存有效事件数据 <insert or delete, data>private Map<String, JSONObject> actionDataMap = new HashMap<>();private String userId;//真正负责处理事件的线程private DispatchTask dispatchTask;/*** 定义操作方法 排队接收* @param action 请求动作:insert 或者 delete* @param data 请求参数*/public void addAction(String action, JSONObject data) {//有新请求进来后 计数器 + 1if(dispatchTask != null){dispatchTask.getIncrementAndGet();}//如果包含直接跳出if (actionDataMap.containsKey(action)) {return;}actionDataMap.put(action, data);}public ActionRoomBean() {}/*** 有参构造* @param userId 用户账号* @param actionDataMap 操作类型,请求参数*/public ActionRoomBean(String userId, Map<String, JSONObject> actionDataMap) {this.actionDataMap = actionDataMap;this.userId = userId;}/*** 创建完这个类的实例后,要先调用startManager方法 启动线程*/public void startManager() {dispatchTask = new DispatchTask(userId, actionDataMap);new Thread(dispatchTask).start();}
}

房间内真正的执行者(子线程):

public class DispatchTask implements Runnable {//等待的时间窗口private static long sleepTime = 1500;//计数器,用户有新的行为之后 +1,用来控制是否继续等待(sleep)private final AtomicInteger count = new AtomicInteger(0);//用于保存有效事件数据 <insert or delete, data>,与 ActionRoomBean中的 actionDataMap 指向的是同一个地址Map<String, JSONObject> actionDataMap;//用户账号String userId;/*** 有参构造*/public DispatchTask(String userId, Map<String, JSONObject> dataLib) {this.userId = userId;this.actionDataMap = dataLib;}@Overridepublic void run() {try {//线程等待前的数量和休眠后被唤醒的数量做对比,如果不相等说明休眠时间内有新的用户行为,则进入循环继续sleepint afterCount = 0;while (afterCount == 0 || afterCount != count.get()){//每休眠一次 +1,如果下次循环的值与 +1之后的afterCount相等,说明时间窗口内没有新的行为,则不循环afterCount = count.incrementAndGet();Thread.sleep(sleepTime);}} catch (InterruptedException e) {throw new RuntimeException(e);}try {String url = RestTemplateUtil.DD_READING_API_URL;JSONObject param = null;// 只有添加操作if (actionDataMap.containsKey(OperationEnum.INSERT.getValue()) && actionDataMap.size() == 1) {url += "/nc/eduUserInsert";param = actionDataMap.get(OperationEnum.INSERT.getValue());if (param != null) {RestTemplateUtil.postForObject(url, param.toJSONString());}} else if (actionDataMap.containsKey(OperationEnum.DELETE.getValue()) && actionDataMap.size() == 1) {//只有删除操作url += "/nc/eduUserDelete";param = actionDataMap.get(OperationEnum.DELETE.getValue());if (param != null) {RestTemplateUtil.postForObject(url, param.toJSONString());}} else if (actionDataMap.containsKey(OperationEnum.INSERT.getValue()) && actionDataMap.containsKey(OperationEnum.DELETE.getValue())) {//既有添加又有删除,就是更新处理url += "/nc/eduUserUpdate";param = actionDataMap.get(OperationEnum.INSERT.getValue());if (param != null) {RestTemplateUtil.postForObject(url, param.toJSONString());}}} finally {//最后从全局变量中删除userIdDispatchController.closeRoom(userId);}}/*** 计数器 + 1*/public Integer getIncrementAndGet() {return count.incrementAndGet();}
}

控制器:

@RestController
@RequestMapping(value = "/api/dd")
public class DispatchController {//全局map,记录当前有多少个用户正在被处理中private final static Map<String, ActionRoomBean> allMap = new ConcurrentHashMap<>();//简单的配置的密钥,用于接口的身份校验@Value("${url.secret}")private String secret;/*** insert和 delete 操作都会进入这个接口,用 operation 区分当前是什么操作*/@PostMapping(value = "/dispatch")public Result dispatch(@RequestBody JSONObject jsonObject,@RequestHeader(value = "secret") String secret){//进行简单的接口身份校验if(!Objects.equals(secret, this.secret)){return Result.generateError("secret eroor");}String userId = jsonObject.getString("userId");//operation = insert 或者 deleteString operation = jsonObject.getString("operation");if(EmptyUtil.isNotEmpty(userId) && EmptyUtil.isNotEmpty(operation)){//调用进入房间的方法unboltRoom(userId, operation, jsonObject);}return Result.generateSuccess();}/*** 为每个userId创建一个实例(房间)* 这里决定了是创建一个新的房间还是进入到已有的房间中*/private void unboltRoom(String userId, String operation, JSONObject jsonObject) {//加锁处理,由于真正的执行是在子线程中 所以加锁对整体性能影响也不是很大//主要是避免:同一个userId创建了多个实例,即使map中key不可重复,也会造成请求丢失//例如:同一个userId进来insert和delete请求各一个,并发不加锁的情况下就有可能创建了两个实例synchronized (this) {ActionRoomBean room = allMap.get(userId);//如果全局map中没有,说明是这个userId是第一个进来if (room == null) {Map<String, JSONObject> actionMap = new HashMap<>(4);actionMap.put(operation, jsonObject);room = new ActionRoomBean(userId, actionMap);//开启计时room.startManager();//放入到全局map中allMap.put(userId, room);}//如果有,直接调用 addAction方法room.addAction(operation, jsonObject);}}/*** 当前批次处理完之后,从集合中删除用户实例*/public static void closeRoom(String userId) {allMap.remove(userId);}
}

整体核心代码就是上边这些,以上还可以通过线程池去优化一下。

如果涉及到批量导入,同时有大量用户同步数据过来,就需要在测试环境进行反复测试 看是否会丢数据(因为每个用户都是一个独立的子线程),对线程的数量进行优化。

相关文章:

如何对第三方相同请求进行筛选过滤

文章目录 问题背景处理思路注意事项代码实现 问题背景 公司内部多个系统共用一套用户体系库&#xff0c;对外(钉钉)我们是两个客户身份(这里是根据系统来的)&#xff0c;例如当第三方服务向我们发起用户同步请求&#xff1a;是一个更新用户操作&#xff0c;它会同时发送一个 d…...

Go RPC

目录 文章目录 Go RPCHTTP RPCTCP RPCJSON RPC Go RPC Go 标准包中已经提供了对 RPC 的支持&#xff0c;而且支持三个级别的 RPC&#xff1a;TCP、HTTP、JSONRPC。但 Go 的 RPC 包是独一无二的 RPC&#xff0c;它和传统的 RPC 系统不同&#xff0c;它只支持 Go 开发的服务器与…...

真正的智能不仅仅是一个技术问题

智能并不是单一的技术问题&#xff0c;而是一个包括技术、人类智慧、社会制度和文化等多个方面的综合体&#xff0c;常常涉及技术变革、系统演变、运行方式创新、组织适应。智能是指人类的思考、判断、决策和创造等高级认知能力&#xff0c;可以通过技术手段来实现增强和扩展。…...

【数据结构】复杂度包装泛型

目录 1.时间和空间复杂度 1.1时间复杂度 1.2空间复杂度 2.包装类 2.1基本数据类型和对应的包装类 2.2装箱和拆箱 //阿里巴巴面试题 3.泛型 3.1擦除机制 3.2泛型的上界 1.时间和空间复杂度 1.1时间复杂度 定义&#xff1a;一个算法所花费的时间与其语句的执行次数成…...

Ae:绘画面板

Ae菜单&#xff1a;窗口/绘画 Paint 快捷键&#xff1a;Ctrl 8 绘画工具&#xff08;画笔工具、仿制图章工具及橡皮擦工具&#xff09;仅能工作在图层面板上。在使用绘画工具之前&#xff0c;建议先在绘画 Paint面板中查看或进行相关设置。 说明&#xff1a; 如果要在绘画描边…...

常见的锁和zookeeper

zookeeper 本文由 简悦 SimpRead 转码&#xff0c; 原文地址 zhuanlan.zhihu.com 前言 只有光头才能变强。 文本已收录至我的 GitHub 仓库&#xff0c;欢迎 Star&#xff1a;https://github.com/ZhongFuCheng3y/3y 上次写了一篇 什么是消息队列&#xff1f;以后&#xff0c;本来…...

经验总结:(Redis NoSQL数据库快速入门)

一、Nosql概述 为什么使用Nosql 1、单机Mysql时代 90年代,一个网站的访问量一般不会太大&#xff0c;单个数据库完全够用。随着用户增多&#xff0c;网站出现以下问题 数据量增加到一定程度&#xff0c;单机数据库就放不下了数据的索引&#xff08;B Tree&#xff09;,一个机…...

form表单与模板引擎

文章目录 一、form表单的基本使用1、什么是表单2、表单的组成部分3、 <form>标签的属性4、表单的同步提交及缺点&#xff08;1&#xff09; 什么是表单的同步提交&#xff08;2&#xff09; 表单同步提交的缺点&#xff08;3&#xff09; 如何解决表单同步提交的缺点 二、…...

医院检验信息管理系统源码(云LIS系统源码)JQuery、EasyUI

云LIS系统是一种医疗实验室信息管理系统&#xff0c;提供全面的实验室信息管理解决方案。它的主要功能包括样本管理、检测流程管理、报告管理、质量控制、数据分析和仪器管理等。 云LIS源码技术说明&#xff1a; 技术架构&#xff1a;Asp.NET CORE 3.1 MVC SQLserver Redis等…...

React 组件

文章目录 React 组件复合组件 React 组件 本节将讨论如何使用组件使得我们的应用更容易来管理。 接下来我们封装一个输出 “Hello World&#xff01;” 的组件&#xff0c;组件名为 HelloMessage&#xff1a; React 实例 <!DOCTYPE html> <html> <head> &…...

硕士学位论文的几种常见节奏

摘要: 本文描述硕士学位论文的几种目录结构, 特别针对机器学习方向. 1. 基础版 XX算法及其在YY中的应用 针对情况: 只有一篇小论文支撑. 第 1 章: 引言 ( > 5页) 1.1 背景及意义 (应用背景、研究意义, 2 页) 1.2 研究进展及趋势 (算法方面, 2 页) 1.3 论文结构 (1 页) 第 …...

找兄弟单词

描述 定义一个单词的“兄弟单词”为&#xff1a;交换该单词字母顺序&#xff08;注&#xff1a;可以交换任意次&#xff09;&#xff0c;而不添加、删除、修改原有的字母就能生成的单词。 兄弟单词要求和原来的单词不同。例如&#xff1a; ab 和 ba 是兄弟单词。 ab 和 ab 则不…...

python字典翻转教学

目录 第1关 创建大学英语四级单词字典 第2关 合并大学英语四六级词汇字典 第3关 查单词输出中文释义 第4关 删除字典中特定字母开头的单词 第5关 单词英汉记忆训练 第1关 创建大学英语四级单词字典 本关任务&#xff1a;编写一个能创建大学英语四级单词字典的小程序。 测…...

sentinel 随笔 3-降级处理

0. 像喝点东西&#xff0c;但不知道喝什么 先来段源码&#xff0c;看一下 我们在dashboard 录入的降级规则&#xff0c;都映射到哪些字段上 package com.alibaba.csp.sentinel.slots.block.degrade;public class DegradeRule extends AbstractRule {public DegradeRule(String…...

如何解决IP能ping通但无法上网的问题?

当我们在网络环境中遇到无法上网的问题时&#xff0c;可能会尝试使用ping命令来测试网络连接是否正常。如果ping测试成功&#xff0c;说明我们的IP地址能够和网络中其他设备进行通信&#xff0c;但是无法上网。这种情况下&#xff0c;我们需要采取一些措施来解决这个问题。本文…...

Autosar实践-CANTp

文章目录 前言一、CanTp是什么?二、Autosar配置三、诊断数据传输流程1.接收单帧失败,上层没有适当的buffer2.成功接收单帧3.成功发送单帧4.成功接收多帧5.成功发送多帧前言 CANTp模块作为提供数据拆包、组包、流控制传输的服务,在Autosar基础软件通信中起着至关重要的作用。…...

Redis简介

Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的键值对&#xff08;key-value&#xff09;数据库&#xff0c;支持网络、可基于内存亦可持久化。 Redis的数据结构包括列表&#xff08;List&#xff09;、集合&#xff08;Set&#xff09;、有序集合&#…...

报错问题修改

Vue 项目报错&#xff1a;‘$‘ is not defined ( no-undef ) 错误原因是不认识 $ 符&#xff0c;他是 JQuery 中得符号&#xff0c;引入了 JQuery 文件里的函数报错onclick is not defined问题(作用域问题) window.onload function (){ onload function (){ 第二种方法…...

专访惠众科技|元宇宙应用如何借助3DCAT实时云渲染实现流畅大并发呈现?

当前互联网流量红利已经逐渐消失&#xff0c;营销同质化愈发严重。在这样的背景下&#xff0c;催生了以为元宇宙 焦点的虚拟产业经济。元宇宙在各行各业中以不同形式快速萌生、成长&#xff0c;呈现出多元化的应用场景。尤其是众多品牌&#xff0c;将元宇宙视为品牌建设与营销新…...

加速开放计算产业化,OCTC五大原则瞄准需求痛点

回顾计算产业过去十余载的历程&#xff0c;开放计算始终是一个绕不开的核心焦点。 始于2011年Facebook发起的数据中心硬件开源项目--开放计算项目&#xff08;简称&#xff1a;OCP&#xff09;&#xff0c;开放计算犹如星星之火&#xff0c;不仅迅速形成燎原之势&#xff0c;更…...

告别浪费!SolidWorks企业级共享方案,实现降本增效全攻略

还在为 SolidWorks 高昂的硬件投入和混乱的图纸管理头疼&#xff1f;告别“一人一机”的浪费模式&#xff0c;企业级共享方案才是降本增效的正解。这套攻略基于“1 台高性能服务器 云飞云共享云桌面”架构&#xff0c;帮你把硬件成本砍掉 60%&#xff0c;把软件利用率翻倍。一…...

Taotoken平台快速获取APIKey并开始你的第一个Python调用示例

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 Taotoken平台快速获取APIKey并开始你的第一个Python调用示例 1. 准备工作&#xff1a;注册与登录 要开始使用Taotoken&#xff0c…...

手把手教你用Mind+和Blynk,让手机轻松遥控掌控板(含自建服务器避坑指南)

从零搭建物联网控制平台&#xff1a;Mind与Blynk深度整合实战 当你第一次尝试用手机控制硬件设备时&#xff0c;那种"隔空取物"的奇妙感总会让人兴奋不已。想象一下&#xff0c;躺在沙发上就能调节书桌上的智能台灯亮度&#xff0c;或者在外出时随时查看家中的温湿度…...

WarcraftHelper终极指南:深度解析魔兽争霸III现代化兼容性解决方案

WarcraftHelper终极指南&#xff1a;深度解析魔兽争霸III现代化兼容性解决方案 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper WarcraftHelper是一款专…...

【C语言】C 语言为什么叫 C 语言呢?

【C语言】C 语言为什么叫 C 语言呢&#xff1f;笔记改自于王道训练营资料 其实是因为先有高级语言ALGOL 60&#xff0c;简称 A 语言&#xff0c;后来经过简化&#xff0c;变为 BCPL 语言&#xff0c;简称 B 语言&#xff0c;而 C 语言是在 B 语言的基础之上发展而来的&#xff…...

通过Taotoken标准OpenAI协议实现分钟级集成现有代码

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 通过Taotoken标准OpenAI协议实现分钟级集成现有代码 1. 迁移背景与核心思路 许多开发团队在构建AI应用时&#xff0c;会直接使用O…...

告别手动复制!用这个自定义编辑器脚本一键备份/克隆Unity Terrain Data

告别手动复制&#xff01;用这个自定义编辑器脚本一键备份/克隆Unity Terrain Data在Unity关卡设计和技术美术的工作流中&#xff0c;地形数据的灵活复用往往意味着反复的手动操作——导出高度图、备份材质参数、复制植被分布&#xff0c;每个环节都可能成为效率瓶颈。想象这样…...

AWS DevOps Agent 完全指南

AWS DevOps Agent 是 AWS 推出的前沿 AI 运维代理,自主调查和解决事件、持续预防故障、提升系统可靠性。本文档覆盖从原理到实战的全生命周期管理。 一、定位与价值 一句话定义 AWS DevOps Agent = AI 驱动的 SRE 队友,724 自主调查告警、定位根因、生成修复方案、预防未来…...

大模型测试新范式:Claude端到端验证的5层断言体系(语义一致性/上下文连贯性/安全边界/成本阈值/时序鲁棒性)

更多请点击&#xff1a; https://codechina.net 第一章&#xff1a;大模型测试新范式&#xff1a;Claude端到端验证的5层断言体系&#xff08;语义一致性/上下文连贯性/安全边界/成本阈值/时序鲁棒性&#xff09; 传统LLM测试常聚焦于准确率或BLEU等静态指标&#xff0c;而Cla…...

AI专著生成必备工具,轻松撰写20万字专著,质量与效率双保障!

学术专著的写作是一个严谨的过程&#xff0c;其背后需要大量的资料和数据作为基础。搜集和整理这些资料与数据往往是写作过程中最繁琐且耗时的部分。研究人员需要广泛收集国内外的前沿文献&#xff0c;确保所用文献不仅具备权威性&#xff0c;还要与研究主题密切相关。同时&…...