1. 程式人生 > >多伺服器資料同步問題

多伺服器資料同步問題

多伺服器併發的操作同一個資料庫的同一張表。

有如下一張表:

/* Table: APP_TASK */

TASKNO VARCHAR2(22) not null,

TASK_TYPE VARCHAR2(2) not null,

EXECUTE_TIME DATE,

PARMAS_XML VARCHAR2(2000),

STATUS VARCHAR2(1) default '0'

……

表中的一條資料我們稱之為一個task。我們的程式有個方法每次取出50個task,每5秒鐘重複執行一次,然後根據每個task的引數去處理task,

處理過程中和處理完成後 會更新task的STATUS等引數。

如果只有一套程式去操作這個表,不會存在問題,但是現在要將這個應用程式部署到多個伺服器上,那麼每個伺服器都操作同一張表,都要取出task,然後再更新

如果不採取措施,我們無法保證它們取出的task不重複,比如伺服器A取出了task1、task2、task3,在同一時間,伺服器B也可能取出task2、task3,如此一來,就會亂套。

之前波哥提了一個方法:

"select * from task where flag = 0"; // 取任務

... // 一系列判斷

int i = "update task set flag = 1 where id='A' and flag = 0"; // 更新任務A,操作任務設定標誌位1

if i = 0 then

task A is used by another server // 如果修改條數為0,則該任務已經被別人處理了,本程序不用處理。

return

也就是說先把task查詢出來(只查1條),然後再更新這個task的flag標誌,如果更新成功,則表示這條task未被其他server佔用,否則,這個task已經被其他server佔用了

以前是一次查詢50個task出來,但是如果按上面的方法,就要每個task更新一次。50個task就要更新50次,而5秒鐘之後又要重複這個操作……

而且實際上,不管你更新1個task還是50個task,都會存在問題,update..where...時,會試圖將where查詢到的資料加鎖,如果該資料已經被其他連線加鎖了,

則update會一直等待其他鎖釋放。

我有個解決方案,用到 select...for update nowait 語句,

select 1個task for update nowait,然後立即 update 這個task set flag=1,最後commit

我也考慮過:

select 50個task for update nowait,但是這樣,另外一個伺服器select...for update nowait時就會因為資料被加鎖而報錯。

所以一次只能試探性的取一個出來。

這還是第一個問題。第二個問題:

如果我們取出來資料,設定flag=1,表示這條資料已經被使用了,如果伺服器重啟或者出現其他意外,導致取出來的這條資料沒有被執行,

那麼這條資料就被埋沒了(因為它的flag已經被設定成1了,而程式查詢的時候只查flag=0的資料)。

第三個問題:

下面要講的這個問題更嚴重了。

比如,我查詢出來1條資料(一個task),這個task從查詢出來,到執行結束,假設需要30秒鐘。

而如果在這30秒鐘之內,有個使用者發出指令要修改或者刪除這條資料,於是,先前查詢出來的task,與資料庫中的資料就不同步了。

所以說,我們需要在"從task被查詢出來到task執行結束"這個時間段內,對這條資料保持加鎖狀態,不允許其他程式再更改這條資料,而且,

必須要在同一個事務中完成對這個task的全部操作。

後來,我想到的一個方案:新增一個is_loaded標誌和load_time時間。

初始狀態IS_LOADED=0,獲取任務後,更改IS_LOADED=1以及LOAD_DATE=systime,任務執行完,從taskSet中移除,更新狀態IS_LOADED=0。

如果伺服器掛了(或者其他原因),那麼IS_LOADED=1,但是Now-load_time > N(這個N是我們設定的超時閥值),意思就是執行時間太長,過期了,我們可以認為可能是伺服器意外關掉了,這種資料可以被我們監控到。至於處理嘛,沒有簡單的方法,我頭都想大了……

將這種特殊任務交給多伺服器去處理,肯定是不行了,交給單一伺服器去處理:(我們只在其中一臺伺服器部署如下的方法)

這臺伺服器,除了查詢IS_LOADED=0的任務,還負責查詢Now-load_time>N的任務(就是SQL語句和其他伺服器上的稍有不同而已),如果有過期的任務,則將其狀態更改成1,使得它可以被重新載入執行。