當前位置:
首頁 > 知識 > 如何在Django模型中管理並發性

如何在Django模型中管理並發性

如何在Django模型中管理並發性

Python部落(python.freelycode.com)組織翻譯,禁止轉載,歡迎轉發。

為單用戶服務的桌面系統的日子已經過去了 - 網路應用程序現在正在為數百萬用戶提供服務,許多用戶出現了廣泛的新問題 - 並發問題。

在本文中,我將介紹在Django模型中管理並發性的兩種方法

如何在Django模型中管理並發性

問題

為了演示常見的並發問題,我們將使用銀行賬戶模型:

如何在Django模型中管理並發性

開始我們為帳戶實例提供一個簡單的存款和撤銷方法:

如何在Django模型中管理並發性

這似乎是足夠簡單的,甚至可能通過本地主機的單元測試和集成測試。 但是, 當兩個用戶同時在同一個帳戶上執行操作時會發生什麼?

1、用戶A提取帳戶 - 餘額為100 $。

2、用戶B提取帳戶 - 餘額為100 $。

3、用戶B退出30 $ - 餘額更新為100 $ - 30 $ = 70 $。

4、用戶A存款50 $ - 餘額更新為100 $ + 50 $ = 150 $。

這裡發生了什麼?

用戶B要求提取30 $,用戶A存入50 $ - 我們預期餘額為120 $,但最終為150 $。

為什麼會這樣呢?

在步驟4,當用戶A更新餘額時,他在存儲器中存儲的金額已經過時(用戶B已經退出30 $)。

為了防止這種情況發生,我們需要確保我們正在處理的資源在我們正在計算的過程中不會改變。

悲觀的做法

悲觀的做法表明,您應該完全鎖定資源,直到完成它 。 如果沒有人可以在您處理對象時獲取對象上的鎖定,那麼可以確保對象沒有被更改。

我們使用資料庫鎖有幾個原因:

1、 資料庫非常擅長管理鎖並保持一致性。

2、資料庫是訪問數據的最低級別 - 獲取最低級別的鎖也會防止其他進程嘗試修改數據。 例如,DB中的直接更新,cron作業,清理任務等。

3、Django應用程序可以在多個進程 (例如工作者)上運行。 在應用程序級別維護鎖將需要大量(不必要的)工作。

要在Django中鎖定一個對象,我們使用select_for_update 。

讓我們用悲觀的方法來實行安全的存款和取款:

如何在Django模型中管理並發性

按以下步驟:

1、我們在我們的查詢器上使用select_for_update來告訴資料庫鎖定對象,直到事務完成。

2、在資料庫中鎖定一行需要一個資料庫事務 - 我們使用Django的裝飾器transaction.atomic來定義事務。

3、我們使用類方法而不是實例方法 - 我們告訴資料庫要上鎖,然後它會返回鎖的對象給我們。 為了實現這一點,我們需要從資料庫中獲取對象。 如果我們使用self,那麼就是在操作一個已經從資料庫中獲取出來的對象,這個對象無法保證自己是沒有被上鎖的。

4、帳戶中的所有操作都在資料庫事務中執行。

讓我們看看如何通過我們的新方法來阻止前面說的情況:

1、用戶A要求退出30 $:

- 用戶A獲取帳戶上的鎖。

-餘額為100美元。

2、用戶B要求存入50 $:

- 嘗試獲取鎖定帳戶失敗(由用戶A鎖定)。

- 用戶B等待鎖釋放 。

3、用戶A撤回30 $:

- 餘額是70 $。

- 帳戶上的用戶A的鎖定被釋放 。

4、用戶B獲取帳戶上的鎖。

-餘額是70 $。

- 新餘額為70 $ + 50 $ = 120 $。

5、賬號上用戶B的鎖定被釋放,餘額為120 $。

Bug消失了!

這裡你需要了解select_for_update

1、在我們的方案中,用戶B等待用戶A釋放鎖,我們可以告訴Django 不要等待鎖釋放並引發DatabaseError。 為此,我們可以將select_for_update的nowait參數設置為True, …select_for_update(nowait=True) 。

2、選擇相關對象也被鎖定 -當使用select_for_update與select_related時,相關對象也被鎖定。

例如,如果我們選擇與用戶一起select_related帳戶,用戶和帳戶將被鎖定。 如果在存款期間,例如有人正在嘗試更新名字,該更新將失敗,因為用戶對象被鎖定。

如果您正在使用PostgreSQL或Oracle,這可能不是一個問題,由於即將到來的Django 2.0 的新功能 。 在此版本中,select_for_update具有「of」選項,用於顯式地聲明要鎖定查詢中的哪些表 。

我用過去的銀行賬戶示例來展示我們在Django模型中使用的常見模式,歡迎您在本下文中跟進:

https://medium.com/@hakibenita/bullet-proofing-django-models-c080739be4e

樂觀的方法

與悲觀的方法不同,樂觀的方法不需要鎖定對象。 樂觀的方法假定衝突不是很常見 ,並且指出只應確保在更新時對對象沒有做任何更改。

我們如何用Django來實現這樣的事情?

首先,我們添加一列以跟蹤對該對象所做的更改:

如何在Django模型中管理並發性

然後,當我們更新一個對象時,我們確保版本沒有改變:

如何在Django模型中管理並發性

接著:

1、我們直接在實例上操作(沒有類方法)。

2、我們依賴於每次更新對象時增加版本的事實。

3、僅當版本沒有更改時,我們才會進行更新:

- 如果對象沒有被修改,我們獲取它,而不是對象被更新 。

- 如果被修改 ,則查詢將返回零記錄,並且對象不會被更新 。

4、Django返回更新行數。 如果「更新」為零,則表示有人在我們獲取對象之後更改了對象。

樂觀鎖定在我們的場景中如何工作:

1. 用戶A獲取帳戶 - 餘額為100 $,版本為0。

2. 用戶B提取帳戶 - 餘額為100 $,版本為0。

3. 用戶B要求退出30 $:

- 餘額更新為100 $ - 30 $ = 70 $。

- 版本增加到1。

4. 用戶A要求存入50 $:

- 計算餘額為100 $ + 50 $ = 150 $。

- 該帳戶不存在與版本0 - > 沒有更新。

您需要了解樂觀的方法:

? 不像悲觀的方法,這種方法需要一個額外的空間和很多規則 。 克服紀律問題的一個方法是抽象這個行為。 django-fsm 使用如上所述的版本欄位來實現樂觀鎖定 。 django-optimistic-lock似乎也是這樣做的。 我們沒有使用任何這些包,但靈感來自這裡。

? 在具有大量並發更新的環境中,這種方法可能是浪費的。

? 這種方法不會對應用程序之外的對象進行修改。 如果您有直接修改數據的其他任務(例如,不通過模型),則需要確保它們也使用該版本。

? 使用樂觀的方法, 函數可以失敗並返回false。 在這種情況下,我們很可能想要重試操作。 使用悲觀的方法與nowait = False操作不能失敗 - 它將等待釋放鎖。

我應該使用哪一個?

像任何偉大的問題一樣,答案取決於以下:

? 如果您的對象有很多並發更新,那麼悲觀的方式更好。

? 如果您在ORM之外發生更新(例如,直接在資料庫中),則悲觀的方法更安全。

? 如果您的方法具有遠程API調用或操作系統調用等副作用,請確保它們是安全的。 還有些事情要考慮 - 遠程通話可能需要很長時間嗎? 遠程電話是否正常(重試安全)?


英文原文:https://ogmcsrgk5.qnssl.com/vcdn/1/優質文章長圖/how-to-manage-concurrency-in-django-models-b240fed4ee2.png
譯者:Mr Chen

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python部落 的精彩文章:

谷歌想要如何重寫互聯網?
軟體開發人員的21個想法
數據科學的3個階段
使用Cython來保護Python代碼庫

TAG:Python部落 |

您可能感興趣

Django意欲改革管理機構和模式
如何在 Emacs 中使用 Magit 管理 Git 項目
如何在Kubernetes中管理和操作Kafka集群
django之管理器
Apache Spark 統一內存管理模型詳解
Python啟用新的管理模式
DockerCon:Docker發布管理軟體容器的新功能
傳微軟正在開發全新文件管理器 採用Fluent Design技術
如何用 Google Tag Manager標籤管理器設置GA onclick按鈕點擊事件
Infortrend更新VMware認證,管理性與安全性雙劍出鞘
淺談C++ allocator內存管理(對比new的局限性)
Pixvana推出VR Casting 助用戶輕鬆管理和分發VR視頻
jvm 內存管理-hotspot虛擬機對象創建
Pixvana推出VR Casting,助用戶輕鬆管理和分發VR視頻
Arm通過收購Stream Technologies進一步增強在物聯網連接和設備管理的技術實力
Django自定義管理器
Kube:在VS Code中管理Helm charts
《Dead In Vinland》糅合了RPG、冒險和生存管理
TomTom Telematics推出新一代車隊管理軟體
DataOps 助力容量管理-Project 遷移優化應用實踐