絕大部分網(wǎng)絡(luò)上對冪等性的解釋類似于:
"冪等性是指重復(fù)使用同樣的參數(shù)調(diào)用同一方法時總能獲得同樣的結(jié)果。比如對同一資源的GET請求訪問結(jié)果都是一樣的。"
我認為這種解釋是非常錯誤的, 冪等性強調(diào)的是外界通過接口對系統(tǒng)內(nèi)部的影響, 外界怎么看系統(tǒng)和冪等性沒有關(guān)系. 就上面這種解釋, System.getCPULoad(), 這兩次調(diào)用返回能一樣嗎? 但因為是只讀接口, 對系統(tǒng)內(nèi)部狀態(tài)沒有影響, 所以這個函數(shù)還是冪等性的.
首先了解一下什么是冪等性,如果你沒有興趣可以直接跳過這段代數(shù)概念解釋 :)
冪等(idempotence)是來自于高等代數(shù)中的概念。
定義如下(加入了自己理解):
單目運算, x為某集合內(nèi)的任意數(shù), f為運算子如果滿足f(x)=f(f(x)), 那么我們稱f運算為具有冪等性(idempotent)
比如在實數(shù)集中,絕對值運算就是一個例子: abs(a)=abs(abs(a))
雙目運算,x為某集合內(nèi)的任意數(shù), f為運算子如果滿足f(x,x)=x, f運算的前提是兩個參數(shù)都同為x, 那么我們也稱f運算為具有冪等性
比如在實數(shù)集中,求兩個數(shù)的最大值的函數(shù): max(x,x) = x, 還有布爾代數(shù)中,邏輯運算 "與", "或" 也都是冪等運算, 因為他們符合AND(0,0) = 0, AND(1,1) = 1, OR(0,0) = 0, OR(1,1) = 1
在將冪等性應(yīng)用到軟件開發(fā)中,需要一些更深的理解. 我的理解如下:
數(shù)學(xué)處理的是運算和數(shù)值, 程序開發(fā)中往往處理的是對象和函數(shù). 但是我們不能簡單地理解為數(shù)學(xué)冪等中的運算就是函數(shù),而數(shù)值就是對象!!
比如有Person對象有兩個屬性weight和age,但是所有的function只能對其中一個屬性操作. 所以從這個層面我們可以理解為: 函數(shù)只對該函數(shù)所操作的對象某個屬性具有冪等性, 而不是說對整個對象有運算冪等性.
Person {
private int weight;
private int age;
//是冪等函數(shù)
public void setAge(int v){
this.age = v;
}
//不是冪等函數(shù)
public void increaseAge(){
this.age++;
}
//是冪等函數(shù)
public void setWeight(int v){
this.weight=v+10;//故意加10斤!!
}
}
還有一點必須要澄清的是: 冪等性所表達的概念關(guān)注的是數(shù)學(xué)層面的運算和數(shù)值, 并沒有提及到數(shù)值的安全性問題.
比如上面的Person的setAge函數(shù), 有兩種case不是冪等性所關(guān)心的, 但程序開發(fā)卻又必須要關(guān)心的:
- 兩個線程同時調(diào)用
- 因為age從業(yè)務(wù)上講不可能遞減, 如果前一次調(diào)用設(shè)置是30歲, 后一次調(diào)用變成了10歲或是更離譜的 -1 歲
所以RESTful設(shè)計中將冪等性和安全性是作為兩個不同的指標來衡量POST,PUT,GET,DELETE操作的:
| 重要 | 安全? | 冪等? |
|---|---|---|
| GET | 是 | 是 |
| DELETE | 否 | 是 |
| PUT | 否 | 是 |
| POST | 否 | 否 |
冪等性是系統(tǒng)的接口對外一種承諾(而不是實現(xiàn)), 承諾只要調(diào)用接口成功, 外部多次調(diào)用對系統(tǒng)的影響是一致的. 聲明為冪等的接口會認為外部調(diào)用失敗是常態(tài), 并且失敗之后必然會有重試.
就象cache有cache基本實現(xiàn)范式一樣, 冪等也有自己的固定外部調(diào)用范式
cache實現(xiàn)范式:
value getValue(key){
value = getValueFromCache(key);
if( value == null ){
value = readFromPersistence(key);
saveValueIntoCache(key,value);
}
return value;
}
冪等外部調(diào)用范式
client.age = 30;
while(一些退出條件){
try{
if(socket.setPersonAge(person,client.age) == FAILED){
int newAge = socket.getPersonAge();
//處理沖突問題: 因為age只可能越來越大,所以將client的age更新為server端更大的age
if(newAge>30){
client.age = newAge;
break;
} else{
<span style="white-space: pre; "> </span>//無法進行沖突解決,再次嘗試
}
} else return;
} catch(Exception){
//發(fā)生網(wǎng)絡(luò)異常, 再次嘗試
}
}
冪等接口的內(nèi)部實現(xiàn)需要有對內(nèi)保護機制, 一般情況是用類似于樂觀鎖的版本機制.版本重點是體現(xiàn)時間的先后.