网络游戏中最重要的数据莫过于玩家在游戏的过程当中产生的数据。git
能够简单的分红两类:github
第一类数据主要是相似角色『基础』信息,背包、技能、任务,以及全部(或者部分)玩家共有的王国、地图、联盟等信息。
第二类主要是相似『日志』信息同样的,好比「某个地方某角色使用了某道具」这样的操做记录。golang
这篇文章中咱们主要讨论第一种数据的处理,关于后一种用做记录和分析的数据,可能会在后面写一个专门的Blog介绍。redis
首先咱们须要决定数据的形态,和描述的方法。
这里有一个选择:以『条目格式』为核心,仍是以『文档格式』为核心。算法
『条目格式』的优势:看起来较为平坦,方便实时落地,和各类数据库存储模式搭配方便,外部工具修改方便,便于批量处理。
『文档格式』的有点:一般是树形结构,和大部分脑中的角色模型更加匹配,在内存中处理的时候,一般来讲效率更高。数据库
采用不一样的数据结构一般会影响到项目使用的技术,甚至直接影响到总体架构和处理模型。若是使用条目,可能就会用memcache/redis存储,使用更多的无状态业务逻辑处理方式。json
咱们团队习惯的,是以 struct
为核心,配合 bson
/protobuf
实现接口和序列化,主要操做都在内存中处理的方式。
使用的时候,内存中的数据结构为 Go 的结构体;落地时使用MongoDB
数据库的保存的bson
格式的文档(Document);在和移动端交互以及某些内部RPC接口调用的时候,使用protobuf
做为传输格式;配置方面,使用etcd
同步的csv条目数据。服务器
不管是bson
、protobuf
仍是 csv 条目数据,均可以使用的是 Go 里面的扩展性极强的 struct tag
来完成自动转换。网络
在长时间的线上业务维护和开发过程当中,玩家的数据结构老是会修改、更新的。
若是每次都中止系统,把几千万甚至上亿的存放在数据库中的数据都升级准备好确定是不可能的。一般状况下会在数据载入的过程当中进行数据兼容检查和更新。数据结构
此时,在go
程序里面使用bson
做为数据的最终存储方式会给咱们带来很是多的便利。bson unmarshal
的自动兼容处理能够把大部分『新增』、『删除』操做都完美处理好,对于那些须要修改的字段或者复合结构,咱们彻底可使用新增操做来替代,升级以后删除旧数据便可。
使用二进制数据的那种痛苦 v0.01 -> v0.02 -> v0.0N 升级过程不再须要见到了。而咱们也不须要向使用条目存储数据的项目同样,等待一个巨大的库执行 alter table 的操做。
有不少人都作过测试,有不一样的结果。咱们实际测试的结果是:
gob 最好,bson,protobuf 和 json 较差。
上面提到,须要操做的数据的,都是以 struct 的形式存放在内存中的。天然不能将全部的数据都载入,一般咱们须要维护一个数据结构,将那些好久没有使用过的数据逐步淘汰出内存。这里使用一个LRU表就能够恰当且方便的完成。
Go的一个LRU实现
获取到须要淘汰的数据,一般会将其持久化到数据库中,线上咱们遇到过这样的一个场景:
因此,作了LRU,最好配合限流或者hash操做,防止同时间大量数据一块儿淘汰。
数据放在内存中,老是给人一种『不靠谱』的感受,至少是有很多人给我提起过的。
然而游戏的业务中最重要的是什么?我我的认为是响应速度和可用性。
实际上,每一个半年左右总会有一些云主机无辜挂掉(该死的阿里云),实践证实,经过一些常规的手段,能够在上层避免掉软硬件crash带来的灾难,至于内存中较新的未持久化的数据,丢了就丢了吧:
Go的动态特性(其实就是Reflect啦),给咱们带来了一些运行时调试的便利。起码有两种方式能够实现不中止服务器修改数据:
因为Go到目前为止(1.11)仍然没有靠谱的动态更新方案,咱们开始考虑,在某些运营业务为主的服务中,尝试用lua来编写业务逻辑,这样能够达到不停机修复bug甚至更新程序。