開(kāi)源國(guó)產(chǎn)即時(shí)通訊GGTalk源碼剖析之:虛擬數(shù)據(jù)庫(kù)
上一篇:《開(kāi)源信創(chuàng)即時(shí)通訊GGTalk源碼剖析之:服務(wù)端全局緩存》
繼上篇《GGTalk 開(kāi)源即時(shí)通訊系統(tǒng)源碼剖析之:服務(wù)端全局緩存》詳細(xì)介紹了 GGTalk 對(duì)需要頻繁查詢數(shù)據(jù)庫(kù)的數(shù)據(jù)做了服務(wù)端全局緩存處理,以降低數(shù)據(jù)庫(kù)的讀取壓力以及加快客戶端請(qǐng)求的響應(yīng),接下來(lái)我們將進(jìn)入GGTalk服務(wù)端的虛擬數(shù)據(jù)庫(kù)。
GGTalk V8.0?除了支持真實(shí)的數(shù)據(jù)庫(kù)外,還內(nèi)置了虛擬的數(shù)據(jù)庫(kù),僅僅通過(guò)一行配置便可以啟動(dòng)虛擬的數(shù)據(jù)庫(kù),無(wú)需部署真實(shí)數(shù)據(jù)庫(kù)便能體驗(yàn)GGTalk的全部功能。若只是需要做簡(jiǎn)單的演示,這將極大地簡(jiǎn)化服務(wù)端的部署過(guò)程,使得服務(wù)端能立即運(yùn)行起來(lái)。
這篇文章將會(huì)詳細(xì)的介紹GGTalk虛擬數(shù)據(jù)庫(kù)的設(shè)計(jì)和實(shí)現(xiàn)。還沒(méi)有GGTalk源碼的朋友,可以到 點(diǎn)擊文章底部鏈接到文末下載。
一. 啟用虛擬數(shù)據(jù)庫(kù)
為了方便大家能夠快速、零成本地將?GGTalk?運(yùn)行起來(lái),并且體驗(yàn)GGTalk的全部功能,GGTalk服務(wù)端在內(nèi)存中內(nèi)置了一個(gè)虛擬的數(shù)據(jù)庫(kù)可以替代真實(shí)的數(shù)據(jù)庫(kù)以方便測(cè)試。接下來(lái)將會(huì)介紹如何啟用虛擬數(shù)據(jù)庫(kù)運(yùn)行GGTalk。
1. 修改服務(wù)端配置文件

首先找到 GGTalk.Server 目錄下的?App.config?文件。

在 App.config 配置文件中,找到關(guān)于UserVirtualDB
的配置,將其值修改為true
,如上圖所示。
2. 啟動(dòng)服務(wù)端程序
在修改完服務(wù)端配置文件后,啟動(dòng)服務(wù)端程序,如此,服務(wù)端使用的就是內(nèi)存中的虛擬數(shù)據(jù)庫(kù)。

若能看到這個(gè)窗口彈出,則代表服務(wù)端程序運(yùn)行成功。
注意:由于服務(wù)端使用的是在內(nèi)存中模擬出來(lái)的虛擬數(shù)據(jù)庫(kù),故服務(wù)端退出時(shí),內(nèi)存將被釋放,虛擬數(shù)據(jù)庫(kù)中的一切數(shù)據(jù)都會(huì)被清除。
二. 如何做到切換為虛擬數(shù)據(jù)庫(kù)
在計(jì)算機(jī)科學(xué)中有一句經(jīng)典名言:
計(jì)算機(jī)科學(xué)領(lǐng)域內(nèi)的任何問(wèn)題,都可以通過(guò)增加一個(gè)間接的中間層來(lái)解決。
在面向?qū)ο笤O(shè)計(jì)(OOP)中,這句名言所表達(dá)的含義通常是通過(guò)抽象出一個(gè)接口(interface)來(lái)完成的。
基于這份了解,為了能切換真實(shí)數(shù)據(jù)庫(kù)與虛擬數(shù)據(jù)庫(kù),我們將數(shù)據(jù)庫(kù)訪問(wèn)層抽象為一個(gè)接口?IDBPersister,在服務(wù)端所有訪問(wèn)數(shù)據(jù)庫(kù)的地方都通過(guò)調(diào)用 IDBPersister 接口來(lái)實(shí)現(xiàn)。
真實(shí)數(shù)據(jù)庫(kù)訪問(wèn) DBPersister 和虛擬數(shù)據(jù)庫(kù) MemoryPersister 都實(shí)現(xiàn) IDBPersister 接口,這樣一來(lái),在程序啟動(dòng)的時(shí)候,就可以自由決定是使用 DBPersister 還是 MemoryPersister 了。
三. 虛擬數(shù)據(jù)庫(kù)的實(shí)現(xiàn)
關(guān)于這部分的代碼位于
GGTalk/GGTalk.Server/MemoryPersister.cs
。
虛擬數(shù)據(jù)庫(kù)的設(shè)計(jì)原理很簡(jiǎn)單,接下來(lái)我們看看其具體是如何實(shí)現(xiàn)的。
1. MemoryPersister類
MemoryPersister 類實(shí)現(xiàn)了GGTalk服務(wù)端中的虛擬數(shù)據(jù)庫(kù),讓我們來(lái)看看它到底是如何實(shí)現(xiàn)的吧。
public class MemoryPersister : OfflineMemoryCache, IDBPersisterExtend {
?//...
?private ObjectManager<string, GGUser> userManager = new ObjectManager<string, GGUser>();
?private ObjectManager<string, GGGroup> groupManager = new ObjectManager<string, GGGroup>();
?//string : ?requesterID + "-" + accepterID
?private ObjectManager<string, AddFriendRequest> addFriendRequestManager = new ObjectManager<string, AddFriendRequest>();
?//string : ?requesterID + "-" + groupID
?private ObjectManager<string, AddGroupRequest> addGroupRequestManager = new ObjectManager<string, AddGroupRequest>();
?//string : ?groupID + "-" + userID
?private ObjectManager<string, GroupBan> groupBanManager = new ObjectManager<string, GroupBan>();
?//...
}
以上是就MemoryPersister類的部分定義,也是實(shí)現(xiàn)虛擬數(shù)據(jù)庫(kù)的核心內(nèi)容。可以觀察到,這個(gè)類繼承了OfflineMemoryCache類,同時(shí)還實(shí)現(xiàn)了IDBPersisterExtend接口。OfflineMemoryCache類的作用是用于在內(nèi)存中存儲(chǔ)離線消息和離線文件條目,這個(gè)不是本篇文章關(guān)注的重點(diǎn),我們重點(diǎn)來(lái)看一下IDBPersisterExtend接口,以下是關(guān)于這個(gè)接口的定義:
public interface IDBPersisterExtend: IDBPersister<GGUser, GGGroup> {
?/// <summary>
?/// 根據(jù)用戶ID獲取其手機(jī)號(hào)
?/// </summary>
?/// <param name="userID"></param>
?/// <returns></returns>
?string GetPhone4UserID(string userID);
?/// <summary>
?/// 更新用戶手機(jī)號(hào)
?/// </summary>
?/// <param name="userID"></param>
?/// <param name="phone"></param>
?void UpdateUserPhone(string userID, string phone, int version);
}
我們可以發(fā)現(xiàn),這個(gè)接口實(shí)現(xiàn)了IDBPersister<GGUser, GGGroup>
接口,再分析一下這個(gè)接口的命名,我們很容易就知道這個(gè)接口僅僅是對(duì)IDBPersister接口的一個(gè)拓展,因此我們繼續(xù)分析IDBPersister接口,這個(gè)接口定義了大量操作數(shù)據(jù)庫(kù)的方法。現(xiàn)在讓我們把思緒收回來(lái),也就是說(shuō)MemoryPersister類最終實(shí)現(xiàn)了IDBPersister<GGUser, GGGroup>接口。因此,在MemoryPersister類也將會(huì)存在大量操作數(shù)據(jù)庫(kù)的方法,如下圖所示:

結(jié)果很顯然,MemoryPersister 類中實(shí)現(xiàn)了很多操作數(shù)據(jù)庫(kù)的方法,等等,到這里只能說(shuō)明 MemoryPersister 類僅僅只是實(shí)現(xiàn)了IDBPersister<GGUser, GGGroup>接口
,僅僅只是約定了方法的名字、方法參數(shù)和返回值,內(nèi)部的實(shí)現(xiàn)一定是操作數(shù)據(jù)庫(kù)嗎?接下來(lái)讓我們隨便點(diǎn)開(kāi)一個(gè)方法看看具體實(shí)現(xiàn):

很容易看出,這是一個(gè)更新用戶信息的方法,方法接收一系列和用戶有關(guān)的字段,然后從userManager上調(diào)用 Get 方法,似乎返回了一個(gè)包含用戶信息的對(duì)象,類型是 GGUser。然后更新這個(gè)對(duì)象的信息。很好,現(xiàn)在讓我們把關(guān)注點(diǎn)放在這個(gè)userManager
上,明白了它代表什么,那么就能知道這個(gè)方法究竟是不是在操作數(shù)據(jù)庫(kù)了。來(lái)到它的定義的地方:

可以發(fā)現(xiàn),它是MemoryPersister類內(nèi)部的一個(gè)私有字段,類型為ObjectManager<string, GGUser>
,ObjectManager 是對(duì) Dictionary 的二次封裝,支持多線程安全,相比Dictionary,使用起來(lái)也更方便。
到這里,首先我們明白,上述的UpdateUserInfo
方法和數(shù)據(jù)庫(kù)一點(diǎn)關(guān)系都沒(méi)有。而這個(gè)類的作用是將數(shù)據(jù)存儲(chǔ)到內(nèi)存中,如果你有了解《GGTalk 開(kāi)源即時(shí)通訊系統(tǒng)源碼剖析之:服務(wù)端全局緩存》的話,你會(huì)發(fā)現(xiàn) ObjectManager 也正是實(shí)現(xiàn)服務(wù)端緩存的那個(gè)類。而GGTalk虛擬數(shù)據(jù)庫(kù)中的數(shù)據(jù)也是通過(guò)這個(gè)類的實(shí)例來(lái)存儲(chǔ)的。
而這里userManager
其實(shí)就是用來(lái)存儲(chǔ)用戶數(shù)據(jù)和操作用戶數(shù)據(jù)。一個(gè)東西,它既能存儲(chǔ)數(shù)據(jù),也能操作數(shù)據(jù),那這個(gè)東西的作用是不是和數(shù)據(jù)庫(kù)很類似呢?沒(méi)錯(cuò),這正是GGTalk虛擬數(shù)據(jù)庫(kù)的設(shè)計(jì),將數(shù)據(jù)存儲(chǔ)在內(nèi)存中,并且定義了一系列能夠操作數(shù)據(jù)的方法(有沒(méi)有感覺(jué)像sql語(yǔ)句)。
現(xiàn)在讓我們?cè)賮?lái)回顧 MemoryPersister 類的定義:
public class MemoryPersister : OfflineMemoryCache, IDBPersisterExtend {
?//...
?private ObjectManager<string, GGUser> userManager = new ObjectManager<string, GGUser>();
?private ObjectManager<string, GGGroup> groupManager = new ObjectManager<string, GGGroup>();
?//string : ?requesterID + "-" + accepterID
?private ObjectManager<string, AddFriendRequest> addFriendRequestManager = new ObjectManager<string, AddFriendRequest>();
?//string : ?requesterID + "-" + groupID
?private ObjectManager<string, AddGroupRequest> addGroupRequestManager = new ObjectManager<string, AddGroupRequest>();
?//string : ?groupID + "-" + userID
?private ObjectManager<string, GroupBan> groupBanManager = new ObjectManager<string, GroupBan>();
?//...
}
在了解GGTalk虛擬數(shù)據(jù)庫(kù)的設(shè)計(jì)后,現(xiàn)在再來(lái)看就很清晰了,各個(gè)字段的作用如下所示:
userManager
:用于管理用戶數(shù)據(jù)的實(shí)例對(duì)象,內(nèi)含數(shù)據(jù)和操作數(shù)據(jù)的方法;groupManager
:用于管理群組數(shù)據(jù)的實(shí)例對(duì)象,內(nèi)含數(shù)據(jù)和操作數(shù)據(jù)的方法;addFriendRequestManager
?:用于管理添加好友請(qǐng)求數(shù)據(jù)的實(shí)例對(duì)象,內(nèi)含數(shù)據(jù)和操作數(shù)據(jù)的方法;addGroupRequestManager
?:用于管理添加群組請(qǐng)求數(shù)據(jù)的實(shí)例對(duì)象,內(nèi)含數(shù)據(jù)和操作數(shù)據(jù)的方法;groupBanManager
?:用于管理群組禁言數(shù)據(jù)的實(shí)例對(duì)象,內(nèi)含數(shù)據(jù)和操作數(shù)據(jù)的方法。
這些字段便是GGTalk虛擬數(shù)據(jù)庫(kù)的核心實(shí)現(xiàn)了。
2. 初始化虛擬數(shù)據(jù)庫(kù)
在了解完GGTalk虛擬數(shù)據(jù)庫(kù)的核心實(shí)現(xiàn)后,現(xiàn)在我們來(lái)看一看GGTalk虛擬數(shù)據(jù)庫(kù)是在哪里進(jìn)行初始化的:

在MemoryPersister類的構(gòu)造函數(shù)中,我們可以發(fā)現(xiàn),userManager字段中被插入了11個(gè)用戶的數(shù)據(jù),groupManager字段中被插入了兩個(gè)群組的數(shù)據(jù)。接下來(lái)我們?cè)賮?lái)到MemoryPersister實(shí)例化的地方:

我們分析這段程序,首先定義了一個(gè)persister 變量
,然后去讀取項(xiàng)目的App.config配置文件中的UseVirtualDB 配置
,若是為true,則將persister變量賦值為MemoryPersister類的實(shí)例。
其實(shí)這段程序的意思就是通過(guò)讀取配置文件來(lái)決定是否啟用虛擬數(shù)據(jù)庫(kù),而這個(gè) UseVirtualDB配置 則是是否啟用虛擬數(shù)據(jù)庫(kù)的關(guān)鍵配置,也是文章開(kāi)頭為了啟用虛擬數(shù)據(jù)庫(kù)而修改的那個(gè)配置。到了這里,是不是豁然開(kāi)朗了呢。
四. 結(jié)語(yǔ)
本篇建議結(jié)合?GGTalk的源代碼?進(jìn)行閱讀,為了方便大家理解GGTalk虛擬數(shù)據(jù)庫(kù)的設(shè)計(jì)思路,本文刻意沒(méi)有將比較復(fù)雜的部分進(jìn)行展開(kāi)講解,同時(shí)也是為了控制文章篇幅,例如 MemoryPersister 類中的 messageRecordMemoryCache 字段,此字段的作用是在內(nèi)存中存儲(chǔ)聊天記錄,對(duì)其有興趣的可以看看它的設(shè)計(jì)和實(shí)現(xiàn)。最后,希望本篇文章對(duì)你有所幫助。
在接下來(lái)的一篇我們將介紹GGTalk客戶端的全局緩存以及客戶端本地存儲(chǔ)。
敬請(qǐng)期待:《GGTalk 開(kāi)源即時(shí)通訊系統(tǒng)源碼剖析之:客戶端全局緩存及本地存儲(chǔ)》
點(diǎn)擊下面鏈接下載GGTalk 8.0版本源碼。
https://zhuanlan.zhihu.com/p/651560820