接口调用限频(代理模式+滑动窗口)

目录

代码示例

接口

代理

接口实现

限流工厂

限流处理器接口

直接交换处理器

限流处理器

限流配置

滑动窗口限流


 

通过代理模式+滑动窗口,限流请求第三方平台,避免出现第三方平台抛出限流异常,影响正常业务流程,从出口出发进行限流请求。

代码示例

接口

/*** 第三方请求*/
public interface ThirdApi {/*** 发送消息** @param userId 用户id* @param message 消息* @return 发送是否成功*/boolean sendMessage(String userId, String message);
}

代理

/*** 第三方请求代理*/
@Component
public class ProxyThirdApi implements ThirdApi {@Resourceprivate ThirdApiServiceClient thirdApiServiceClient;@Resourceprivate LimitProcessorFactory limitProcessorFactory;@Resourceprivate YmlConstant ymlConstant;private ThirdApi thirdApi;@PostConstructpublic void initThirdApi() {thirdApi = new ThirdApiImpl(thirdApiServiceClient, ymlConstant);}@Override@SneakyThrowspublic boolean sendMessage(String userId, String message) {// 限流String bizLimit = "MSG_SEND_LIMIT";Object result = limitProcessorFactory.getProcessor(bizLimit).process(() -> thirdApi.sendMessage(userId, message));if (result instanceof Boolean) {return (Boolean) result;} else {return false;}}
}

接口实现

/*** 第三方请求实现**/
@Slf4j
@AllArgsConstructor
public class ThirdApiImpl implements ThirdApi {private final ThirdApiServiceClient thirdApiServiceClient;private final YmlConstant ymlConstant;@Overridepublic boolean sendMessage(String userId, String message) {MessageReq messageReq = new MessageReq();messageReq.setContent(message);messageReq.setReceiveId(userId);log.info("[ThirdApiImpl][sendMessage] {}", JSON.toJSONString(messageReq));HttpResponse<SendMessagesResp> sendResp = thirdApiServiceClient.sendMessage(messageReq);if (sendResp.isOk()) {return true;} else {log.error("[ThirdApiImpl][sendMessage] 消息发送失败,返回信息:{}", JSON.toJSONString(sendResp));return false;}}
}

限流工厂

/*** 限流工厂**/
@Component
public class LimitProcessorFactory {@Resourceprivate LimitProperties properties;@Getterprivate Map<String, LimitProperties.LimitData> propertiesMap;private final Map<String, LimiterProcessor> processorMap = new ConcurrentHashMap<>(10);@PostConstructpublic void initPropertiesMap() {List<LimitProperties.LimitData> props = properties.getProps();if (CollectionUtils.isEmpty(props)) {propertiesMap = Collections.emptyMap();} else {propertiesMap = props.stream().collect(Collectors.toMap(LimitProperties.LimitData::getName, Function.identity()));}}/*** 获取限流处理器** @param name 业务名称* @return 限流处理器*/public LimiterProcessor getProcessor(String name) {LimitProperties.LimitData props = propertiesMap.get(name);if (Objects.isNull(props)) {throw new BusinessException(String.format("无法找到[%s]的处理器配置", name));}if (props.getEnabled()) {return processorMap.computeIfAbsent(props.getName(), name -> {TimeUnit timeUnit = props.getTimeUnit();// 使用窗口滑动算法进行限流RateLimiter limiter = new SlidingWindowRateLimiter(props.getInterval(), props.getLimit(), timeUnit);return new LimiterProcessor(name, timeUnit.toMillis(props.getWaitTime()), limiter);});} else {return new SynchronousProcessor();}}
}

限流处理器接口

/*** 限流处理器接口*/
public interface LimiterProcessor {/*** 限流** @param callback 回调* @return 执行结果* @throws Throwable Throwable*/Object process(LimiterCallback callback) throws Throwable;
}

直接交换处理器

/*** 直接交换处理器** @author zhimajiang*/
@Slf4j
public class SynchronousProcessor implements LimiterProcessor {@Overridepublic Object process(LimiterCallback callback) throws Throwable {return callback.process();}
}

限流处理器

/*** 限流处理器**/
@Slf4j
@AllArgsConstructor
public class Processor implements LimiterProcessor {private final String name;private final long waitTime;private final RateLimiter rateLimiter;@Overridepublic Object process(LimiterCallback callback) throws Throwable {while (true) {if (rateLimiter.tryAcquire()) {// 未被限流,则尝试唤醒其他被限流的任务Object proceed = callback.process();synchronized (this) {this.notifyAll();}return proceed;} else {// 已被限流则进入阻塞log.info("LimiterProcessor][process] {}-限流", name);synchronized (this) {try {this.wait(waitTime);} catch (InterruptedException ignored) {}}}}}
}

限流配置

/*** 限流配置**/
@Data
@Configuration
@ConfigurationProperties("limit")
public class LimitProperties {/*** 限流配置*/private List<LimitProperties.LimitData> props;@Datapublic static class LimitData {/*** 名称*/private String name;/*** 是否启用*/private Boolean enabled = false;/*** 时间间隔*/private int interval;/*** 限制阈值*/private int limit;/*** 阻塞等待时间*/private int waitTime = 1000;/*** 时间单位*/private TimeUnit timeUnit = TimeUnit.MILLISECONDS;}
}

滑动窗口限流

/*** 滑动窗口限流**/
public class SlidingWindowRateLimiter implements RateLimiter {/*** 子窗口数量*/private final int slotNum;/*** 子窗口大小*/private final long slotSize;/*** 限流阈值*/private final int limit;/*** 上一次的窗口结束时间*/private long lastTime;/*** 子窗口流量计数*/private final AtomicInteger[] counters;/*** 滑动窗口限流** @param windowSize 时间窗口大小* @param slotNum    子窗口数量* @param limit      限流阈值* @param timeUnit   时间单位*/public SlidingWindowRateLimiter(int windowSize, int slotNum, int limit, TimeUnit timeUnit) {long windowSizeMills = timeUnit.toMillis(windowSize);this.slotNum = slotNum;this.slotSize = windowSizeMills / slotNum;this.limit = limit;this.lastTime = System.currentTimeMillis();this.counters = new AtomicInteger[slotNum];resetCounters();}/*** 滑动窗口限流** @param windowSize 时间窗口大小* @param limit      限流阈值* @param timeUnit   时间单位*/public SlidingWindowRateLimiter(int windowSize, int limit, TimeUnit timeUnit) {this(windowSize, 5, limit, timeUnit);}/*** 滑动窗口限流** @param windowSize 时间窗口大小(毫秒)* @param limit      限流阈值*/public SlidingWindowRateLimiter(int windowSize, int limit) {this(windowSize, 5, limit, TimeUnit.MILLISECONDS);}/*** 重置子窗口流量计数*/private void resetCounters() {for (int i = 0; i < this.slotNum; i++) {this.counters[i] = new AtomicInteger(0);}}/*** 限流请求** @return true-允许执行 false-触发限流*/@Overridepublic synchronized boolean tryAcquire() {long currentTime = System.currentTimeMillis();// 小窗口移动格数int slideNum = (int) Math.floor((double) (currentTime - this.lastTime) / this.slotSize);slideWindow(slideNum);// 窗口时间内的请求总数int sum = Arrays.stream(this.counters).mapToInt(AtomicInteger::get).sum();this.lastTime = this.lastTime + slideNum * slotSize;if (sum >= limit) {return false;} else {this.counters[this.slotNum - 1].incrementAndGet();return true;}}/*** 将计数器内全部元素向左移动num个位置** @param num 移动位置个数*/private void slideWindow(int num) {if (num == 0) {return;}if (num >= this.slotNum) {// 如果移动步数大于子窗口个数,则计数全部清零resetCounters();return;}// 对于a[0]~a[num-1]来说,移动元素则代表删除元素,所以直接从a[num]开始移动for (int index = num; index < this.slotNum; index++) {// 移动元素int newIndex = index - num;this.counters[newIndex] = this.counters[index];this.counters[index].getAndSet(0);}}
}

 

 

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

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

相关文章

不安全物联网的轻量级加密:综述

Abstract 本文综述了针对物联网&#xff08;IoT&#xff09;的轻量级加密解决方案。这项综述全面覆盖了从轻量级加密方案到不同类型分组密码的比较等多个方面。同时&#xff0c;还对硬件与软件解决方案之间的比较进行了讨论&#xff0c;并分析了当前最受信赖且研究最深入的分组…

【小程序】全局数据共享

目录 全局数据共享 1. 什么是全局数据共享 2. 小程序中的全局数据共享方案 全局数据共享 - MobX 1. 安装 MobX 相关的包 2. 创建 MobX 的 Store 实例 3. 将 Store 中的成员绑定到页面中 4. 在页面上使用 Store 中的成员 ​5. 将 Store 中的成员绑定到组件中 6. 在组件中…

自动化测试- 自动化测试模型

目录 自动化测试模型简介 1、线性模型 举例 测试页面html文件 测试脚本 2. 关键字驱动测试&#xff08;Keyword-Driven Testing&#xff09; 需测试内容 关键字驱动测试框架 创建测试用例文件 运行测试 3. 数据驱动测试&#xff08;Data-Driven Testing&#xff09; …

【GlobalMapper精品教程】091:根据指定字段融合图斑(字段值相同融合到一起)

文章目录 一、加载数据二、符号化三、融合图斑1. 根据图斑位置进行融合2. 根据指定字段四、注意事项一、加载数据 订阅专栏后,从私信中查收配套实验数据包,找到data091.rar,解压并加载,如下图所示: 属性表如下: 二、符号化 为了便于比对不同的融合结果,查看属性表根据…

strace工具使用

下载地址&#xff1a; https://github.com/strace/strace/releases/tag/v6.12 解压后执行以下命令 ./configure --hostarm-linux --prefix/home/wei/Code/strace/strace-6.12/out CC/home/wei/Code/firmware/prebuilts/host/gcc/gcc-arm-10.2-2020.11-x86_64-arm-none-linux…

图像处理-Ch2-空间域的图像增强

Ch2 空间域的图像增强 文章目录 Ch2 空间域的图像增强Background灰度变换函数(Gray-level Transformation)对数变换(Logarithmic)幂律变换(Power-Law)分段线性变换函数(Piecewise-Linear)对比度拉伸(Contrast-Stretching)灰度级分层(Gray-level Slicing) 直方图处理(Histogram …

Linux | Ubuntu零基础安装学习cURL文件传输工具

目录 介绍 检查安装包 下载安装 手册 介绍 ‌cURL是一个利用URL语法在命令行下工作的文件传输工具&#xff0c;首次发行于1997年‌‌12。cURL支持多种协议&#xff0c;包括FTP、FTPS、HTTP、HTTPS、TFTP、SFTP、Gopher、SCP、Telnet、DICT、FILE、LDAP、LDAPS、IMAP、POP3…

cesium通过经纬度获取3dtiles 得feature信息

找到这里3dtiles的两种访问方式&#xff1a; 1.1 3DTileContent#getFeature 这里涉及3DTile 数据结构&#xff0c;暂不了解3DTile 数据结构&#xff0c;因此暂不使用。 1.2 scene.pick 本次使用 scene表示虚拟场景中所有 3D 图形对象和状态的容器&#xff1b;scene中…

内置ALC的前置放大器D2538A/D3308

一、概述 D2538A/D3308是芯谷科技推出的带有ALC&#xff08;自动电平控制&#xff09;的前置音频放大器芯片&#xff0c;最初产品为单声道/立体声收录机及盒式录音机而开发&#xff0c;作为录音/回放的磁头放大器使用&#xff1b;由于产品的高增益、低噪声及ALC外部可调的特性&…

基于SSM的“快递管理系统”的设计与实现(源码+数据库+文档+PPT)

基于SSM的“快递管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 登陆页面 注册页面 快递员页面 派单员订单管理页面 派单员订单添…

frp(s) 内网穿透 Liunx环境双端Docker部署

FRP(Fast Reverse Proxy)是一款高性能的反向代理应用,主要用于内网穿透、负载均衡和反向代理等多种场景。它能够将内网中的服务暴露给公网,实现远程访问。此外,FRP还可以用于接收类似GitHub或第三方提供的Webhook请求。在微服务架构中,FRP可以作为服务调用的反向代理,提…

STM32F103RCT6学习之五:ADC

1.ADC基础 ADC&#xff08;Analog-Digital Converter&#xff09;模拟-数字转换器ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量&#xff0c;建立模拟电路到数字电路的桥梁12位逐次逼近型ADC&#xff0c;1us转换时间 输入电压范围&#xff1a;0~3.3V&#xff…

实现类似gpt 打字效果

1. css的动画&#xff08;animation) css中实现动画有两种方式&#xff1a;transition过渡动画、 animation自定义动画。 具体的可以看MDN链接&#xff1a;https://developer.mozilla.org/zh-CN/docs/Web/CSS/animation 使用keyframes自定义关键帧动画并未其命名使用自定义动…

微软远程桌面APP怎么用

微软远程桌面&#xff08;Remote Desktop&#xff09;客户端&#xff08;RD Client&#xff09;是一款由微软开发的应用程序&#xff0c;允许用户通过网络连接远程访问和控制另一台计算机。同时&#xff0c;微软远程桌面RD Client支持多种设备和操作系统&#xff0c;包括Window…

Unity3d UGUI如何优雅的实现Web框架(Vue/Rect)类似数据绑定功能(含源码)

前言 Unity3d的UGUI系统与Web前端开发中常见的数据绑定和属性绑定机制有所不同。UGUI是一个相对简单和基础的UI系统&#xff0c;并不内置像Web前端&#xff08;例如 Vue.js或React中&#xff09;那样的双向数据绑定或自动更新UI的机制。UGUI是一种比较传统的 UI 系统&#xff…

10分钟掌握项目管理核心工具:WBS、甘特图、关键路径法全解析

一、引言 在项目管理的广阔天地里&#xff0c;犹如一场精心编排的交响乐演奏&#xff0c;每个乐器、每个音符都需精准配合才能奏响美妙乐章。而 WBS&#xff08;工作分解结构&#xff09;、甘特图、关键路径法无疑是这场交响乐中的关键乐章&#xff0c;它们从不同维度为项目管…

代码思想之快慢路径

处理业务代码的过程中&#xff0c;对业务代码有了一些调整&#xff0c;后续发现这是一种代码思想 在一段复杂的逻辑里&#xff0c;我把查询redis操作写在了前面&#xff0c; 业务逻辑&#xff1a; 如果需要不打压就退出本次处理 查询redis拿到商品需要打压的次数 如果次数 …

纯 HTML+CSS+JS 实现一个炫酷的圣诞树动画特效

纯 HTMLCSSJS 实现一个炫酷的圣诞树动画特效 前言 圣诞节快到了&#xff0c;今天给大家带来一个简单但是效果不错的圣诞树动画特效。这个特效完全使用原生 HTML、CSS 和 JavaScript 实现&#xff0c;包含闪烁的星星、随机彩灯等元素&#xff0c;非常适合节日气氛&#xff01;…

【RAG实战】语言模型基础

语言模型赋予了计算机理解和生成人类语言的能力。它结合了统计学原理和深度神经网络技术&#xff0c;通过对大量的样本数据进行复杂的概率分布分析来学习语言结构的内在模式和相关性。具体地&#xff0c;语言模型可根据上下文中已出现的词序列&#xff0c;使用概率推断来预测接…

富芮坤FR800X系列之PWM输出程序应用设计

文章目录 前言1.设计背景2.简介3.如何设计控制调光的接口呢4.硬件设计5.软件设计5.1.软件流程图5.2.软件代码 6.小结 前言 版权归作者所有、未经允许、请勿转载。 读者对象&#xff1a; 本文档主要适用以下工程师&#xff1a; 嵌入式系统工程师 单片机软件工程师 IOT固…