第1页
面向游戏业务的内存数据库开发
真有趣的游戏内存数据库实践报告
第2页
Copyright © 2016 SoFunny
第3页
正式开始之前
Copyright © 2016 SoFunny
第4页
自我介绍
• 陈明达,花名:达达 • 来自厦门真有趣信息科技有限公司 • C#、PHP、Erlang、Go、AS3 • 杂而不精,现学现卖,土法造炮
Copyright © 2016 SoFunny
第5页
今天主要的内容
• 游戏内存数据库方面的实践经验 • 游戏内存数据库的一些实现细节 • 供大家参考,希望大家可以少走弯路
Copyright © 2016 SoFunny
第6页
为什么讲内存数据库
• 因为它影响产品的方方面面 • 产品品质:游戏过程要流畅,游戏数据要安全 • 开发效率:开发要简单高效,不要很容易出BUG • 运维效率:运维方案要成熟可靠并且高效 • 运营效率:要方便数据挖掘 • 它的演进过程也是架构演进的缩影
Copyright © 2016 SoFunny
第7页
内存数据库的诞生过程
Copyright © 2016 SoFunny
第8页
第一次实践
• 《魔法之城》,SLG,2008年 • PHP + Memcached + MySQL • 手工编写数据库到缓存的加载代码 • 手工编写缓存到数据库的落地代码
PHP Memcached
MySQL
Copyright © 2016 SoFunny
第9页
第一次实践遇到的问题
• 手工做的事情太多,每一个业务都 是特例,缺乏强约束力的规范,导 致BUG频发
• 没有事务保护,业务上的BUG直接 导致数据损坏,手工修数据和排查 问题耗费大量精力
Copyright © 2016 SoFunny
第10页
迷茫阶段
• 怎样才能减少手工和特例化产生的BUG? • 怎么实现事务机制? • 怎么容灾? • 传统数据库还是KV数据库? • 是否有现成方案?
Copyright © 2016 SoFunny
第11页
理清思路
• 时间和能力有限,做最有把握做到的事 • 使用MySQL,运维方案成熟可靠,便于数据挖掘 • 自动映射MySQL表结构,规则简单,容易自动化 • 在游戏进程内组织数据,运行效率最高,容易实现
Copyright © 2016 SoFunny
第12页
第二次实践
• 《神仙道》,RPG,2010年 • Erlang + ETS + MySQL • 自动映射MySQL数据库结构 • 有事务机制 • 有SQL日志 • 有平滑升级机制
Erlang
业务逻辑
ETS
MySQL
SQL日志
Copyright © 2016 SoFunny
第13页
第二次实践遇到的问题
• 按表来组织缓存,数据集太大,游 戏越跑越慢(ID取模分表缓解)
• SQL文本日志文件不利于数据挖据
Copyright © 2016 SoFunny
第14页
第三次实践
• 《仙侠道》,RPG • Go + MySQL • 按玩家切分数据集 • 按业务切分数据集 • 结构化的事务日志 • 脚本化的审计工具
Copyright © 2016 SoFunny
Go
业务逻辑
Player DB
MySQL
事务日志
第15页
第三次实践遇到的问题
• 优化太早,引入不必要的复杂度 • 常驻内存,运营到后期硬件利用率降低 • 自定义二进制格式的事务日志不利于周边工具开发
Copyright © 2016 SoFunny
第16页
近期我们在做的
• 支持大服制游戏 • 冷数据剔除,提升硬件利用率 • JSON替代二进制,进一步发挥日志的价值 • 加入索引机制,允许后期优化,避免过早优化
Copyright © 2016 SoFunny
第17页
内存数据库的实现细节
Copyright © 2016 SoFunny
第18页
几个关键点
• 部署结构 • 平滑升级 • 数据切分 • 事务机制
Copyright © 2016 SoFunny
第19页
部署结构
• 游戏服和MySQL是多对一关系
1服 2服 3服
• 游戏启动时全量缓存数据 • 游戏数据变更异步回写MySQL • MySQL做主从同步
主库 从库
• 在从库上做完整备份+增量备份
完整
增量
运营
• 运营后台到从库查询数据
备份
备份
后台
Copyright © 2016 SoFunny
第20页
平滑升级
• 每次结版前都要手工收集数据库变更是很辛苦的,而 且容易犯错
• 从开发过程中就严格执行平滑升级过程,跨版本升级 只是重现开发过程
• 以日期+序号作为命名规则,每次数据库变更都提交一 份PHP脚本到一个固定目录下
• 用一个PHP脚本去顺序执行目录下的变更脚本,并记 录下当前版本
Copyright © 2016 SoFunny
第21页
按玩家切分数据
• 游戏业务的特性: • 玩家大部分时间都在操作自己数据 • 每个玩家的数据集并不大
• 按玩家切分数据会产生两种表: • 一对一,每个玩家只有一条数据,比如玩家信息 • 一对多,每个玩家有多条数据,比如物品
• 命名规则很重要: • 玩家表“player_”开头 • 一对一的表,用“pid”做主键 • 一对多的表,用“id”做主键,必须有“pid”字段
Copyright © 2016 SoFunny
第22页
按业务切分数据
• 全局数据,比如帮派: • 帮派成员,帮派公告,所有周边系统都是以帮派为单 位访问数据的,所以按帮派切分数据很合理
• 玩家数据进一步切分: • 装备强化,装备洗练,装备镶嵌,所有装备养成系统 都是以装备为单位访问数据的,所以可以在装备对象 上加上子表数据引用
• 如果可以建立完善的索引机制,没必要根据业务切分数据
Copyright © 2016 SoFunny
第23页
事务回滚
• 首先需要顺序记录下事务过程中的非查询操作 • 事务回滚的时候从最后一笔纪录往前回滚 • 如果是插入数据,就把新数据删除 • 如果是修改或删除,就把旧数据替换回去
Copyright © 2016 SoFunny
第24页
事务回滚–示例
Insert 1 Update 2 Update 3
Delete
Copyright © 2016 SoFunny
第25页
事务提交
• 我们已经有一份非查询操作记录 • 按这份记录顺序生成SQL同步到数据库即可 • 但是必须强调,要以事务为单位:
• SET autocommit=0; • START TRANSACTION; • COMMIT;
Copyright © 2016 SoFunny
第26页
事务日志
• 我们已经有一份非查询操作记录 • 按这份记录顺序写入日志文件即可 • 前面说到结构化的事务日志:
Copyright © 2016 SoFunny
第27页
事务隔离
• 并发操作数据的时候,如何做好事务隔离? • 最土的办法是不要并发,等价于MySQL的串行级别 • 串行为什么可行?
• 人可感受的延迟大约在100ms左右 • 平均每次请求处理耗时40us • 100ms足够执行2500个请求 • 利用多核?那就按玩家做事务隔离,但是互动要单独隔离
Copyright © 2016 SoFunny
第28页
Thanks