解鎖新姿勢 | 如何用配置中心實現全域性動態流控?
摘要: 當資源成為瓶頸時,服務框架需要對消費者做限流,啟動流控保護機制。流量控制有多種策略,比較常用的有:針對訪問速率的靜態流控、針對資源佔用的動態流控、針對消費者併發連線數的連線控制和針對並行訪問數的併發控制。在分散式架構中,應用和應用之間的呼叫型別分為以下兩種,流控方式也略有不同。
點此檢視原文:https://yq.aliyun.com/articles/380180?spm=a2c41.11181499.0.0
當資源成為瓶頸時,服務框架需要對消費者做限流,啟動流控保護機制。流量控制有多種策略,比較常用的有:針對訪問速率的靜態流控、針對資源佔用的動態流控、針對消費者併發連線數的連線控制和針對並行訪問數的併發控制。在實踐中,各種流量控制策略需要綜合使用才能起到較好的效果。
在分散式架構中,應用和應用之間的呼叫型別分為以下兩種,流控方式也略有不同。
同步RPC類呼叫,比如RESTful,Dubbo,HSF等都屬於該類。對於該類同步呼叫,通常限流方式為兩種:針對服務提供者的併發全域性流控,或針對服務消費者的併發區域性流控。兩種的控制手段類似,都是通過限制服務端或客服端併發呼叫數來進行限制。
非同步MQ類呼叫,典型如RocketMQ, Kafka,等。對於該類非同步呼叫,通常限流方式是在訂閱端限流。限流方式為兩種:針對訊息訂閱者的併發流控,或針對訊息訂閱者的消費延時流控。
針對訊息訂閱者的消費延時流控基本原理是,在每次客戶端消費時,可以增加一個延時來控制消費速度,這樣理論消費併發最快速度為:
MaxRate = 1 / ConsumInterval * ConcurrentThreadNumber
比如如果訊息併發消費執行緒為20,延時為100ms,則理論上可以將併發消費控制在200以下。具體公式如下:
200 = 1 / 0.1 * 20
相比並發執行緒數流控,消費延時流控優點在於實現相對簡單,對MQ類客戶端包依賴較少,不需要客戶端提供控制併發執行緒數的動態調整介面。
以上各種流量控制方法,在分散式架構下,如果要做到全域性動態控制,一個簡單的技術方法是依賴配置中心,即通過配置中心來進行流控引數的下發。
下面章節詳細介紹如何基於配置中心來實現非同步訊息消費的全域性動態流控。使用的例子為阿里雲上的 MQ (訊息佇列)和 ACM (應用配置管理)兩款產品。
注:之所以用MQ為示例是因為在本文撰寫之時,正好MQ Consumer Client SDK並不支援動態調整現成併發數,因此通過基於ACM來動態調整消費延遲的方法正好可以解決MQ消費流控動態的問題。
基於消費延時流控的基本原理
基本原理如下。其中,管理員或應用程式通過ACM控制檯釋出消費延時配置(RCV_INTERVAL_TIME),所有MQ消費程式訂閱該配置。理論上,該配置從釋出到下發所有客戶端,可以在1秒內完成(取決於網路延時)。
程式碼示例
該章節基於配置中心來實現非同步訊息消費的全域性動態流控的程式碼示例。使用的例子為阿里雲上的MQ(訊息佇列)和ACM(應用配置管理)兩款產品,基於Java語言。關於SDK的詳細介紹,可參見兩款產品的官方文件。
在ACM上建立消費延時的引數,截圖如下。
設定全域性消費延時變數
首先,設定消費接收延時的全域性變數, 如下。
// 初始化訊息接收延時引數,單位為millisecondstaticintRCV_INTERVAL_TIME =10000;// 初始化配置服務,控制檯通過示例程式碼自動獲取下面引數ConfigService.init("acm.aliyun.com",/*租戶ID*/"xxx",/*AK*/"xxx",/*SK*/"yyy");// 主動獲取配置String content = ConfigService.getConfig("app.mq.qos","DEFAULT_GROUP",6000); Properties p =newProperties();try{ p.load(newStringReader(content)); RCV_INTERVAL_TIME = Integer.valueOf(p.getProperty("RCV_INTERVAL_TIME")); }catch(IOException e) { e.printStackTrace(); }
其次,設定ACM listener,確保當配置被修改時,即使更新 RCV_INTERVAL_TIME 引數, 如下。
// 初始化的時候,給配置新增監聽,配置變更會回撥通知ConfigService.addListener("app.mq.qos","DEFAULT_GROUP",newConfigChangeListener() {publicvoidreceiveConfigInfo(String configInfo) { Properties p =newProperties();try{ p.load(newStringReader(configInfo)); RCV_INTERVAL_TIME = Integer.valueOf(p.getProperty("RCV_INTERVAL_TIME")); }catch(IOException e) { e.printStackTrace(); } } });
設定 MQ 消費延時邏輯
完整例項如下。
注:這裡 RCV_INTERVAL_TIME 引數的訪問是故意沒有加鎖的,讀者可以自行思考原因。Aliyun ONS Client不提供動態執行緒併發數,預設併發為20。因此這裡正好使用消費延時引數來動態調節QoS。
//以下程式碼可直接貼在Main()函式裡Properties properties =newProperties(); properties.put(PropertyKeyConst.ConsumerId,"CID_consumer_group"); properties.put(PropertyKeyConst.AccessKey,"xxx"); properties.put(PropertyKeyConst.SecretKey,"yyy"); properties.setProperty(PropertyKeyConst.SendMsgTimeoutMillis,"3000");// 設定 TCP 接入域名(此處以公共雲生產環境為例)properties.put(PropertyKeyConst.ONSAddr,"http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet"); Consumer consumer = ONSFactory.createConsumer(properties); consumer.subscribe(/*Topic*/"topic-name",/*Tag*/null,newMessageListener() {publicActionconsume(Message message, ConsumeContext context) {// MQ Subscribe QoS logical start, // Each consuming process will sleep for RCV_INTERVAL_TIME seconds with 100 ms sleeping cycle.// Within each cycle, the thread will check RCV_INTERVAL_TIME in case it's set to a smaller value. // RCV_INTERVAL_TIME <= 0 means no sleeping.intrcvIntervalTimeLeft = RCV_INTERVAL_TIME;while(rcvIntervalTimeLeft >0) {if(rcvIntervalTimeLeft > RCV_INTERVAL_TIME) { rcvIntervalTimeLeft = RCV_INTERVAL_TIME; }try{if(rcvIntervalTimeLeft >=100) { rcvIntervalTimeLeft -=100; Thread.sleep(100); }else{ Thread.sleep(rcvIntervalTimeLeft); rcvIntervalTimeLeft =0; } }catch(InterruptedException e) { e.printStackTrace(); } }// MQ Subscribe interval logical endsSystem.out.println("Receive: "+ message);/*
* Put your business logic here.
*/doSomething();returnAction.CommitMessage; } }); consumer.start();
執行結果
單機執行consumer進行消費,假設queue內的訊息無限多,不存在消費萬的情況,分三段測試,分別執行約5分鐘,通過ACM配置推送來達到以下效果。
RCV_INTERVAL_TIME = 100 ms
RCV_INTERVAL_TIME = 5000 ms
RCV_INTERVAL_TIME = 1000 ms
結果如下,在單MQ消費業務處理耗時約100ms情況下的,單機併發20執行緒的測試結果。
RCV_INTERVAL_TIME = 100 ms:平均消費效能約為 9000 tpm 左右
RCV_INTERVAL_TIME = 5000 ms:平均消費效能被限制到了 200 tpm 左右
RCV_INTERVAL_TIME = 1000 ms:平均消費效能回升到到了 1100 tpm 左右
以上結果基本達到消費和 tpm 成反比的預期,最關鍵的是整個過程中,應用不中斷,流控推送結果秒級生效到分散式叢集。單機效能結果如下所示。
相關文章
- 解鎖自定義分享功能新姿勢
- 解鎖!玩轉 HelloGitHub 的新姿勢Github
- 科學家稱性愛機器人即將到來 解鎖性愛新姿勢!機器人
- 解鎖Android設計佈局的新姿勢Android
- 解鎖快取新姿勢——更靈活的 Cache快取
- ECS控制檯雲資源分組管理新姿勢—全域性標籤
- 新姿勢!Redis中呼叫Lua指令碼以實現原子性操作Redis指令碼
- 波卡生態現行的流動性解決方案
- 解鎖企業管理新姿勢,FileMaker定製化AppAPP
- WPF實現手勢解鎖
- 詳解Nacos 配置中心客戶端配置快取動態更新的原始碼實現客戶端快取原始碼
- 《人類跌落夢境》春節版本上線 解鎖新姿勢
- React - Context API 維護全域性狀態,實現全域性元件通訊ReactContextAPI元件
- 全域性鎖、表鎖、行鎖
- 全域性鎖和表鎖
- Go 武林外傳 - 解鎖 Micro 新姿勢之自定義服務Go
- 解鎖Search Ads優化新姿勢 |[多盟誠懇分享]優化
- MySQL 全域性鎖和表鎖MySql
- 用 canvas 實現 Web 手勢解鎖CanvasWeb
- 原生 JavaScript 實現手勢解鎖元件JavaScript元件
- 又拍雲張聰:OpenResty 動態流控的幾種姿勢REST
- MySQL全域性鎖、表鎖以及行鎖MySql
- 基於Apache Zookeeper手寫實現動態配置中心(純程式碼實踐)Apache
- 面試題詳解:如何用Redis實現分散式鎖?面試題Redis分散式
- 解鎖伺服器連線狀態新姿勢:tcping工具助你高效診斷網路連通性伺服器TCP
- 你是否真的瞭解全域性解析鎖(GIL)
- 手把手教你實現Java監聽器全域性監控Java
- uni 結合vuex 編寫動態全域性配置變數 this.baseurlVue變數
- 如何用 Redis 實現分散式鎖Redis分散式
- 003、v3admin學習,修改全域性配置如去掉水印等
- python GIL 全域性鎖Python
- Java中的動態配置更新:從配置中心到應用熱載入的實現Java
- 如何用Python實現iPhone X的人臉解鎖功能?PythoniPhone
- MySQL鎖問題分析-全域性讀鎖MySql
- Laravel 動態屬性的實現Laravel
- Hook方法的新姿勢--Stinger (使用libffi實現AOP )Hook
- 靜態全域性變數和全域性變數變數
- HTML5實現螢幕手勢解鎖HTML