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

news2024/12/28 9:51:10

目录

代码示例

接口

代理

接口实现

限流工厂

限流处理器接口

直接交换处理器

限流处理器

限流配置

滑动窗口限流


 

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

代码示例

接口

/**
 * 第三方请求
 */
public interface ThirdApi {

    /**
     * 发送消息
     *
     * @param userId 用户id
     * @param message 消息
     * @return 发送是否成功
     */
    boolean sendMessage(String userId, String message);
}

代理

/**
 * 第三方请求代理
 */
@Component
public class ProxyThirdApi implements ThirdApi {

    @Resource
    private ThirdApiServiceClient thirdApiServiceClient;
    @Resource
    private LimitProcessorFactory limitProcessorFactory;
    @Resource
    private YmlConstant ymlConstant;

    private ThirdApi thirdApi;

    @PostConstruct
    public void initThirdApi() {
        thirdApi = new ThirdApiImpl(thirdApiServiceClient, ymlConstant);
    }

    @Override
    @SneakyThrows
    public 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;

    @Override
    public 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 {

    @Resource
    private LimitProperties properties;

    @Getter
    private Map<String, LimitProperties.LimitData> propertiesMap;

    private final Map<String, LimiterProcessor> processorMap = new ConcurrentHashMap<>(10);


    @PostConstruct
    public 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 {

    @Override
    public 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;

    @Override
    public 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;

    @Data
    public 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-触发限流
     */
    @Override
    public 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.coloradmin.cn/o/2266863.html

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

相关文章

leetcode-42.接雨水-day19

思路&#xff1a;将一整个区域分成若干个小区域看&#xff0c;高低起伏 1.记录每个板子上的雨水数量&#xff0c;最后相加求和。h板高 2.-->由于每个板子区间能装多少水取决于他的最大前缀板高和最大后缀板高&#xff0c; 3. 然后根据短板效应&#xff0c;则每个板子区间最多…

Postman接口测试02|接口用例设计

目录 六、接口用例设计 1、接口测试的测试点&#xff08;测试维度&#xff09; 1️⃣功能测试 2️⃣性能测试 3️⃣安全测试 2、设计方法与思路 3、单接口测试用例 4、业务场景测试用例 1️⃣分析测试点 2️⃣添加员工 3️⃣查询员工、修改员工 4️⃣删除员工、查询…

自定义kali:增加60+常用渗透工具,哥斯拉特战版,cs魔改应有尽有,菜单栏启动

前言&#xff1a; 集合了六十多个工具&#xff0c;有师傅说需要&#xff0c;特搞来&#xff0c;我是脚本小子我爱用 介绍&#xff1a; 主要在菜单增加了非常多别人现成的工具&#xff0c;工具名单&#xff1a; 信息收集&#xff1a; 密探渗透测试工具 水泽 ehole 灯塔 …

数据结构(Java)——链表

1.概念及结构 链表是一种 物理存储结构上非连续 存储结构&#xff0c;数据元素的 逻辑顺序 是通过链表中的 引用链接 次序实现的 。 2.分类 链表的结构非常多样&#xff0c;以下情况组合起来就有 8 种链表结构&#xff1a; &#xff08;1&#xff09;单向或者双向 &#xff08;…

Linux 文件的特殊权限—Sticky Bit(SBIT)权限

本文为Ubuntu Linux操作系统- 第十九期~~ 其他特殊权限: 【SUID 权限】和【SGID 权限】 更多Linux 相关内容请点击&#x1f449;【Linux专栏】~ 主页&#xff1a;【练小杰的CSDN】 文章目录 Sticky&#xff08;SBIT&#xff09;权限基本概念Sticky Bit 的表示方式举例 设置和取…

PPT画图——如何设置导致图片为600dpi

winr&#xff0c;输入regedit打开注册表 按路径找&#xff0c;HKEY_CURRENT_USER\Software\Microsoft\Office\XX.0\PowerPoint\Options&#xff08;xx为版本号&#xff0c;16.0 or 15.0或则其他&#xff09;。名称命名&#xff1a;ExportBitmapResolution 保存即可&#xff0c;…

小米汽车加速出海,官网建设引领海外市场布局!

面对国内市场的饱和态势&#xff0c;中国企业出海步伐纷纷加速&#xff0c;小米也是其中的一员。Canalys数据显示&#xff0c;2024年第三季度&#xff0c;小米以13.8%的市场份额占比&#xff0c;实现了连续17个季度位居全球前三的成绩。 据“36 氪汽车”报道&#xff0c;小米汽…

Cocos Creator 试玩广告开发 第二弹

上一篇的项目是2d的&#xff0c;现在谈谈对于3d试玩项目的一些经历。 相对于2d来说&#xff0c;3d的项目更接近于Unity的开发&#xff0c;但是也有很多不一样的地方&#xff0c;具体的也可以参考Cocos给他官方示例。 Unity 开发者入门 Cocos Creator 快速指南 | Cocos Creator…

CTFshow—爆破

Web21 直接访问页面的话会弹窗需要输入密码验证&#xff0c;抓个包看看&#xff0c;发现是Authorization认证&#xff0c;Authorization请求头用于验证是否有从服务器访问所需数据的权限。 把Authorization后面的数据进行base64解码&#xff0c;就是我们刚刚输入的账号密码。 …

docker-开源nocodb,使用已有数据库

使用已有数据库 创建本地数据库 数据库&#xff1a;nocodb 用户&#xff1a;nocodb 密码&#xff1a;xxxxxx修改docker-compose.yml 默认网关的 IP 地址是 172.17.0.1&#xff08;适用于 bridge 网络模式&#xff09;version: "2.1" services:nocodb:environment:…

UGUI简单动画制作

一、最终效果 UI简单动画制作 二、制作过程 1、打开动画制作窗口 2、新建一个动画 3、给一个对象制作动画 4、创建动画控制器进行不同动画变换控制 5、书写脚本&#xff0c;通过按钮来进行不同动画切换 using System.Collections; using System.Collections.Generic; using U…

[SAP ABAP] 程序备份

备份当前程序到本地的方式如下&#xff1a; 1.复制粘贴 Ctrl A 、Ctrl V 2.【实用程序】|【更多实用程序】|【上载/下载】|【下载】 ​ 3.快捷键&#xff0c;支持多种格式导出(.abap .html .pdf 等) 在事务码SE38(ABAP编辑器)屏幕右下角&#xff0c;点击【Options选项】图…

代码随想录Day51 99. 岛屿数量,99. 岛屿数量,100. 岛屿的最大面积。

1.岛屿数量深搜 卡码网题目链接&#xff08;ACM模式&#xff09;(opens new window) 题目描述&#xff1a; 给定一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的矩阵&#xff0c;你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接…

【漏洞复现】CVE-2022-41678 Arbitrary JMX Service Invocation with Web Interface

漏洞信息 NVD - cve-2022-41678 Apache ActiveMQ prior to 5.16.5, 5.17.3, there is a authenticated RCE exists in the Jolokia /api/jolokia. 组件影响版本安全版本Apache:ActiveMQ< 5.16.6> 5.16.6Apache:ActiveMQ5.17.0 - 5.17.4> 5.17.4&#xff0c;> 6.…

Bash 脚本教程

注&#xff1a;本文为 “Bash 脚本编写” 相关文章合辑。 BASH 脚本编写教程 as good as well于 2017-08-04 22:04:28 发布 这里有个老 American 写的 BASH 脚本编写教程&#xff0c;非常不错&#xff0c;至少没接触过 BASH 的也能看懂&#xff01; 建立一个脚本 Linux 中有…

操作系统(26)数据一致性控制

前言 操作系统数据一致性控制是确保在计算机系统中&#xff0c;数据在不同的操作和处理过程中始终保持正确和完整的一种机制。 一、数据一致性的重要性 在当今数字化的时代&#xff0c;操作系统作为计算机系统的核心&#xff0c;负责管理和协调各种资源&#xff0c;以确保计算机…

48页PPT|2024智慧仓储解决方案解读

本文概述了智慧物流仓储建设方案的行业洞察、业务蓝图及建设方案。首先&#xff0c;从政策层面分析了2012年至2020年间国家发布的促进仓储业、物流业转型升级的政策&#xff0c;这些政策强调了自动化、标准化、信息化水平的提升&#xff0c;以及智能化立体仓库的建设&#xff0…

Windows和Linux安全配置和加固

一.A模块基础设施设置/安全加固 A-1.登录加固 1.密码策略 a.最小密码长度不少于8个字符&#xff0c;将密码长度最小值的属性配置界面截图。 练习用的WindowsServer2008,系统左下角开始 > 管理工具 > 本地安全策略 > 账户策略 > 密码策略 > 密码最小长度&#…

EleutherAI/pythia-70m

EleutherAI/pythia-70m” 是由 EleutherAI 开发的一个小型开源语言模型&#xff0c;它是 Pythia Scaling Suite 系列中参数量最小的模型&#xff0c;拥有大约 7000 万个参数。这个模型主要旨在促进对语言模型可解释性的研究&#xff1b; Pythia Scaling Suite是为促进可解释性…

Linux系统编程——详解页表

目录 一、前言 二、深入理解页表 三、页表的实际组成 四、总结&#xff1a; 一、前言 页表是我们之前在讲到程序地址空间的时候说到的&#xff0c;它是物理内存到进程程序地址空间的一个桥梁&#xff0c;通过它物理内存的数据和代码才能映射到进程的程序地址空间中&#xff…