概述
這裏的流的速度限制是指在單位時間窗口內,最多允許指定的單位數據通過。比如我們需要從源端 A 發送 1000 條數據到目的端 B,如果設置的速度限制爲最多 100 條每秒,那麼理論上需要 10 秒的時間才能將數據傳輸完成,即使當前的網絡允許在極短的時間便完成這個任務。
但是我們沒辦法嚴格控制每秒時間內的數量一定是小於等於 100 的,因爲我們不能每傳輸一條數據便進行速度與其控制的計算,這樣會極大的帶來性能的損耗。
所以這裏引入時間窗口的概念,因爲我們希望能夠通過計算一個時間窗口內的速度,來判斷是否需要進行速度控制。如果該時間窗口內的速度大於閾值,那麼便通過睡眠一定的時間,來使更長時間上的平均速度是不高於閾值的。比如一個時間窗口的大小爲 1s,閾值仍爲 100 條每秒,那麼如果在這 1s 以內傳輸了 300 條數據,則需要等待 2s 以後才能繼續進行數據傳輸。這樣,即使在前 1s 速度超過了閾值(300 > 100),但是因爲後 2s 的速度爲 0,所以從整體上來看,這 3s 的的平均速度仍然是不高於閾值的。最終它的速度走勢也許會類似下圖:
可以看見,雖然閾值爲 100 條每秒,但是仍然會有部分時間窗口內的總量超過閾值,不過通過限制之後時間內的速度,從而使整體平均的速度是不高於閾值的。
公式
根據前面概述,可推理出公式。設置一下變量:
- 時間窗口的時間間隔爲:flowControlInterval(單位:毫秒)
- 實際時間間隔爲:interval(單位:毫秒)
- 實際時間間隔內傳輸的數據總量爲:numResults(單位:條)
- 最大限制速度爲:maxSpeed(單位:條/秒)
- 當前速度爲 currentSpeed(單位:條/秒)
- 等待休眠的時間爲:limitSleepTime(單位:毫秒)
那麼已知 flowControlInterval、interval、numResults 的值,可得到:
當前速度:
currentSpeed = numResults * 1000 / interval;
睡眠時間:
limitSleepTime = currentSpeed * interval / maxSpeed - interval;
使用 Java 代碼表示大致如下:
long interval = nowTimestamp - lastTimestamp;
if (interval >= flowControlInterval) {
long numResults = totlaResults - lastResults;
long currentSpeed = numResults * 1000 / interval;
if (currentSpeed > maxSpeed) {
// 計算休眠時間
limitSleepTime = currentSpeed * interval / maxSpeed - interval;
}
if (limitSleepTime > 0) {
Thread.sleep(limitSleepTime);
}
}
這裏的 nowTimestamp、totlaResults 爲當前時間的時間戳與當前總共傳輸的數據總量,lastTimestamp、lastResults 爲上一次計算後記錄的時間的時間戳與數據傳輸總量,通過計算可得到實際的時間間隔與該時間段內的數據傳輸量。
注:以上算法與代碼均參考於阿里的開源項目 DataX,源碼路徑:https://github.com/alibaba/DataX/blob/master/core/src/main/java/com/alibaba/datax/core/transport/channel/Channel.java#L192。
總結:
通過以上方式限制流的速度,可以對包括數量、字節大小等所有可以量化的指標進行限制,雖然不能保證每一個單位時間內的速度總是不高於閾值,但是卻能使平均的速度是不高於閾值的。