Redis 在遊戲開發中的應用

2022-08-17 16:54:12 字數 3802 閱讀 1960

redis是乙個新興的nosql資料快取元件,與memcache類似,但是功能卻比memcache多一些。

首先,redis和memcache都是基於記憶體的,所以讀取和寫入速度都非常快。但是memcache只支援簡單的key-value資料的儲存方式,而redis對key-value ,hash,list,set,sortset等資料結構有很好的支援。

下面就redis在遊戲的開發應用中做一些簡單的介紹。

一,資料的快取

在這一點上,redis和memcache是一樣的。都是把資料提前放入到記憶體中。當邏輯處理中需要用到資料時,先從記憶體中讀取,相同的,寫的時候也先向記憶體中寫入,然後再運算元據庫,以增加資料處理的速度。不同的是,redis帶有把資料寫入到硬碟的功能,具體的寫入策略可以在redis的配置檔案中配置。這樣當主機突然出現故障時,比如斷電,重啟機器不會造成資料的丟失。這個在遊戲的應用中特別重要。一般在遊戲開發中,資料的處理會採用:快取 + 持久化佇列 + 資料庫(mysql)的架構。執行的流程是先把資料寫入到快取,然後把需要持久化的資料放入到持久化佇列中,啟動乙個守護執行緒,從持久化佇列中不斷的取出資料,並存入或更新到資料庫。如果使用memcache這樣沒有寫入到硬碟功能的快取元件,出現故障時,持久人佇列中如果還有沒有處理完的資料,那麼就會造成資料的丟失,引用玩家的資料出現短暫的回檔。當然這些也可以自己開發一些功能去防止,但是增加了開發成本。

二,不丟失資料的持久化佇列的實現

上面說過redis具有把資料寫入到硬碟的功能,而且支援多種資料結構。那麼就可以利用redis的list實現持久化佇列,而且當機器出現故障時,不會出現佇列中資料丟失的情況,重啟之後,資料會自動載入到redis的list之中。

具體實現方法:

(1)在redis中構造乙個list儲存

(2)乙個執行緒使用redis的lpush方法,向list的左邊加入資料

(3)另外乙個執行緒使用redis的rpop方法,從list取出資料進行處理,並且從list中刪除了取出的資料。這樣就實現了乙個簡單的生產者--消費者模式的佇列。

三,對併發操作的控制(跨房間)

一般來說,我們操作乙個資料的流程是這樣的,取出--處理---儲存,這樣在單執行緒中操作是沒有任務問題的,但是在多執行緒環境中就不適用了,我們必須考慮資料同步的問題,保證資料操作的原子性。如果在遊戲中,對玩家戰隊的屬性進行更新,一般在資料庫中都會儲存乙個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,那麼五分鐘之後,這條記錄就會在記憶體中刪除。這樣不僅可以節省記憶體,而且增加了伺服器的效能

六、排行榜

遊戲伺服器中涉及到很多排行資訊,比如玩家等級排名、金錢排名、戰鬥力排名等。

一般情況下僅需要取排名的前n名就可以了,這時可以利用資料庫的排序功能,或者自己維護乙個元素數量有限的top集合。

但是有時候我們需要每乙個玩家的排名,玩家的數量太多,不能利用資料庫(全表排序壓力太大),自己維護也會比較麻煩。

使用redis可以很好的解決這個問題。它提供的有序set,支援每個鍵值(比如玩家id)擁有乙個分數(score),每次往這個set裡新增元素,

redis會對其進行排序,修改某一元素的score後,也會更新排序,在獲取資料時,可以指定排序範圍。

更重要的是,這個排序結果會被儲存起來,不用在伺服器啟動時重新計算。

通過它,排行榜的實時重新整理、全服排行都不再成為麻煩事。

七、郵件

參考:redis在遊戲開發中的應用

向量在遊戲開發中的應用(二)

中講了利用向量方向的性質來解決問題。這篇部落格將繼續用乙個簡單的小例子來講解如何將向量的點乘性質應用到實際的遊戲開發中。向量點乘的幾何定義 設二維空間內有兩個向量u和v,它們的夾角為 0,則內積定義為以下實數 向量點乘的公式 u v u v cos 根據該公式可以退到下面五條結論 1.u v 0,表...

Rectangle物件在遊戲開發中的應用2

上一次發了rectangle物件的應用 覺得有必要補充一下,希望朋友們活學活用 應用三 對兩個並不存在的物件進行碰撞檢測 有的時候你想進行碰撞的兩個物體並不存在,那麼我們就無法通過正常的hittest手段檢測兩個物體是否發生了碰撞。這個時候通過rectangle物件無疑是最方便的。下面舉出兩個例子來...

Redis在遊戲伺服器中的應用

排行榜 遊戲伺服器中涉及到很多排行資訊,比如玩家等級排名 金錢排名 戰鬥力排名等。一般情況下僅需要取排名的前n名就可以了,這時可以利用資料庫的排序功能,或者自己維護乙個元素數量有限的top集合。但是有時候我們需要每乙個玩家的排名,玩家的數量太多,不能利用資料庫 全表排序壓力太大 自己維護也會比較麻煩...