今天簡單跟大家聊下指數退避算法(Exponential Backoff ),關於指數避退算法的話題開始前首先向大家拋出幾個問題:指數退避算法是什么呢?為什么要用指數退避算法呢?指數退避算法的應用場景有哪些呢?代碼如何實現呢?帶着這些疑問諸君且向下看。
指數退避算法到底是什么呢?wiki上有這么一段解釋:"Exponential backoff is an algorithm that uses feedback to multiplicatively decrease the rate of some process, in order to gradually find an acceptable rate"。通俗點說, 退避算法就是網絡上的節點在發送數據沖突后,等待一定時間后再發,等待時間是隨指數增長,從而避免頻繁的觸發沖突。在計算機網絡中,二進制指數退避算法或截斷指數退避算法常常作為避免網絡堵塞的一部分用於同一數據塊的重發策略。發生n次沖突后,等待時間在0~2^n-1個間隙時間(slot times) 之間選擇隨機選擇。比如,發生第一次沖突后,每個發送方將會等待0或1個間隙時間(slot times);第二次沖突后,每個發送方的等待時間將會在0到3個間隙時間(slot times) 之間任意選擇;第三次沖突后,每個發送方的等待時間將會在0到7個間隙時間(slot times) 之間任意選擇,以此類推,隨着沖突次數的增加,發送方的等待時間將會有成倍增加的可能性。而從“截斷( truncated)”的字面意思我們不妨可以猜出,到一定次數,指數運算會停止,也就是說等待時間不會再無限的增加下去。比如,設置上限n=10,則最長等待時間為1023個間隙時間。因為在等待時間內某些場景同樣有沖突發生的可能性,所以一般流程會在16次重試后終止。具體的退避算法如下:
-
確定基本退避時間,它就是爭用期(總線上的單程端到端傳播時延記為 x,以太網的端到端往返時間2x)。以太網把爭用期定為51.2us。對於10Mb/s以太網,在爭用期內可發送512bit,即64字節。也可以說爭用期是512比特時間。1比特時間就是發送1比特所需要的時間。所以這種時間單位與數據率密切相關。
-
從離散的整數集合[0,1,…,]中隨機取出一個數,記為r。重傳應推后的時間就是r倍的爭用期。上面的參數k按下面的公式計算: k=Min[重傳次數,10] 可見當重傳次數不超過10時,參數k等於重傳次數;但當重傳次數超過10時,k就不在增大而一直等於10。
-
當重傳達16次仍不能成功時(這表明同時打算發送的數據站太多,以致連續發生沖突),則丟棄該,並向高層報告。例如,在第1次重傳時,k=1,隨機數r從整數{0,1}中選一個數。因此重傳推遲的時間是0或爭用期,在這兩個時間中隨機選擇一個。若再發生碰撞,則重傳時,k=2,隨機數r就從整數{0,1,2,3}中選一個數。因此重傳推遲的時間是在0,2x ,4x和6x 這4個時間中隨機抽取一個。同樣,若在發生碰撞,則重傳時k=3,隨機數r就從整數{0,1,2,3,4,5,6,7}中選一個數。以此類推。若連續多次發生沖突,就表明可能有較多的站參與爭用信道。但使用退避算法可使重傳需要推遲的平均時間隨重傳次數而增大(這也稱為動態退避),因而減小發生碰撞的概率,有利於整個系統的穩定。
相信讀到這里,大家對第二個問題的答案也呼之欲出了吧, 大多數指數退避算法會利用抖動(隨機延遲)來防止連續的沖突。 但是,如果使用並發客戶端,抖動可幫助您更快地成功執行請求。
至於指數避退算法的場景有哪些呢?指數退避算法在計算機網絡中應用很廣泛,這里簡單說兩個場景,第一個場景,接入三方支付服務,在三方支付提供的接入接口規范中,服務方交易結束結果通知和商戶主動查詢交易結果都用到重發機制,這就是所謂的退避算法,這地方其實也引出了另一個知識點——接口的冪等性( 使用相同參數對同一資源重復調用某個接口的結果與調用一次的結果相同),這里不再過多贅述。第二個場景,在app應用中,很多場景會遇到輪詢一類的問題,一般的輪詢對於app性能和電量的消耗都是個巨大的災難。那如何解決這種問題呢?app在上一次更新操作之后還未被使用的情況下,使用指數退避算法來減少更新頻率,從而節省資源和減少電的消耗。
最后一個問題,這里簡單的用偽代碼和java代碼的方式給大家演示一下增量延遲輪詢的實現方法。
偽代碼
Do some asynchronous operation.· retries = 0 DO wait for (2^retries * 100) milliseconds status = Get the result of the asynchronous operation. IF status = SUCCESS retry = false ELSE IF status = NOT_READY retry = true ELSE IF status = THROTTLED retry = true ELSE Some other error occurred, so stop calling the API. retry = false END IF retries = retries + 1 WHILE (retry AND (retries < MAX_RETRIES))
java代碼
public enum Results { SUCCESS, NOT_READY, THROTTLED, SERVER_ERROR } /* * Performs an asynchronous operation, then polls for the result of the * operation using an incremental delay. */ public static void doOperationAndWaitForResult() { try { // Do some asynchronous operation. long token = asyncOperation(); int retries = 0; boolean retry = false; do { long waitTime = Math.min(getWaitTimeExp(retries), MAX_WAIT_INTERVAL); System.out.print(waitTime + "\n"); // Wait for the result. Thread.sleep(waitTime); // Get the result of the asynchronous operation. Results result = getAsyncOperationResult(token); if (Results.SUCCESS == result) { retry = false; } else if (Results.NOT_READY == result) { retry = true; } else if (Results.THROTTLED == result) { retry = true; } else if (Results.SERVER_ERROR == result) { retry = true; } else { // Some other error occurred, so stop calling the API. retry = false; } } while (retry && (retries++ < MAX_RETRIES)); } catch (Exception ex) { } } /* * Returns the next wait interval, in milliseconds, using an exponential * backoff algorithm. */ public static long getWaitTimeExp(int retryCount) { long waitTime = ((long) Math.pow(2, retryCount) * 100L); return waitTime; }
ok,就談到這啦,源於自己認知的局限性,也許談的一些地方會有錯誤之處,歡迎大牛指正。