2013-07-18 91 views
10

的谷歌App Engine文檔包含了這一段:GAE交易失敗和冪等

注意:如果您的應用程序提交一個 事務,當接收到一個例外,它並不總是意味着該交易失敗。 可能會收到DatastoreTimeoutException, ConcurrentModificationException或DatastoreFailureException 在事務已提交併且最終將成功應用 的情況下發生異常。只要有可能,使您的 數據存儲交易idempotent,以便如果您重複交易, 最終結果將是相同的。

等等,什麼?看起來有一類非常重要的事務,它們根本不可能變成冪等因爲它們依賴於當前的數據存儲狀態。例如,一個簡單的計數器,就像一個像按鈕一樣。交易需要讀取當前計數,增加它,並再次寫出計數。如果交易似乎「失敗」,但並不真正失敗,並且我無法在客戶端上說出這一點,那麼我需要再試一次,這將導致一次點擊生成兩個「喜歡」。 GAE有什麼方法可以防止這種情況發生?

編輯:

看來,這是在分佈式系統中固有的問題,按照比吉多·範羅蘇姆非其它 - 請參見以下鏈接:

app engine datastore transaction exception

所以看起來設計冪等如果你想要高度的可靠性,交易是非常必要的。

我想知道是否可以在整個應用程序中實現一個全局系統來確保冪等性。關鍵將是維護數據存儲中的事務日誌。客戶端會生成一個GUID,然後在請求中包含該GUID(對於同一個請求,重試時將重新發送相同的GUID)。在服務器上,在每個事務開始時,它將在數據存儲中查找具有該ID的Transactions實體組中的記錄。如果它找到了,那麼這是一個重複的事務,所以它會在沒有做任何事情的情況下返回。

當然,這需要啓用跨組交易,或將單獨的事務日誌作爲每個實體組的子項。如果失敗的實體密鑰查找速度很慢,性能也會受到影響,因爲幾乎每個事務都會包含失敗的查找,因爲大多數GUID都是新的。

就附加數據存儲交互而言額外的$成本而言,如果我必須使每個交易都是冪等的,這可能仍然會少一些,因爲這將需要大量檢查每個級別數據存儲中的內容。

+0

閱讀Nick Johnsons關於分佈式交易的文章 - http://blog.notdot.net/2009/9/Distributed-Transactions-on-App-Engine –

+0

這非常有趣。我正在考慮如何將該技術應用於創建可靠計數器的任務。如果只有一個用戶訪問該計數器就足夠簡單了:假設客戶端知道計數器的當前值,那麼只需將計數器的預期下一個值發送到數據庫,而不是發送消息「增加」。然而,我碰到的絆腳石是:如果多個用戶可能(可能同時)增加計數器,你將如何實現這一點。似乎應該有一種不涉及保存日誌的方式。 – eeeeaaii

+0

在數據存儲中有很多有關計數器的討論,如果您有很多併發的高頻更新,您會發現需要使用分片計數器才能獲得吞吐量。 –

回答

6

dan wilkerson,simon goldsmith,et al。在應用引擎的本地(每個實體組)事務之上設計了一個完整的global transaction system。在高層次上,它使用的技術類似於你描述的GUID。丹處理了「潛艇寫道」,即你描述的那些報告失敗但後來表面成功的交易,以及數據存儲的許多其他理論和實踐細節。 erick armbrust在tapioca-orm中實施了dan的設計。

我不一定建議您實施他的設計或使用木薯醇,但是您一定會對研究感興趣。

回答您的問題:很多人實施使用數據存儲沒有冪等性的GAE應用程序。只有當您需要與您描述的某種擔保進行交易時纔是重要的。瞭解你什麼時候需要它們是非常重要的,但你通常不會。

數據存儲在megastore之上實現,這在深度in this paper中進行了描述。簡而言之,它使用每個實體組內的multi-version concurrency control和跨數據中心進行復制的Paxos,這兩者都有助於潛艇寫入。我不知道數據存儲區中的潛艇寫入頻率是否有公共數字,但如果存在,則使用這些術語和數據存儲郵件列表的搜索應找到它們。

亞馬遜的S3並不是真正的可比系統;它比分佈式數據庫更像CDN。亞馬遜的SimpleDB具有可比性。它最初只提供eventual consistency,並最終添加了一種非常有限的交易,他們稱之爲conditional writes,但它沒有真正的交易。其他NoSQL數據庫(redis,mongo,couchdb等)在事務和一致性方面有不同的變化。

基本上,分佈式數據庫在規模,事務廣度和一致性保證強度之間總是存在折衷。這一點最爲人所知的是eric brewer的CAP theorem,其中說權衡的三個軸是一致性,可用性和分區容差。

+0

所以有趣的是,這篇論文談到潛艇寫作是寫作發生的情況,但讀取返回陳舊的數據。對我來說,這似乎不太關心。更有問題的問題是,App Engine文檔指出,在潛艇寫入的情況下,拋出異常,使客戶端認爲它必須重試。 – eeeeaaii

+0

另一件有趣的事情是,這篇論文似乎與Guido van Rossum在上述鏈接中所說的內容相矛盾 - 潛艇寫作特別似乎是針對應用引擎的,並且他特別說這是一個由應用引擎做出的優化決策球隊。所以,一般來說,CAP定理肯定,但潛艇寫作是特別的應用引擎的問題。他還提出了一個重要的觀點,即交易秩序從不妥協,因此如果重做交易會導致錯誤,則不應該有問題(似乎是圍繞真正冪等性的捷徑)。 – eeeeaaii

+0

要澄清第二條評論:假設您正在創建一條記錄,並且記錄的關鍵完全由客戶端發送的信息確定(例如,註冊頁面將發送用戶名,該關鍵字將成爲關鍵字)。假定潛艇寫入和「重試」類型的虛假錯誤(例如ConcurrentModificationException)。客戶端重試,記錄已經存在,引發了不屬於重試類型的錯誤,並且用戶看到錯誤但實際上已註冊。不是用戶友好的結果,但至少你的數據沒有被破壞,潛艇很少,對嗎? – eeeeaaii

0

另一個值得關注的選項是應用引擎的內置cross-group transaction支持,它允許您在單個數據存儲區事務中對最多五個實體組進行操作。

如果你喜歡閱讀堆棧溢出,this SO question有更多的細節。

1

我想出使計數器idempotent最好的方法是使用一組而不是一個整數來計數。因此,當一個人「喜歡」的東西,而不是增加一個計數器,我添加喜歡的東西是這樣的:

class Thing { 
Set<User> likes = .... 

public void like (User u) { 
    likes.add(u); 
} 
public Integer getLikeCount() { 
    return likes.size(); 
} 
} 

這是Java,但我希望你,即使你使用python我的觀點。

這種方法是冪等的,你可以添加一個用戶多少次你喜歡,它只會被計算一次。當然,它存在一個巨大的集合而不是簡單的計數器的懲罰。但是,嘿,你不需要跟蹤喜歡嗎?如果您不想膨脹Thing對象,請創建另一個對象ThingLikes,並在Thing對象上緩存相似計數。