開源信創(chuàng)即時通訊GGTalk源碼剖析之:服務端全局緩存
上一篇:《開源信創(chuàng)國產(chǎn)即時通訊系統(tǒng)GGTalk源碼剖析之:數(shù)據(jù)庫設計》
GGTalk?對需要頻繁查詢數(shù)據(jù)庫的數(shù)據(jù)做了服務端全局緩存處理,這樣做一來大大降低了數(shù)據(jù)庫的讀取壓力,二來在客戶端的請求到來時,服務端能更快地響應,極大地提升了用戶體驗。這篇文章將會詳細剖析關(guān)于?GGTalk?服務端全局緩存的設計與實現(xiàn)。還沒有GGTalk源碼的朋友,可以點擊文章底部鏈接下載。
一. GGTalk 服務端三大核心
首先,我們需要了解?GGTalk服務端?的三大核心,其分別是:
消息處理:處理來自客戶端的消息;
全局緩存:將用戶和群組的數(shù)據(jù)緩存在內(nèi)存中;
數(shù)據(jù)庫交互:對數(shù)據(jù)庫中的信息進行增刪改查。
1. 消息處理
此部分的代碼位于?
GGTalk/TalkBase/Server/Core/ServerHandle.cs
當一個客戶端的請求進來時,首先會進入消息處理環(huán)節(jié),根據(jù)用戶傳遞的消息號,進入不同的邏輯分支。以修改用戶信息為例:

當一個用戶信息被修改時,會調(diào)用如上方法,然后通過調(diào)用?rapid客戶端引擎?上的?SendMessage?方法發(fā)送一條消息(其中?data
?為用戶信息的?byte[]數(shù)組
)。

客戶端發(fā)送消息會觸發(fā)?rapid服務端引擎?上的?MessageReceived?事件,最終程序流程會來到如下圖的地方。

根據(jù)客戶端傳遞在消息號來匹配對應的?if分支,然后進行對應的處理。
2. 全局緩存
此部分的代碼位于?
GGTalk/TalkBase/Server/Core/ServerGlobalCache.cs
接著前面修改用戶信息的例子:

消息處理后會來到如上?if分支,其中分別調(diào)用了?serverGlobalCache?上的?UpdateUserInfo
?和?GetUser
?方法,下面是這兩個方法的具體實現(xiàn)。

此方法會從全局緩存獲取用戶數(shù)據(jù),若緩存中不存在,則會從數(shù)據(jù)庫中查詢,并將查詢到的用戶數(shù)據(jù)存入緩存中,方法最終返回用戶數(shù)據(jù)。

此方法先去獲取用戶的信息,修改用戶信息,然后通過調(diào)用?user?上的?DeletePartialCopy
?方法清除用戶的緩存,最后再更新數(shù)據(jù)庫中用戶的信息。
3. 數(shù)據(jù)庫交互
此部分的代碼位于?
GGTalk/GGTalk.Server/DBPersister.cs
同樣在這個修改用戶信息的例子中,在前面的講解中有涉及到兩處與數(shù)據(jù)庫的交互,分別是?GetUser
?和?UpdateUserInfo
?方法的調(diào)用。下面是這兩個方法的具體實現(xiàn):

在數(shù)據(jù)庫的交互環(huán)節(jié),我們使用的是?sqlsugar?來操作數(shù)據(jù)庫(這是一個開源的ORM框架,若想了解其詳細用法,請移步sqlsugar文檔)。
二. 服務端全局緩存
看到這里,相信你對?GGTalk服務端?的三大核心有了一定的了解,接下來將會詳細介紹關(guān)于?GGTalk?服務端全局緩存的設計。
1. 代碼位置

由于在?GGTalk服務端?中對用戶和群組信息查詢過于頻繁,故而?GGTalk?將用戶和群組的信息緩存在服務端內(nèi)存之中,進而達到減少資源消耗和更快的服務端響應的好處,但這樣做同時也會增加編碼的復雜度,那么?GGTalk?是如何在其中進行取舍的呢?下面將介紹具體實現(xiàn)。
2. ServerGlobalCache類

ServerGlobalCache類?就是?GGTalk?服務端全局緩存的核心實現(xiàn)了,這個類接受兩個泛型參數(shù),TUser
和TGroup
,并要求TUser
必須是TalkBase命名空間中的IUser
接口的實現(xiàn)類或子類。TGroup
必須是TalkBase命名空間中的IGroup
接口的實現(xiàn)類或子類。
IUser
:用戶基礎(chǔ)接口,定義了關(guān)于用戶一系列的屬性和方法。

除了這兩個泛型參數(shù)外,我們可以發(fā)現(xiàn)?ServerGlobalCache類?中還有三個字段,這三個字段是?ServerGlobalCache類?中所有方法的核心,其作用如下:
dbPersister:與數(shù)據(jù)庫進行交互;
userCache:與用戶緩存相關(guān);
groupCache:與群組緩存相關(guān)。
關(guān)于這三個字段,在后面的具體場景會展開更加詳細的介紹。
3. 緩存數(shù)據(jù)的實現(xiàn)
關(guān)于服務端緩存,最關(guān)鍵的就是?userCache?和?groupCache?字段了,其中?userCache?用于緩存用戶的信息;而?groupCache?用于緩存群組的信息。
首先我們來看關(guān)于這兩個字段的類型ObjectManager
:public class ObjectManager<TPKey, TObject>
ObjectManager是對Dictionary的二次封裝,支持多線程安全,使用起來也更方便。這個類接受兩個泛型參數(shù),我們通過傳入不同的泛型可以實現(xiàn)不同數(shù)據(jù)的管理(在?GGTalk服務端?中,僅管理了用戶和群組的數(shù)據(jù))。
其內(nèi)部的Dictionary就是用來將用戶或群組的數(shù)據(jù)存儲在內(nèi)存中,達到緩存數(shù)據(jù)的目的。
4. 將數(shù)據(jù)庫中數(shù)據(jù)的讀入內(nèi)存(緩存數(shù)據(jù))
我們來看?ServerGlobalCache類?中如下兩個方法:

從名字上來看我們很容易就知道這兩個方法的意思,預加載用戶數(shù)據(jù)和預加載群組數(shù)據(jù),這兩個方法的主要作用就是將數(shù)據(jù)庫中用戶和群組的數(shù)據(jù)加載到內(nèi)存中。首先通過?dbPersister?字段來從數(shù)據(jù)庫中查詢到所有用戶和群組數(shù)據(jù),通過foreach遍歷,分別調(diào)用userCache
和groupCache
上的Add
方法將每一條數(shù)據(jù)存儲到前面提到的objectDictionary
字段中,也是就存儲在了服務端程序運行時的內(nèi)存里面。
5. 數(shù)據(jù)庫增刪改查
看到這里,關(guān)于?ServerGlobalCache類?的基礎(chǔ)設施你已經(jīng)了解的七七八八了,接下來都是基于這些基礎(chǔ)設施而實現(xiàn)的方法了。在這里我要糾正一個你可能感到疑惑的點,本篇文章不是介紹服務端緩存嗎,這里怎么扯到數(shù)據(jù)庫的增刪改查呢?
因為往往數(shù)據(jù)緩存和數(shù)據(jù)源之間存在著一些聯(lián)動,所以?ServerGlobalCache類?的作用不僅僅是緩存數(shù)據(jù),同時也存在大量獲取數(shù)據(jù)庫中的數(shù)據(jù)的方法,這也是為什么在類里面會有一個dbPersister
字段,當然關(guān)于具體從數(shù)據(jù)庫中讀取數(shù)據(jù)的方法不在這個類里邊(回顧?GGTalk三大核心)。
接下來,讓我們看看?ServerGlobalCache類?還有什么:

我們可以看到,這些折疊的部分的代碼行數(shù)幾乎占據(jù)了?ServerGlobalCache類?的百分之九十,這是正是對數(shù)據(jù)庫和數(shù)據(jù)緩存的操作,每個折疊代碼塊的注釋都對應著?GGTalk數(shù)據(jù)庫?的一張表。
接下來我們主要分析一下關(guān)于用戶和群組的部分操作,看看?GGTalk服務端?是如何對數(shù)據(jù)庫和數(shù)據(jù)緩存進行操作的。
首先來看一個簡單的,添加新用戶操作:

這個方法接受一個TUser
類型的參數(shù),參數(shù)中包含用戶的必要信息,然后分別添加到用戶緩存和插入到數(shù)據(jù)庫中。
接下來,再看最開始講三大核心的那個例子:

現(xiàn)在再來看是不是很清晰了呢,這個方法用于查詢單個用戶,接受一個用戶ID,首先會從用戶緩存中查找這個用戶,如果緩存中不存在,則從數(shù)據(jù)庫中查找,在用戶存在的情況下再將其存入內(nèi)存之中。
接下來再分析兩個關(guān)于群組操作的方法。
1、根據(jù)群組ID獲取群組信息:

和獲取用戶信息類似,此方法首先會在群組緩存中查找對應ID的群組,若群組不存在,則會從數(shù)據(jù)庫讀取對應ID的群組,并且在群組存在的情況下將其存入內(nèi)存之中。
2、解散群組操作

這個方法接受群組ID作為參數(shù),首先會調(diào)用GetCroup
方法依次從內(nèi)存和數(shù)據(jù)庫中讀取關(guān)于目標群組的數(shù)據(jù)(如果緩存中沒有的話)。若群組存在,則從群組的MemberList
屬性中遍歷用戶ID,再通過GetUser
方法查詢用戶數(shù)據(jù),通過用戶的QuitGroup
退出群組,然后在數(shù)據(jù)庫中更新用戶的信息。在這個群組中的每一個存在的用戶都退出群組后,從群組緩存中清除該群組的數(shù)據(jù)。然后再同步數(shù)據(jù)庫中的群組表的數(shù)據(jù),以及在數(shù)據(jù)庫中申請加入群組表中刪除加入此群組的記錄。
三. 結(jié)語
將數(shù)據(jù)庫中的數(shù)據(jù)緩存在內(nèi)存中是一把雙刃劍,若是將大量的數(shù)據(jù)保存在內(nèi)存中,這會大大加大內(nèi)存的占用,存在程序因為內(nèi)存不足而導致程序崩潰的風險。如何避免這樣的事情發(fā)生,這要求我們對內(nèi)存保持足夠的敏感。最后,希望這篇文章能夠?qū)δ阌兴鶐椭?br>在接下來的一篇我們將介紹GGTalk服務端的虛擬數(shù)據(jù)庫。
敬請期待:《GGTalk 開源即時通訊系統(tǒng)源碼剖析之:虛擬數(shù)據(jù)庫》
點擊下面鏈接下載GGTalk 8.0版本源碼。
https://zhuanlan.zhihu.com/p/651560820