Redis在遊戲業務中的使用

遊資網發表於2019-09-16
Redis在遊戲業務中的使用

本文介紹一下我在多個遊戲專案中,對Redis使用的經驗。Redis是個好東西,可以用在不同的業務場景。

0 Redis是什麼

Redis最大的特點是所有的資料都存放在記憶體裡,它常用於資料庫、cache和訊息佇列等場景。

1 Redis特性

讀寫效能高

首先,redis資料都在記憶體裡,其次,整體來看可以把redis看成一個大hashmap,所以基於key索引資料都可以在O(1)時間複雜度內完成,所以一個Redis節點(Redis程式)的QPS可以達到10w,特別適合作為傳統資料庫(mongo/mysql)的補充提升讀寫效率。

單執行緒

所有的讀寫請求都是單執行緒按照順序執行的,每個請求都是原子的,不會被打斷。

分散式

Redis支援分散式叢集,比如阿里雲會在叢集的多個節點前面放一個負載均衡,就可以像訪問單節點一樣訪問redis叢集。

如下圖所示,是我們在阿里雲的一個16節點redis叢集。

Redis在遊戲業務中的使用
阿里雲的redis叢集

由於阿里雲提供的redis叢集節點數量總是有上限的(其實可以做到無上限),因此為了獲得更好和更靈活的水平擴充套件能力,我們遊戲在業務層又增加了一層分散式功能。在操作redis的同時,可以傳入一個引數作為index通過hash對映到某一個redis叢集上。

持久化

因為支援持久化,若同時開啟RDB和AOF持久化選項,可以作為持久化資料庫儲存資料,重啟redis資料不會丟失。

阿里雲同時開啟了RDB和AOF(存疑)。

少量資料的持久化儲存可以使用redis,但是大量資料不可以。因為redis會把資料全部放到記憶體裡,所以對於大量數量,記憶體成本太高。

比如好友關係這類關係資訊屬於多個玩家之間的資訊,儲存在玩家身上也不合適。而且這類資訊使用比較頻繁並且資料量不大,就可以持久化儲存在redis中。

其他高階特性

Redis還有一些高階特性:

  • 事務:watch關鍵詞。
  • 多命令:可以將多個命令同時發給某個redis節點,降低網路通訊時間。
  • lua指令碼:通過lua指令碼,可以將一些redis操作包裝成原子操作。
  • 釋出/訂閱:可以用於訊息分發。
  • stream:redis抄kafka做的訊息佇列,4.0的功能...


2 Redis資料結構

redis整體來看儲存的是一個很大的hashmap,其中key是一個字串,value可以有多種資料結構。

value支援String、Hash、List、Set、SortedSet五種資料儲存結構,可以實現不同的業務需求:·

  • String:儲存簡單的int\string
  • List:列表/佇列,也可以作為訊息佇列使用。
  • Hash:一個key/value的鍵值對,這裡的value只支援簡單的型別(int\string)
  • Set:元素不會重複的集合。
  • SortedSet:有序集合,元素不會重複,同時元素會有一個score值,這個集合會根據score進行實時排序。常用的需求是排行榜。


大家使用redis時,使用方式的就是通過介面操作這些資料結構,所以建議大家看一下redis的基本操作http://doc.redisfans.com/

3 Redis在遊戲業務中的常見應用

一般來說,遊戲會使用mongo或者mysql儲存各類遊戲資訊,但這種資料庫將資料存在硬碟裡,讀些速度比較慢。而redis將資料放在記憶體裡,讀寫速度可以提高一個數量級。因此,可以配合mongo/mysql使用。

Cache

redis最常見的功能就是作為cache使用,遊戲中很多邏輯如果去讀mongodb,會有效能問題,所以使用redis作為快取,降低對mongodb的讀寫。

此外,還有些需求可以使用Reids作為快取,比如玩家個人簡要資訊。當我們檢視其他玩家資訊時,往往不需要全量資訊,只需要部分資訊(線上狀態、等級、部分資訊等),而這些資訊要是去讀取mongo會產生非常大的壓力。因此,可以將指定的玩家資訊放在Redis裡,提高訪問效率。

業務狀態/資料儲存

因為redis本身就支援持久化儲存,所以redis在一定程度上可以替代mongo。

那麼,什麼資料適合存在mongo,什麼資料適合存在redis呢?

對於一個資料應該放在redis還是mongo中儲存,主要考慮兩個方面:1.資料量。2.資料訪問頻率。

若資料量較小,比如記錄哪些區域聊天頻道需要關閉,這種資訊一般使用redis記錄即可。

若資料量較大但不是特別大,但資料訪問頻率比較高,比如玩家好友關係資訊,那麼一般也可以使用redis儲存。

若資料量特別大,比如玩家/家族全量資訊,一般使用mongo進行儲存。

無狀態服務開發支援

一般遊戲伺服器都是有狀態的,也就是說記憶體裡是有資料的。而無狀態的服務的意思是記憶體裡不儲存資料(有可能在執行邏輯過程中有些臨時變數)。

關於有狀態無狀態的描述,可以參考https://blog.csdn.net/yinxiangbing/article/details/53353940

記憶體裡不儲存狀態,那麼狀態存在哪裡呢?redis就可以儲存狀態的地方。

舉個例子,我們要開發一個組隊服務,那麼就需要一個組隊匹配池,我們可以將匹配池儲存在服務記憶體中(有狀態),當然也可以將這個匹配池資訊儲存在redis中(無狀態)。

無狀態(資訊儲存在redis)的好處如下:

若我們將匹配池儲存在記憶體中,那麼這個業務效能的瓶頸最大是一個程式,哪怕使用多執行緒,那麼天花板就是一臺伺服器。而若使用redis儲存資料,我們就可以寫多個程式來執行業務邏輯,效能瓶頸就不再存在,理論上就不會出現效能瓶頸。

若資料放在記憶體裡,機器和程式是有概率crash,資料也就丟失了。而redis可以使用主從切換,資料不會丟失,容錯能力強。

壞處:成本高、程式設計麻煩。

我在網易曾經寫過一套微服務框架,其實就是用的無狀態的原理寫遊戲邏輯。為服務框架非常適合寫非戰鬥邏輯,對大DAU遊戲可以提供非常強力的支援。後面有機會可以寫文章介紹下思想。

多程式/執行緒/執行緒之間的共享資料儲存

有些不同的程式需要共享資料,可以使用redis儲存這些共享資料。

比如有個遊戲需求是全服玩家給最喜愛的角色投票,所有玩家在不同的程式都需要操作這些資訊,就可以將這些資訊儲存在redis中進行共享。

臨時資料管理

redis的資料支援生命週期,所以若一些資料是臨時的(比如活動資料,活動結束後就不需要了),就可以將資料存在redis中,並且給一個生存時間(通過EXPIRE命令),當時間超過生存時間就會自動刪除。

排行榜

Redis的SortedSet非常適合做排行榜功能,在幾年前Redis不火時,如何寫一個好用的遊戲排行榜還是一個課題,近兩年就再也沒有人提這個事情了,沒啥好說的,用redis就好了。

分散式鎖

不同程式中的邏輯若希望使用鎖寫同步邏輯,可以基於redis的SETNX命令實現。

參考:https://juejin.im/post/5b737b9b518825613d3894f4

訊息佇列

遊戲中不同的程式之間的通訊,也可以使用redis作為訊息佇列來通訊。

比如給遊戲伺服器發一個控制命令,就可以將命令存在redis的list中,遊戲伺服器實時檢查redis是否有命令,有則pop出來執行。

Redis能否作為訊息佇列使用是一個常常被爭論的話題,因為有更專業的訊息佇列,比如Kfaka。我曾經服務的一個百萬級DAU專案曾經使用Redis作為簡單功能的訊息佇列(只有訊息傳輸功能),由此可見Redis作為訊息佇列在生產環境使用問題並不大。

4 Redis使用注意事項

避免出現大key,通過小key進行分散

大key表示一個key對應的value非常大。比如,我們希望記錄每個玩家的當前線上狀態,若我們建立了一個key為online_status的hashmap,這個hashmap的格式為uid:status。那麼,所有的玩家的線上資訊都儲存在這個key中。這種情況,就會導致online_status這個key對應的value極大。

redis雖然沒有限制一個key對應的value的大小(應該沒有),但是不建議使用大key。正常的使用方式是將每個玩家的資訊作為一個字串直接儲存在redis中,比如key為online_status_{uid},value為是否線上。

原因是redis基於key來做的分散式,若建立了一個大key,就會導致分散式功能失效,所有的請求都會到達同一個redis節點,導致這個節點卡頓,其他節點空閒。

Bigkey沒辦法檢測

避免hotkey

hotkey的含義是某個key讀寫頻率很高,特別繁忙。

需要避免這種情況的原因和大key類似,會導致分散式失效,某些節點卡頓。

hotkey在Redis4.0版本有檢測方式

給key增加字首

大家需要建立一個新的key前,需要保證key在所有業務中是獨一無二的,因此key最好有namespace的概念。

我們可以通過給key前面加一個字首,來避免key重複的情況。比如我們需要記錄玩家線上資訊,key設計為player:online_status,而當我們又需要記錄家族的活躍狀態,key設計為family:online_status。

(舉個栗子,家族活躍狀態應該叫active_status更合適...)

5 redis冷熱資料分離

上文說過,Redis不適合做大資料的持久化儲存,因為Redis會將所有資料都放在記憶體裡,相比硬碟儲存,成本極高(作為一個成功遊戲專案都覺得貴)。

其實有一種折中方案,就是將資料分為冷資料(表示長時間不活躍的資料,比如流失玩家資料)和熱資料(活躍資料,比如經常登入玩家資訊),將冷資料放在硬碟裡,將熱資料放在記憶體裡。然後當長期不活躍玩家迴流後,就將這個玩家的資料從冷資料轉為熱資料,從硬碟中轉到記憶體中,玩家長期不線上則把他的資料從熱資料轉為冷資料,從記憶體中轉到硬碟中。這樣的話,記憶體成本就會降低很多。

之前專案同事曾經嘗試實現過這個功能。就是寫一個proxy,後面掛一個redis和mongo,proxy實現一套redis協議解析,並且實現冷熱資料切換和管理。冷資料存在mongo,當訪問冷資料時將資料載入到redis中轉為熱資料,熱資料超過一個ttl後,又變為冷資料存到mongo中。

這個有一些問題,比如redis資料和mongo資料格式如何對應等,如何保證資料一致性和高可用性等。此外,對redis的使用API各種限制也比較多。

騰訊雲有一個類似的產品TcaplusDB,但是不支援Redis命令,據說騰訊遊戲內部用的都是這個東西(騰訊的同學可以說說)。騰訊還有一個類似的開源專案DCache,估計就是TcaplusDB的開源產品。

阿里雲已經有Redis冷熱資料分離的beta版本,等成熟了,甚至可以一定程度上取代mongodb。我認為這個專案用在遊戲行業非常合適,關注已久。

雲資料庫Redis_混合型儲存_冷熱資料分離_節約成本

promotion.aliyun.com

附:《忍者必須死3》團隊招人

我們忍3團隊目前還是一箇中小團隊,但是遊戲玩家數量大,遊戲質量高。專案裡需要做的事情非常多,公司在緊鑼密鼓的招人。

所以,招人,客戶端、服務端都招,高階或資深都有需求。

我們內部是全棧開發模式,但只要求你有一定的客戶端或服務端經驗。只要人靠譜,技術棧啥的都問題不大。

公司在杭州濱江,團隊氛圍很好,具有競爭力(不輸大廠)的薪資待遇。

另外,策劃、QA、美術、海外發行等崗位都在招,有需要我可以幫忙轉簡歷。

其他資訊,私信我或加我微信詳聊:shinepengwei

作者:水風
專欄地址:https://zhuanlan.zhihu.com/p/81639208

相關文章