標籤:jedis redis java linux
Redis是一個新興的NoSql資料緩衝組件,與memcache類似,但是功能卻比memcache多一些。首先,Redis和memcache都是基於記憶體的,所以讀取和寫入速度都非常快。但是memcache只支援簡單的key-value資料的儲存方式,而Redis對key-value ,hash,list,set,SortSet等資料結構有很好的支援。下面就Redis在遊戲的開發應用中做一些簡單的介紹。
IT圖書網:http://www.myitbook.cn
一,資料的緩衝
在這一點上,redis和memcache是一樣的。都是把資料提前放入到記憶體中。當邏輯處理中需要用到資料時,先從記憶體中讀取,相同的,寫的時候也先向記憶體中寫入,然後再操作資料庫,以增加資料處理的速度。不同的是,redis帶有把資料寫入到硬碟的功能,具體的寫入策略可以在redis的設定檔中配置。這樣當主機突然出現故障時,比如斷電,重啟機器不會造成資料的丟失。這個在遊戲的應用中特別重要。一般在遊戲開發中,資料的處理會採用:緩衝 + 持久化隊列 + 資料庫(mysql)的架構。執行的流程是先把資料寫入到緩衝,然後把需要持久化的資料放入到持久化隊列中,啟動一個守護線程,從持久化隊列中不斷的取出資料,並存入或更新到資料庫。如果使用memcache這樣沒有寫入到硬碟功能的緩衝組件,出現故障時,持久人隊列中如果還有沒有處理完的資料,那麼就會造成資料的丟失,引用玩家的資料出現短暫的回檔。當然這些也可以自己開發一些功能去防止,但是增加了開發成本。
二,不遺失資料的持久化隊列的實現
上面說過Redis具有把資料寫入到硬碟的功能,而且支援多種資料結構。那麼就可以利用Redis的list實現持久化隊列,而且當機器出現故障時,不會出現隊列中資料丟失的情況,重啟之後,資料會自動載入到redis的list之中。
具體實現方法:(1)在Redis中構造一個list儲存
(2)一個線程使用Redis的lpush方法,向list的左邊加入資料
(3)另外一個線程使用Redis的rpop方法,從list取出資料進行處理,並且從list中刪除了取出的資料。這樣就實現了一個簡單的生產者--消費者模式的隊列。
三,對並行作業的控制
IT圖書網:http://www.myitbook.cn
一般來說,我們操作一個資料的流程是這樣的,取出--處理---儲存,這樣在單線程中操作是沒有任務問題的,但是在多線程環境中就不適用了,我們必須考慮資料同步的問題,保證資料操作的原子性。如果在遊戲中,對玩家戰隊的屬性進行更新,一般在資料庫中都會儲存一個TeamInfo表,裡面有玩家相應的屬性,比如名字,等級,金幣,鑽石等等。在memcache中儲存一個TeamInfo對象,這時玩家獲得金幣,我們就需要取出玩家所有的屬性,然後set金幣,完成後再儲存整個對象。這個時候就得考慮資料的同步了,如果在操作的時候,另外一個線程B修改了鑽石,並完成了儲存,而這個時候我把金幣修改完成之後,再儲存,這時,就出現了資料混亂的結果。考慮資料同步無非也是加鎖或樂觀同步。不但增加了代碼量,還增加了維護的難度。而在Redis中,它支援對hash資料結構的操作。我們可以把玩家的對象按每個欄位儲存到redis的hash中。結構如:
當我需要更新金幣時,比如增加或減少,我可以使用Redis內建的原子操作方法:hincrby(String key,String field,int value)進行操作,value是正為加,是負為減,這樣就簡化和避免了一些並行作業,而且這個操做還減少了對資料的操作步驟,因為沒有取出,再操作的過程了,只有一次寫入。而且在遊戲中很少一次更新非常多的欄位,如果有這樣的情況,下面的方法可以解決
三,對事務的支援
redis提供了一個事務操作的機制,MULTI 命令用於開啟一個事務,它總是返回 OK 。
MULTI 執行之後, 用戶端可以繼續向伺服器發送任意多條命令, 這些命令不會立即被執行, 而是被放到一個隊列中, 當 EXEC 命令被調用時, 所有隊列中的命令才會被執行。
另一方面, 通過調用 DISCARD , 用戶端可以清空事務隊列, 並放棄執行事務。
以下是一個事務例子, 它原子地增加了 foo 和 bar 兩個鍵的值:
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1
EXEC 命令的回複是一個數組, 數組中的每個元素都是執行事務中的命令所產生的回複。 其中, 回複元素的先後順序和命令發送的先後順序一致。當用戶端處於事務狀態時, 所有傳入的命令都會返回一個內容為 QUEUED 的狀態回複(status reply), 這些被入隊的命令將在 EXEC命令被調用時執行。從 Redis 2.6.5 開始,伺服器會對命令入隊失敗的情況進行記錄,並在用戶端調用 EXEC 命令時,拒絕執行並自動放棄這個事務。
四,提供外部的CAS行為,實現樂觀鎖機制
在遊戲開發中,有時候需要我們自己在外部實現樂觀鎖機制,WATCH 命令可以為 Redis 事務提供 check-and-set (CAS)行為,被 WATCH 的鍵會被監視,並會發覺這些鍵是否被改動過了。 如果有至少一個被監視的鍵在 EXEC 執行之前被修改了, 那麼整個事務都會被取消, EXEC 返回空多條批量回複(null multi-bulk reply)來表示事務已經失敗。
舉個例子, 假設我們需要原子性地為某個值進行增 1 操作(假設 INCR 不存在)。
首先我們可能會這樣做:
val = GET mykey
val = val + 1
SET mykey $val
上面的這個實現在只有一個用戶端的時候可以執行得很好。 但是, 當多個用戶端同時對同一個鍵進行這樣的操作時, 就會產生競爭條件。
舉個例子, 如果用戶端 A 和 B 都讀取了鍵原來的值, 比如 10 , 那麼兩個用戶端都會將鍵的值設為 11 , 但正確的結果應該是 12 才對。
有了 WATCH , 我們就可以輕鬆地解決這類問題了:
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
使用上面的代碼, 如果在 WATCH 執行之後, EXEC 執行之前, 有其他用戶端修改了 mykey 的值, 那麼當前用戶端的事務就會失敗。 程式需要做的, 就是不斷重試這個操作, 直到沒有發生碰撞為止。這種形式的鎖被稱作樂觀鎖, 它是一種非常強大的鎖機制。 並且因為大多數情況下, 不同的用戶端會訪問不同的鍵, 碰撞的情況一般都很少, 所以通常並不需要進行重試。
四,緩衝生命週期的控制
在遊戲伺服器中,為了節省效能,我們沒有必要把所有玩家的資訊都緩衝到記憶體中。比如有一些不常登陸的玩家,那麼他的資訊就沒必要一直呆在緩衝中了,需要清除。Redis為這個功能提供了一個方法:expire,它可以為key設定以秒為單位的生命週期,比如設定為300s,那麼五分鐘之後,這條記錄就會在記憶體中刪除。這樣不僅可以節省記憶體,而且增加了伺服器的效能。
IT圖書網:http://www.myitbook.cn
Redis在遊戲開發中的應用