firefly游戏服务器学习笔记 6———— db模块
前面介紹過master模塊,現(xiàn)在我們看看dbfront模塊,源碼在firefly/dbentrust和app/defront 目錄。
顧名思義 entrust 就是數(shù)據(jù)庫(kù)托管的意思。這個(gè)模塊實(shí)現(xiàn)的功能就是負(fù)責(zé)從數(shù)據(jù)庫(kù)讀取數(shù)據(jù),并且緩存到memcache。然后定期的檢查緩存并寫入更新到DB。
?
剛剛看到9秒論壇里面有篇文章介紹這個(gè)dbentrust庫(kù)的左右。寫的很詳細(xì)。地址如下:
?? ????ht空格tp://www.9miao.com/thread-44002-1-1.html
既然文章已經(jīng)寫了很詳細(xì)的說(shuō)明,我就偷懶了:)
?
下面我主要介紹一下db整體模塊的結(jié)構(gòu),流程,邏輯。
前面的章節(jié)應(yīng)該提到過,除了master模塊以外,其它模塊(db,gate,net,game1,admin)都是通過master的子進(jìn)程方式啟動(dòng)。啟動(dòng)代碼如下:
?
??? defstartChildren(self):
??????? """
??????? """
??????? print "startchildren ......"
??????? config =json.load(open(self.configpath, 'r'))
??????? sersconf =config.get('servers')
??????? for sername insersconf.keys():
??????????? cmds = 'python%s %s %s' % (self.mainpath, sername, self.configpath)
??????????? subprocess.Popen(cmds,shell=True)
??????? reactor.run()
?
通過簡(jiǎn)單加打印便可以發(fā)現(xiàn),這里其實(shí)就是“python appmain.py db config.json”
?
OK,那么我們可以拋開master,單獨(dú)命令行啟動(dòng)這個(gè)db模塊。
為了更加清晰的學(xué)習(xí)代碼,我已經(jīng)把每個(gè)模塊單獨(dú)分離開,具體分離后的代碼請(qǐng)看github。 地址為:htt空格ps://github.com/chenee/firefly_study
?
我們下面自己那這份代碼解說(shuō),大家可以對(duì)照源代碼進(jìn)行學(xué)習(xí)。
(說(shuō)明,這份代碼只是為了學(xué)習(xí)才拆分開,會(huì)存在很多冗余,甚至不一致的地方。僅供參考)
代碼目錄如下:
1 .???????????????????????????????????????????????????????????????????????????????????????????????????????????????????
? 2 ├── app? #原先的游戲邏輯目錄,這個(gè)和firefly庫(kù)目錄對(duì)應(yīng),存放游戲具體實(shí)現(xiàn)。但是這里被我打亂了。
? 3 │?? ├── __init__.py
? 4 │?? ├── dbfront?#數(shù)據(jù)庫(kù)操作相關(guān)文件目錄
? 5 │?? │?? ├── McharacterManager.py #角色管理操作文件,從數(shù)據(jù)庫(kù)讀取所有角色信息,緩存到memcache
? 6 │?? │?? ├── __init__.py
? 7 │?? │?? ├── initconfig.py? #db模塊中游戲部分的初始化文件,負(fù)責(zé)app目錄的內(nèi)容的加載。
? 8 │?? │?? ├── madminanager.py #MAdmin類的管理類。Madmin下面會(huì)提到。
? 9 │?? │?? ├── mcharacter.py #角色類,角色在memcache中的映射。
?10 │?? │?? └── memmode.py #幾個(gè)Madmin類的初始化工作
?11 │?? ├── dbfrontserver.py? #啟動(dòng)接口,唯一作用就是調(diào)用initconfig.py
?12 │?? ├── logs
?13 │?? │?? └── dbfront.log #log文件
?14 │?? └── share
?15 │?????? ├──__init__.py
?16 │?????? └──dbopear?? #數(shù)據(jù)庫(kù)操作文件,對(duì)于db模塊來(lái)說(shuō)就只使用了一個(gè)文件,typo!
?17 │??????????├── __init__.py
?18 │??????????└── dbCharacter.py #tb_character角色表的select,update封裝類。
?19 ├── appmain.py? #啟動(dòng)腳本,讀config.json配置文件然后初始化DB模塊類
?20 ├── config.json? #配置文件,非常重要的文件
?21 ├── dbpool.py #db連接池,原先的文件只提供初始化和取連接池的2個(gè)函數(shù)。感覺很多dbopear目錄的的sql操作完全可以封裝,具體見我game1模塊里面的改動(dòng),其它幾個(gè)模塊的文件可能不同步。最終會(huì)按照game1的模式整合。
?22 ├── dbserver.py #db模塊的類文件,這個(gè)對(duì)于原先FFServer。針對(duì)每個(gè)模塊我把他改成對(duì)應(yīng)名稱,便于理解
?23 ├── globalobject.py #全局類,這里的全局只每個(gè)模塊內(nèi)部的全局,而不是整個(gè)系統(tǒng)的全局。每個(gè)模塊自己的globalobject類完全可以不同。
?24 ├── leafnode.py #就是原先的node.py,在PB那個(gè)章節(jié)我們介紹過。
?25 ├── logobj.py #log類
?26 ├── memclient.py #memcache的客戶端實(shí)現(xiàn),提供對(duì)memcache的訪問操作接口
?27 ├── memobject.py #memcached關(guān)系對(duì)象通過key鍵的名稱前綴來(lái)建立
各個(gè)key-value 直接的關(guān)系; 比如memobject.name= “tbl_role”, 那么memobject.get(“id”)得到的就是tbl_role:id的值。
?28 ├── mmode.py #里面包括2個(gè)重要的類,MMode,MAdmin;都是memobject的子類,邏輯上MMode代表內(nèi)存中的一條數(shù)據(jù),MAdmin,代表內(nèi)存中的一張表。而前面madminanager.py就是這些表的管理類。
MAdmin對(duì)應(yīng)memcache的前綴是表名稱:如tb_item
MMode對(duì)應(yīng)memcache的前綴是pk(primary key,主鍵ID)。如 tb_item:1001
那么基本的一條數(shù)據(jù)組織的格式是:tbl_item:1001 {id:10001, name:chenee , money:10000};也就是memcache的key是 “ 表名稱:該條的主鍵值”,value是這條內(nèi)容的json格式。
?
驗(yàn)證方式,可以telnet到memcache打印出來(lái)看結(jié)果。(以前做的,現(xiàn)在記不清了,可能有誤,此刻我自己還木有驗(yàn)證)
?
?29 ├── reference.py #PB相關(guān),看前面一章介紹
30 ├── run.sh #shell啟動(dòng)腳本,為了方便,我自己寫的。
?31 ├── serviceControl.py #對(duì)應(yīng)原先的一個(gè)叫做admin.py的文件,其實(shí)就是給leafnode加2條命令(stop,reload)這個(gè)在PB章節(jié)也說(shuō)過了。
?32 ├── services.py #服務(wù)類,前面提過
?33 ├── singleton.py #單例類,我blog上面有相關(guān)闡述,后面一章我粘貼過來(lái)。
?34 └── util.py #大部分都是sql查詢操作的封裝函數(shù)。
?35
?
?
仔細(xì)看完上面目錄介紹,基本上應(yīng)該對(duì)DB的結(jié)構(gòu)有個(gè)大致掌握了。下面我們分析一下源碼。
啟動(dòng)db模塊的命令:
$cat run.sh
python appmain.py
appmain.py便于學(xué)習(xí)被我改動(dòng)過了,如下:
if __name__ == "__main__":
??? servername ="dbfront"
??? config =json.load(open("config.json", 'r'))
?
??? dbconf =config.get('db')
??? memconf =config.get('memcached')
??? sersconf =config.get('servers',{})
??? masterconf = config.get('master',{})
??? serconfig =sersconf.get(servername)
?
??? ser = DBServer()
??? ser.config(serconfig,dbconfig=dbconf, memconfig=memconf,masterconf=masterconf)
??? ser.start()
?
實(shí)際上就是實(shí)例化DBServer類,把從config.json文件讀取的信息傳遞過去。DBServer就是原先f(wàn)irefly/server/server.py文件。改個(gè)名字好看。
config.json也被我改了一下,“services”里面只保留“dbfront”,其它都services內(nèi)容都無(wú)關(guān)。就不貼出來(lái)了,占地方。
?
現(xiàn)在看DBServer(FFServer)類:
class DBServer:
?
??? def __init__(self):
??????? """
??????? """
??????? self.leafNode =None
??????? self.db = None
? ??????self.mem = None
??????? self.servername =None
?
??? defconfig(self,config,dbconfig = None,memconfig = None,masterconf=None):
??????? """配置服務(wù)器
??????? """
??????? servername =config.get('name')#服務(wù)器名稱
??????? logpath =config.get('log')#日志
??????? hasdb =config.get('db')#數(shù)據(jù)庫(kù)連接
??????? hasmem =config.get('mem')#memcached連接
?
??????? app =config.get('app')#入口模塊名稱
?
??????? self.servername =servername
?
??????? if masterconf:
??????????? masterport =masterconf.get('rootport')
??????????? addr = ('localhost',masterport)
??????????? self.leafNode= leafNode(servername)
???????????self.leafNode.connect(addr)
???????????GlobalObject().leafNode = self.leafNode
?
?
??????? if hasdb anddbconfig:
???????????log.msg(str(dbconfig))
???????????dbpool.initPool(**dbconfig)
?
??????? if hasmem andmemconfig:
??????????? urls =memconfig.get('urls')
??????????? hostname =str(memconfig.get('hostname'))
???????????mclient.connect(urls, hostname)
?
??????? if logpath:
???????????log.addObserver(loogoo(logpath))#日志處理
???????log.startLogging(sys.stdout)
?
?
??????? if app:
???????????reactor.callLater(0.1,__import__,app)
?
?
??? def start(self):
??????? """啟動(dòng)服務(wù)器
??????? """
??????? log.msg('%sstart...'%self.servername)
??????? log.msg('%s pid:%s'%(self.servername,os.getpid()))
??????? reactor.run()
?
?
根據(jù)config.json的解析結(jié)果,我們精簡(jiǎn)掉所有無(wú)關(guān)內(nèi)容。發(fā)現(xiàn),DB模塊包括以下幾個(gè)功能模塊:
mastconfig #說(shuō)明我們需要連接一個(gè)root,也就是前面提到的master模塊
db #有數(shù)據(jù)庫(kù)操作,需要簡(jiǎn)歷數(shù)據(jù)池
mem #有memcache操作,要連接memcache。
?
所有連接信息,如ip、port等都是從config.json里面取得。
1、masterconfig部分,就是前面PB章節(jié)的介紹,這里實(shí)現(xiàn)leafNode去連接master模塊的root,就不再贅述了。
2、db pool部分也很簡(jiǎn)單,就是建立一個(gè)pool,提供一個(gè)connection的接口。大家去了解DBUtils.PooledDB這個(gè)庫(kù)就可以了。
3、mem部分,也沒有啥可說(shuō),純memclient就是調(diào)用python的Memcache而已,memcache的結(jié)構(gòu)又超級(jí)簡(jiǎn)單,就是get,set。不含任何邏輯的。想要實(shí)現(xiàn)邏輯關(guān)系,都要自己去構(gòu)建,就是上面我們提到的MMode和MAdmin等文件來(lái)實(shí)現(xiàn)。
?
OK,firefly庫(kù)部分的調(diào)用完畢,這個(gè)時(shí)候DB模塊已經(jīng)建立了,和master的PB連接,數(shù)據(jù)池,memcache連接。下面就是游戲內(nèi)容部分的實(shí)現(xiàn)了。
?
除了master模塊,其它所有模塊的游戲部分(app目錄下面的內(nèi)容)都是通過
??????? if app:
???????????reactor.callLater(0.1,__import__,app)
這種方式來(lái)import進(jìn)來(lái)的。對(duì)我這種python新手還真的迷惑的半天。實(shí)際上就是根據(jù)config.json里面對(duì)于app項(xiàng)的內(nèi)容。對(duì)于db這里展開是:
?? reactor.callLater(0.1,__import__,app.dbfrontserver)
就是過0.1秒執(zhí)行 import app.dbfrontserver。其內(nèi)容如下:
GlobalObject().stophandler = initconfig.doWhenStop
initconfig.loadModule()
loadModule()干3件事情:
def loadModule():
??? register_madmin()
??? initData()
??? CheckMemDB(1800)
注冊(cè)幾個(gè)表,初始化角色數(shù)據(jù)到內(nèi)存,同步內(nèi)存數(shù)據(jù)到數(shù)據(jù)庫(kù)
?
注冊(cè)表的代碼在mmode.py中,過程就是實(shí)例化幾個(gè)MAdmin來(lái)表示相應(yīng)表的結(jié)構(gòu),然后添加到MAdminManager這個(gè)單例管理類中。
MAdmin有幾個(gè)屬性代表表的主鍵,外鍵,表名稱等信息。
MAdmin的insert函數(shù)會(huì)調(diào)用父類的Memobject的insert函數(shù)。
??????? nowdict =dict(self.__dict__)
??????? delnowdict['_client']
??????? newmapping =dict(zip([self.produceKey(keyname) for keyname in nowdict.keys()],
?????????????????????????????nowdict.values()))
???????self._client.set_multi(newmapping)
實(shí)際上就是根據(jù)self的所有屬性(除了_client,這個(gè)屬性指的是memclient)來(lái)生成一個(gè)字典,然后把這個(gè)字典的內(nèi)容緩存到memcache中。
比如tb_item表對(duì)應(yīng)的MAdmin,生成的memcache內(nèi)容就包括(不限于)
Key?????????????????????value
tb_item:_name????? xxxx
tb_item:_lock????? xxxx
tb_item:_fk????? xxxx
tb_item:_pk??? xxxxx
這里其實(shí)只是把表結(jié)構(gòu)給緩存到memcache了,壓根沒有碰表的數(shù)據(jù)。MAdmin有幾個(gè)個(gè)函數(shù)可以取數(shù)據(jù),
load()#這個(gè)是根據(jù)表名稱,select * 并且一條一條生成MMode,然后緩存進(jìn)memcache,MMode前面提到過,代表一條數(shù)據(jù)的內(nèi)存對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu)。
?
getObj(self,pk):#先判斷pk這條數(shù)據(jù)是否在memcache,是否有效,如果沒有再?gòu)臄?shù)據(jù)庫(kù)取出來(lái)并同步到memcache中。
?
這兩條函數(shù)其實(shí)在db模塊啟動(dòng)過程中都沒有被調(diào)用,(可以加斷點(diǎn)或者打印驗(yàn)證)
?
OK,分析到這里下面在看角色初始化initData()的部分就簡(jiǎn)單了
?? def initData(self):
??????? allmcharacter =dbCharacter.getALlCharacterBaseInfo()
??????? for cinfo inallmcharacter:
??????????? pid =cinfo['id']
??????????? mcha =Mcharacter(pid, 'character%d' % pid, mclient)
???????????mcha.initData(cinfo)
Mcharacter也是MemObject的子類,做的就是根據(jù)數(shù)據(jù)庫(kù)中的角色信息實(shí)例化Mcharacter內(nèi)存數(shù)據(jù),然后調(diào)用memobject的insert同步到memcache。
取角色信息的過程相反。調(diào)用mcharacterinfo()函數(shù),唯一一點(diǎn)不同是,這個(gè)函數(shù)有@property修飾,我查了一下,表示這個(gè)函數(shù)可以當(dāng)成屬性來(lái)用,python真酷!
?
?
這里吐槽一下注釋: 擺明是從啥地方copy過來(lái)的,注釋的牛頭不對(duì)馬嘴,害的我看了老半天,都木有想明白。
"""初始化城鎮(zhèn)要塞對(duì)象
??????? @paramterritoryId: int 領(lǐng)地的ID
??????? @param guard:int 殖民者的ID
??????? @paramguardname: str 殖民者的名稱
??????? @paramupdateTime: int 領(lǐng)地被更新的時(shí)間
"""
最后再嘮叨一下checkAdmins();這個(gè)函數(shù)負(fù)責(zé)每隔1800(magic number)秒刷一邊MAdminManager類管理的所有MAdmin(表)。調(diào)用這些MAdmin對(duì)應(yīng)的checkAll();
這個(gè)checkAll函數(shù)會(huì)取得memcache中所有緩存數(shù)據(jù),比較是否以本表前綴開頭,如果是,則判斷這些是否有效,是否過期,是否需要寫入數(shù)據(jù)庫(kù)。。。。
?
在我看來(lái),這里有些可以優(yōu)化的邏輯。比如把取memcache所有數(shù)據(jù)的步驟提到MAdminManager層面,這樣每個(gè)MAdmin就不用單獨(dú)執(zhí)行一遍。
但是如果是多個(gè)memcache服務(wù)器,又該怎么辦?各種頭疼,問題太多,智商不夠用。
?
這個(gè)函數(shù)是魔鬼,我暫時(shí)沒有敢去動(dòng)它,等我多學(xué)習(xí)學(xué)習(xí)相關(guān)內(nèi)容再去做優(yōu)化。總結(jié)
以上是生活随笔為你收集整理的firefly游戏服务器学习笔记 6———— db模块的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何更改C语言标准
- 下一篇: 电影推荐系统 python简书_【记录|