接口調用限頻(代理模式+滑動窗口)

目錄

代碼示例

接口

代理

接口實現

限流工廠

限流處理器接口

直接交換處理器

限流處理器

限流配置

滑動窗口限流


?

通過代理模式+滑動窗口,限流請求第三方平臺,避免出現第三方平臺拋出限流異常,影響正常業務流程,從出口出發進行限流請求。

代碼示例

接口

/*** 第三方請求*/
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.pswp.cn/web/64316.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/64316.shtml
英文地址,請注明出處:http://en.pswp.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,解壓并加載,如下圖所示: 屬性表如下: 二、符號化 為了便于比對不同的融合結果,查看屬性表根據…

JavaScript 實現動態產品展示網頁

JavaScript 實現動態產品展示網頁 1. HTML 頁面結構2. CSS 樣式設計3. JavaScript 實現功能功能總結 本文設計了一個基于 JavaScript 的動態產品展示網頁案例&#xff0c;核心功能包括&#xff1a; 動態產品分類過濾&#xff1a;通過點擊分類按鈕&#xff0c;僅顯示屬于該分類…

網絡爬蟲科普:原理、類型、策略與常用工具

網絡爬蟲科普&#xff1a;原理、類型、策略與常用工具 網絡爬蟲在當今互聯網時代扮演著極為重要的角色&#xff0c;它能幫助我們從海量的網絡信息中提取出有價值的數據。以下將從網絡爬蟲的基本概念、工作流程、類型、搜索策略以及常用工具等方面進行詳細科普介紹。 一、網絡…

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 系統展示 登陸頁面 注冊頁面 快遞員頁面 派單員訂單管理頁面 派單員訂單添…

Mac 查詢IP配置,網絡代理

常用命令 1.查詢IP ifconfig | grep "inet" 2.ping查詢 ping 172.18.54.19&#xff08;自己IP&#xff09; 3.取消代理&#xff0c;通過在終端執行以下命令&#xff0c;可以取消 Git 的代理設置 git config --global --unset http.proxy git config --global …

Spring創建異步線程,使用@Async注解時不指定value可以嗎?

在Spring中使用Async注解時&#xff0c;不指定value是可以的。如果沒有指定value&#xff08;即線程池的名稱&#xff09;&#xff0c;Spring會默認使用名稱為taskExecutor的線程池。如果沒有定義taskExecutor線程池&#xff0c;則Spring會自動創建一個默認的線程池。 默認行為…

Python小括號( )、中括號[ ]和大括號{}代表什么

python語言最常見的括號有三種&#xff0c;分別是&#xff1a;小括號( )、中括號[ ]和大括號也叫做花括號{ }&#xff0c;分別用來代表不同的python基本內置數據類型。 小括號&#xff08;&#xff09;&#xff1a;struct結構體&#xff0c;但不能改值 python中的小括號( )&am…

QML 之狀態

文章目錄 狀態示例 1&#xff1a;矩形的可見/隱藏切換功能介紹&#xff1a; 示例 2&#xff1a;按鈕的激活/非激活狀態功能介紹&#xff1a; 示例 3&#xff1a;面板的展開/折疊功能介紹&#xff1a; 示例 4&#xff1a;燈泡的開/關功能介紹&#xff1a; 總結 狀態 狀態是界面中…

C語言簡單測試總結

前言 在學C語言之前回顧一下C中的一些知識.選用的是中國大學MOOC中C程序設計(面向對象進階)中的C語言水平評估測試題. 題目 ?The keyword "unsigned" can modify the keyword [ B ] A.signed B.long C.long double D.float題解:unsigned是無符號的意識,通常在…

frp(s) 內網穿透 Liunx環境雙端Docker部署

FRP(Fast Reverse Proxy)是一款高性能的反向代理應用,主要用于內網穿透、負載均衡和反向代理等多種場景。它能夠將內網中的服務暴露給公網,實現遠程訪問。此外,FRP還可以用于接收類似GitHub或第三方提供的Webhook請求。在微服務架構中,FRP可以作為服務調用的反向代理,提…

代碼隨想錄算法訓練營第三十五天|01背包問題 二維和一維(卡碼網第46題)、416分割等和子集

day35 動態規劃part03 1. 01背包問題 二維 卡碼網第46題 01 背包&#xff1a;有n件物品和一個最多能背重量為w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的價值是value[i] 。每件物品只能用一次&#xff0c;求解將哪些物品裝入背包里物品價值總和最大。 動規五部…

【Unity3D】ECS入門學習(九)SystemBase

SystemBase&#xff1a;支持主線程或多線程執行篩選實體任務。 主要介紹是內部成員&#xff1a;Entities的各種篩選方法&#xff0c;其內部成員還有EntityManager ForEach方法篩選&#xff0c;傳遞一個有參委托函數進去&#xff0c;參數ref xxx組件類&#xff08;可填多個&…