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