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服务器,又该怎么办?各种头疼,问题太多,智商不够用。

 

这个函数是魔鬼,我暂时没有敢去动它,等我多学习学习相关内容再去做优化。

你可能感兴趣的:(游戏,服务器,memcached,cocos2dx,Firefly)