先来看看KBEngine对数据库的支持,我们围绕三个问题来展开。
-
支持什么类型的数据库?
在KBEngine中,对数据库的操作全部由DBMgr进程完成,其他服务器进程通过与DBMgr进行网络传输交互,间接访问数据库。
在它的PPT中展示其支持MySQL、MongoDB、Redis和自定义的数据库,但是从代码上来分析,KBEngine仅针对MySQL和Redis做了全量的支持,MongoDB和自定义的数据库需要自己实现方法。
KBEngine封装了一套接口基类db_interface,所有对数据库的操作由该接口类对外提供,KBEngine为我们实现了db_interface_mysql和db_interface_redis,分别是MySQL和Redis的接口实现。对数据库的操作最终由它们来完成,KBEngine采用了MySQL自带的MySQL.h(自带于MySQL中,安装完成或解压后能找到)和hiRedis(hiRedis是一个操作Redis的在github上开源且协议友好的C语言封装库,官方也推荐使用)分别来操作MySQL和Redis。
graph LR
db_interface-->db_interface_mysql
db_interface-->db_interface_redis
db_interface_mysql-->MySQL.h
db_interface_redis-->hiRedis
-
支持的数据库模式?
支持的独立单机的standalone数据库服务器,没有做数据库集群。事实上,MySQL和Redis都有集群(cluster)模式和主从(master-slave)模式。另外,KBEngine也没有实现在MySQL前面加上Redis或memcached做热数据缓存的策略(MySQL是文件形式的数据库,而Redis和memcached是内存形式的数据库),要做热数据缓存,需要保证在内存中的热数据和硬盘中的持久化数据的一致性问题,参考大牛的文章《缓存更新的套路》。另外,如果我们要做成数据库集群的形式,我们需要额外考虑分布式数据的一致性处理,参考《分布式系统的事务处理》,虽然这些东西支持分布式集群的数据库会帮我们实现一些基础框架。
回到开始,KBEngine仅支持了单机的standalone数据库服务器,为什么不做集群呢,我们来分析具体的游戏场景:在游戏进行中,对数据库的访问不是重点要素,大量的逻辑计算往往才是,因此需要存储到数据库中的数据量事实上并不是很大,比如玩家的属性、背包数据,而且可以先驻留在引擎中,再延迟地刷到数据库里,而对于单局式的游戏,往往一局的时间不短,进行完一局才会有数据比如玩家胜率之类的刷到数据库中…访问量级都在可控范围内,即使游戏上线运行后收效暴好,用户暴增导致数据库成为瓶颈了,也可以通过增加多个standalone数据库服务器来承载起来。KBEngine作为MMORPG的服务端引擎,在存档往数据库刷数据时也是用的延迟写的策略,在BaseApp进程间轮流调度,BaseApp向CellApp获取Entity的Cell部分的数据再定时转给DBMgr存储到数据库中。
什么情况下会用到数据库集群呢,我理解是以数据承载为核心的业务需要,应对的是亿级的海量数据和高并发访问需求,比如搜索引擎、社交平台、短视频平台、订单系统等等,游戏服务器花大成本做分布式数据库性价比不高。
前面还提到热数据缓存的策略,其实有了虚拟内存技术后,可以玩点骚操作,只使用Redis这类内存形态的数据库,用硬盘来虚拟内存,这样让操作系统帮忙打黑工,热数据在内存中,不常访问的数据被操作系统自动换出到硬盘上,这样可以不用考虑内存中的热数据和硬盘中的持久化数据的一致性问题了(当然也有新的问题T_T,Redis采用rdb快照/aof命令记录/两种混合的形式持久化整个数据库,一旦数据库服务器宕机或者掉电重启,灾难就来了:采用热数据缓存的策略,完整的数据还是在MySQL的硬盘文件中,Redis只需要恢复很少的热数据部分;而完全采用Redis并用虚拟内存的策略,要恢复整个数据库到内存及虚拟内存中,灾难严重程度与数据量成正比)。
容灾,KBEngine没有做数据库级别的容灾,但是数据会在各BaseApp进程间备份,刷数据库是各个BaseApp轮流通过DBMgr来延迟刷写上去的,数据库失效重启间隔只要保证其他进程都存活,一般不会出现问题。大多数游戏的数据库都没有做冗余备份,对于极度重要的数据,可以单独设置一个主从模式的数据库来存储(理念:对数据的重要程度作分级处理,《如何长时间保存重要数据》)。
缓存穿透和缓存雪崩问题,由于真实的数据库访问都从DBMgr进程中走,数据库服务器架设在内网环境,不对客户端直接暴露,因此能够在前面做相应的保护措施。
-
存储什么数据到数据库中?
KBEngine是服务器框架,存储什么数据到数据库中由具体游戏服务器开发来配置,配置在xml文件中。游戏场景中的一个对象就是一个Entity,要存储的内容即Entity中的一部分数据结构,比如玩家数据、固定NPC数据,有工会拍卖行等等的还有这些没有展现实体的逻辑Entity的数据。
附:Redis和MySQL一些使用上的对比分析(不做详细对比了,google一下应有尽有,只对一些重要的和思考分析的内容做个简要总结):
Redis是内存形式的键值(KV)数据库,游戏上一般用hash类型存储。Redis服务进程是单线程的,因此在standalone模式下线程绝对安全。提供持久化支持,rdb快照、aof命令记录、两种混合(rdb快照+增量aof命令记录)。
MySQL是文件形式的结构化数据库,MySQL的具体功能由存储引擎驱动,常见的存储引擎是InnoDB和MyISAM,我们可以通过命令单次临时地改变存储引擎,或者通过编辑my.cnf配置文件default-storage-engine=InnoDB永久地修改存储引擎。 MySQL在5.1版本前默认使用的是MyISAM,之后使用的是InnoDB,因此我们重点关注:它们俩间的区别,InnoDB的出现解决了什么问题,又有什么问题,这个存储引擎是符合我们的需求的吗。 InnoDB支持事务,是根据主键进行展开的B+树的聚集式索引,树叶节点直接存数据,因此主键索引效率高,但不保存表行数,执行SELECT COUNT *时会遍历全表。锁最细粒度为行锁,行锁只对主键索引有效,并且该索引不能失效,否则会锁表。对于UPDATE、DELETE和INSERT语句,InnoDB会自动加排他锁,对于普通SELECT语句,InnoDB默认不加任何锁。 MyISAM是非聚集型索引,存储时会分开两份存储,一份索引和一份数据,其中索引文件中的索引指向数据文件中的数据。不支持事务但查询速度快(InnoDB在做SELECT的时候要维护更多的东西:InnoDB要缓存数据块,MyISAM只缓存索引块,InnoDB寻址需要映射到块再访问到行,MyISAM直接记录了文件的偏移,InnoDB还需要维护MVCC多版本并发控制(支持事务需要的)),支持全文索引和树索引,表锁。 拓展一下,MySQL的存储引擎还有Memory和Archive:Memory即内存形态的数据库,支持树索引和Hash索引(嘿嘿,和Redis及memcached类似了,但是Redis的名声还是盖过了Memory存储引擎的MySQL,另外也有不少人的文章中说Redis的性能更好一些,我没有做过测试实验,因此不做评论);Archive下插入速度快,压缩率高,一般用在日志等基本不读写的存档。
选取就显而易见了,游戏服务器的数据库中的数据,是频繁读写的,甚至有可能写的频率比读还高(有内存缓存驻留,只是定时地刷写数据库),表锁的粒度太大,InnoDB更符合一些。
协议
本文以上内容遵循CC BY-ND 4.0协议,署名-禁止演绎。
转载请注明出处:https://tis.ac.cn/blog/kongdeyou/database_of_kbe/
作者:kongdeyou(https://tis.ac.cn/blog/author/kongdeyou/)