解鎖新姿勢 | 如何用配置中心實現全域性動態流控?

weixin_34124651發表於2018-01-24

摘要: 當資源成為瓶頸時,服務框架需要對消費者做限流,啟動流控保護機制。流量控制有多種策略,比較常用的有:針對訪問速率的靜態流控、針對資源佔用的動態流控、針對消費者併發連線數的連線控制和針對並行訪問數的併發控制。在分散式架構中,應用和應用之間的呼叫型別分為以下兩種,流控方式也略有不同。

點此檢視原文: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秒內完成(取決於網路延時)。

10070702-4e8521fbe21f6c8b

程式碼示例

該章節基於配置中心來實現非同步訊息消費的全域性動態流控的程式碼示例。使用的例子為阿里雲上的MQ(訊息佇列)和ACM(應用配置管理)兩款產品,基於Java語言。關於SDK的詳細介紹,可參見兩款產品的官方文件。

在ACM上建立消費延時的引數,截圖如下。

10070702-8042c617c59e8142

設定全域性消費延時變數

首先,設定消費接收延時的全域性變數, 如下。

// 初始化訊息接收延時引數,單位為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 成反比的預期,最關鍵的是整個過程中,應用不中斷,流控推送結果秒級生效到分散式叢集。單機效能結果如下所示。

10070702-67201973521b8d83

相關文章