冪等性與重試(idempotence and retry)
/ idempotence: "eye-dem-POH-tens" /
想像按下「呼叫電梯」的按鈕。按一次召來電梯;再按五次什麼都不會變——電梯已經在來了。那個按鈕是冪等的:再做一次和只做一次效果相同。現在改想像一個「提領 100 元」的按鈕。按五次就領走 500 元。那一個不是冪等的,而當你想在失敗後重試的那一刻,這個差別就變得至關重要。
冪等性是這樣一個性質:把一個操作做超過一次,效果和恰好做一次相同。設定一個值(x = 5)是冪等的;遞增一個值(x = x + 1)不是。「若存在則刪除某檔案」是冪等的;附加一行不是。重試是「自動把失敗的操作再試一次」的策略——之所以有用,是因為許多失敗是短暫的:一個網路封包掉了、一台伺服器一時過載、一把鎖一瞬間被人持有。稍停一下再試一次,往往就成功了(通常搭配退避——在連續嘗試之間等得更久,以免猛敲一個正在掙扎的系統)。這兩個概念密不可分,因為唯有操作冪等時,重試才安全。如果你送出一個「對卡片扣款」的請求、沒收到回應、然後重試,你可能會對顧客扣款兩次——第一個請求其實可能已經成功,只是它的回覆丟失了。冪等性正是讓重試安全的東西:若操作冪等,一次重複的重試就不會造成額外傷害。
為何重要:重試是對付短暫失敗最便宜、最有效的健壯性工具之一,但若操作的副作用會疊加,它就是把上了膛的槍。這正是為什麼分散式系統如此費力地讓操作冪等——例如附上一個唯一的請求識別碼,好讓伺服器能認出並忽略重複(「我已經處理過請求 7 了」),把一個非冪等的動作變成可安全重試的。誠實的提醒:不是每種失敗都該重試——「找不到檔案」或「權限不足」每次都會以相同方式失敗,重試只是白費力氣;而沒有上限、沒有退避的天真重試,可能把一次短暫的打嗝變成自找的過載(重試風暴),讓一個掙扎中的系統一直起不來。
冪等且可安全重試:open(path, O_CREAT) 後接著寫入整份檔案內容——重跑一次得到的是同一個檔案。非冪等:一個單純附加日誌行的 write()——在回應丟失後重試,可能把那行寫兩次。修法通常是接收端用一個唯一識別碼來去重。
唯有「重做這個操作不會疊加額外副作用」時,重試才安全。
重試只對冪等操作才安全;重試一個非冪等的操作(一次扣款、一次附加)可能在「先前嘗試其實已成功、只是回覆丟失」時把它的效果複製一份。也要為重試設上限並退避,否則一次短暫的小故障會變成自找的重試風暴。