幂等:
幂等性是系統的接口對外一種承諾(而不是實作), 承諾隻要調用接口成功, 外部多次調用對系統的影響是一緻的. 聲明為幂等的接口會認為外部調用失敗是常态, 并且失敗之後必然會有重試
舉個例子:
有一個訂單系統,對外提供了一個處理接口,
如果有個訂單001是要扣除使用者的100塊錢,那麼訂單001被多次調用,
也隻會處理成功一次,也就是隻會扣除使用者100塊。
也可以了解為去除重複調用
訂單的狀态:
a) 無記錄
b) 正在被處理
c) 處理成功
我們需要考慮多種情況:
情況1:
a) a線程開始處理訂單001
b) a線程處理訂單001成功
c) a線程記錄訂單001處理成功
情況2:
a) a線程開始處理訂單001
b) a線程處理訂單001失敗,復原
c) a線程删除訂單001的記錄
情況3:
a) a線程已成功處理訂單001
b) b線程開始處理訂單001,發現訂單001已經被成功處理,放棄此次處理
情況4:
a) a線程開始處理訂單001,正在進行中
b) b線程開始處理訂單001,發現訂單001正在被處理且未逾時,放棄此次處理
情況5:
a) a線程開始處理訂單001,且逾時了釋放了訂單001的狀态
b) b線程開始處理訂單001,發現訂單001無人處理,那麼鎖定訂單001開始處理
我們用redis來設計處理的幂等性,但是需要注意的是,這裡幂等性的有效期依賴redis的key的生命周期:
a) 正在處理,假設5分鐘則認為處理逾時(這個值需要根據程式的處理邏輯的逾時時間設定)
SET 訂單号 時間戳 過期時間
SET 1893505609317740 1466849127 EX 300 NX
b) 成功處理,利用SET的時間戳控制設定權
SET 訂單号 成功 過期時間
SET 1893505609317740 0 EX 86400 XX
c) 删除訂單,隻允許建立者删除,利用SET的時間戳控制删除權
DEL 1893505609317740
上python示範代碼:
# -*- coding: utf-8 -*-
import time
import redis
def check_validity(orderno, del_time):
return True
def deal():
return True
if __name__ == '__main__':
# 連接配接redis
r = redis.StrictRedis(host='localhost', port=6379, db=0)
# 訂單号
orderno = "1893505609317740"
# 目前時間戳
sec = int(time.time())
# 處理逾時時間
deal_timeout = 300
# 訂單狀态儲存多久,比如24小時不支付就失效了
del_time = 86400
# 是否成功處理
status = False
# 檢查訂單的有效期
if check_validity(orderno, del_time):
print "orderno is ok"
else:
print "orderno is overdue"
exit()
# 試圖鎖住訂單
res = r.set(orderno, sec, ex=deal_timeout, nx=True)
if res == True:
print "set key succeed"
else:
print "set key failed, key maybe exist, return"
exit()
# 成功處理了/失敗了
status = deal()
# 更改訂單狀态
redis_sec = r.get(orderno)
if sec != int(redis_sec):
print "check timestamp failed"
exit()
else:
print "check timestamp success"
if status == True:
# 設定訂單處理成功.設定為0
res = r.set(orderno, 0, ex=del_time, xx=True)
print "deal succeed: %s" % res
else:
# 删除訂單
res = r.delete(orderno)
print "deal failed: %s" % res
小結:
這種設計依然有缺陷,但是能保證大部分的訂單是幂等性的,
首先要保證redis是高可用,
其次訂單的處理過期時間很重要,不能随意設定,必須根據程式處理流程推理出來,
其次redis的存儲空間也影響了訂單的狀态儲存多久。