[英]How to handle race conditions in Web Service?
我用Java Servlets實現了一個Web服務。
我得到了以下設置:有一個處理'job'-entries的數據庫。 每個作業都具有“正在執行”或“在隊列中”或“已完成”的狀態。 如果用戶啟動新作業,則會在數據庫中創建一個帶有作業且狀態為“隊列”的條目。
只有在已執行少於五個其他作業的情況下才能執行作業。 如果還有其他五個已經執行狀態需要保持“隊列”並且Cronjob將在稍后處理該作業的執行。
現在我只是想知道,如果目前執行的工作少於五個,我的腳本將執行這項工作。 但是,如果同時在我的腳本詢問數據庫正在執行多少作業和開始執行作業的腳本之間,另一個用戶的另一個請求創建一個作業,並且因此得到“四個執行作業”數據庫。
然后會出現競爭條件,並且將執行6個工作。
我怎么能防止這樣的事情? 有什么建議? 非常非常感謝你!
如果我理解正確並且您可以控制向DB發出請求的應用程序層,則可以使用信號量控制誰正在訪問數據庫。
在某種程度上,信號量就像交通信號燈。 它們只能訪問N個線程的關鍵代碼。 因此,您可以將N設置為5,並且只允許關鍵代碼中的線程將其狀態更改為executing
等。
這是一個關於使用它們的好教程。
您可以使用記錄鎖定來控制並發。 一種方法是執行“select for update”查詢。
您的應用程序必須具有存儲worker_count的其他表。 然后你的servlet必須做如下:
獲取數據庫連接
關閉自動提交
以“IN QUEUE”狀態插入作業
執行“從...中選擇worker_cnt進行更新”查詢。
(此時執行相同查詢的其他用戶必須等到我們提交)
讀取worker_cnt值
如果worker_cnt> = 5則提交並退出。
(此時您獲得執行作業的票證,但其他用戶仍在等待)
將作業更新為“EXECUTING”
增加worker_cnt
承諾。
(此時其他用戶可以繼續查詢並獲得更新的worker_cnt)
做執行工作
將工作更新為'FINISHED'
減少worker_cnt
再次提交
關閉數據庫連接
編輯:我現在明白你的問題。 我做了另一個回復:)
是的,你可能有競爭條件。 您可以使用數據庫鎖來處理它們。 如果通常不以並發方式訪問記錄,請查看悲觀鎖。 如果通常以並發方式訪問記錄,請查看樂觀鎖。
蓋伊格林是對的,你要求的是一個可以用信號量解決的互斥情況。 Dijkstra的這個構造應該可以解決您的問題。
此構造通常用於代碼,一次只能由一個進程執行。 示例情況正是您面臨的情況; 例如,需要確保您不會遇到丟失更新或臟讀的數據庫事務。 為什么要同時執行5次? 當您允許同時執行時,您確定不會遇到這些問題嗎?
基本思想是在代碼中有一個所謂的關鍵部分,必須保護其免受競爭條件的影響。 需要互斥處理。 代碼的這一部分被標記為關鍵,並且在執行之前告訴其他方也要將其調用為wait()
。 一旦完成它的魔術,它就會調用notify()
,現在內部處理程序允許下一個進程在線執行臨界區。
但:
我強烈建議您不要自己實施任何互斥處理方法。 在幾年前的理論計算機科學課上,我們在操作系統級別上分析了這些結構並證明了可能出錯的地方。 乍一看它看起來很簡單,除了眼睛之外還有更多的東西,根據語言,如果你自己做的話,很難做到正確。 特別是在Java和相關語言中,您無法控制底層VM正在執行的操作。 相反,有預先實現的開箱即用解決方案已經過測試並證明是正確的。
在生產環境中處理互斥之前,請先閱讀一下,並確保理解它的含義。 例如,有一本信息量小書,這是一本寫得很好,很好閱讀的參考書。 至少瞥了一眼。
我不太確定Java Servlets,但Java確實有一個開箱即用的解決方案,用於在一個名為synchronized
的關鍵字中進行互斥,以標記代碼中不允許由多個進程同時執行的關鍵部分。 不需要外部庫。
一個很好的示例代碼中提供這種早期的崗位上SO。 雖然已經說明了,但是讓我提醒你真的使用notifyAll()
如果你處理幾個生產者/消費者,否則會發生奇怪的事情,並且在飢餓中旋轉的野生過程將會殺死你的貓。
關於該主題的另一個更大的教程可以在這里找到。
正如其他人的回應,這種情況需要信號量或互斥量。 我認為你可能要小心的一個領域是,權威的互斥體在哪里生活。 根據具體情況,您可以有幾種不同的最佳解決方案(權衡安全性與性能/復雜性):
a)如果你只有一個服務器(非集群),並且修改數據庫的唯一用例是通過你的Servlet,那么你可以實現一個靜態的內存互斥(一些你可以同步訪問的常見對象) 。 這對性能影響最小,並且最容易維護(因為所有相關代碼都在您的項目中)。 此外,它不依賴於您正在使用的特定數據庫的特性。 它還允許您鎖定對非數據庫對象的訪問。
b)如果你有幾個單獨的服務器,但它們都是你代碼的實例,你可以實現一個同步服務,它允許特定的實例在允許更新之前獲得鎖(可能有一個超時)。數據庫。 這將更復雜,但仍然所有邏輯都將駐留在您的代碼中,並且該解決方案將可跨數據庫類型移植。
c)如果您的數據庫可以由您的服務器或不同的后端進程(例如ETL)更新,那么唯一的方法是在數據庫中實現記錄級別鎖定。 如果這樣做,您將依賴於數據庫提供的特定類型的支持,如果您碰巧移植到其他數據庫,則可能需要更改。 在我看來,這是最復雜,最不易維護的選擇,只有在c)的條件明確無誤時才應該采用。
答案隱含在你的問題中:你的請求必須排隊,所以建立一個生產者和消費者的fifo隊列。
servlet總是在隊列中添加作業(可選擇檢查它是否已滿),另外5個線程將一次提取一個作業或在隊列為空時休眠。
沒有必要為此使用cron或mutex,只需記住同步隊列或消費者可以兩次提取相同的作業。
在我看來,即使你不使用ExecutorService,如果你總是更新數據庫並從單線程開始你的工作,最容易實現你的邏輯。 您可以在隊列中安排作業的執行,並有一個線程來執行並將數據庫狀態更新為正確的表單。
如果要控制執行的作業數。 一種方法是使用ExecutorsService和FixedThreadPool為5.這樣您就可以確定一次只執行5個作業而不再執行...所有其他作業將在ExecutorService中排隊。
我的一些同事會指出低級並發API。 我認為這些不是用於修復一般編程問題。 無論你決定做什么嘗試使用更高級別的API,而不是深入細節。 大多數低級別的東西已經在現有框架中實現,我懷疑你會做得更好。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.