亚马逊AWS官方博客
利用DynamoDB和S3结合gzip压缩,最大化存储玩家数据
前言
一些传统游戏架构中,采用MySQL存储玩家存档数据,利用分库分表分散单库单表的存储和性能压力,从而达到支持更多玩家的目的。随着数据量增长,数据表中varchar类型已经无法满足游戏中单字段的存储需求,而blob字段的应用对于这种架构下改造成本是最低的,因此一些游戏开始在最初设计的时候,数据库表结构就采用了Blob字段作为其玩家的游戏任务、道具等数据的存储。
Blob字段在MySQL 5.6 / 5.7中存在bug(MySQL Bugs: #96466),这个bug有概率导致数据库集群崩溃,造成数据丢失。即使在MySQL 8.0中,由于引擎本身设计的限制,在单表20GB以上,高频的更新就会导致数据库出现性能受限。并且随着表增大,性能问题会越来越明显。
随着当游戏业务爆发时增长的时候,传统关系型数据库在分库分表的时候,需要进行应用改造,同时存在一定的停机维护时间。而且这些扩展完成后,在游戏的夕阳期进行收缩也需要进行应用改造,这无疑对业务开发和基础运维的部门造成了很多额外的工作量。
DynamoDB在应用到这个场景上是非常适用的。在业务发展任意阶段,都可以实现0停机的扩展,自动伸缩的特性。而且这一切对于应用层是完全透明的。同时在日常运维中也可以贴合业务负载进行动态扩缩容,从而进一步降低成本。
概述
本文主要讲述在游戏场景下,根据DynamoDB的限制(每个项目都必须小于400KB),在限制下尽可能存储更多的数据和当存储量超出限制时,扩展存储的最大化利用空间。重点描述如何利用DynamoDB+S3保存玩家存档中的大数据量属性,避免数据存在S3上后,在数据写入S3时,发生读取到S3旧存档的情况。同时利用gzip压缩减少数据大小,减少IO的开销提升性能。
架构图
实战编码
目标
- 所有数据保存前都进行gzip压缩,读取后都用gzip解压。
- S3存储和DynamoDB的binary字段存储可以自适应。如果用户数据压缩后如果大于指定的值则写入S3,否则直接保存到当前数据库项目中的字段。
- DynamoDB项目读取的时候,解析解压后的字段,如果字符串以s3://开头,则继续从S3中获取数据
- 设置S3读锁字段,判断当前状态是否正在写入S3,以阻塞读进程。在每个项目需要写入S3前都会设置read_lock为Ture,S3写成功后则设置为False。读取记录后,read_lock是否为True,如果是判断被阻塞,进程会等待一段时间后进行重试,直到重试次数超出指定的值。重试超时后,读进程会认为写进程可能由于某种原因导致写永远无法成功,于是会将read_lock设置成False。
第一步:初始化环境参数
参数说明
- UPLOAD_TO_S3_THRESHOLD_BYTES:为字段最大的数据存储长度限制。单位为:字节数。由于DynamoDB一个项目(Item)数据大小限制为400KB。我们除了数据存档中最大字段还必须预留一部分空间给其他字段,避免整个Item超出400KB。
- USER_DATA_BUCKET:S3用于存储超出400KB后的玩家大字段数据。需要提前建好,具体步骤参考:创建存储桶
- S3_READ_LOCK_RETRY_TIMES:限制当玩家在S3上的存档处在写入状态时候,读请求重试的次数。在项目处于读锁状态的时候,读进程会等待一段时间后重试。
- S3_READ_RETRY_INTERVAL:读锁状态下,重试读的间隔时间,单位:秒。
注意:S3_READ_LOCK_RETRY_TIMES乘以S3_READ_RETRY_INTERVAL
的时间理论上必须小于S3存档上传时间的最大值,因此实际使用本文中的代码应该根据存档可能的大小来调整这2个参数。否则可能存档会有大概率会发生脏读的情况。
第二步:创建DynamoDB表
第三步:编写辅助逻辑
指数级回退函数
S3路径判断函数
S3文件获取
检查大小超限
S3 Key生成函数
这个函数可以将玩家的存档随机分配到S3桶下不同的Prefix中,这有利于提高S3中IO的性能。
文件上传到S3
第四步:编写主体逻辑
写入单个项目到DynamoDB数据库
读取数据库中一条玩家记录
最后,编写测试逻辑
准备几个不同大小的json文件,观察写入数据库中的变化。
如果需要测试读锁,可以将数据库中单个项目的read_lock手动设置成True,然后观察读取逻辑在这个过程中的变化。
总结
在本次测试中发现,json格式的数据使用gzip后,压缩率约为25%左右,理论上我们可以把单个项目(item)中可以存储最大约为1.6MB的数据项。即便有少量压缩后超过400KB的数据,也可以存储到S3上,仅在DynamoDB中存储元数据和大字段数据在S3上的路径。
gzip会带来一些额外的计算和IO开销,但是这些开销主要会落在游戏服务器上,对于数据库来说反而减少了IO的开销。
在大多数场景下,玩家数据即便不压缩也很少会超过400KB。这种情况下,建议可以尝试对比压缩启用和不启用两种场景的性能数据。以决定哪种方式更适合自己的游戏。
限制
对于存在单用户有高并发存档需求的游戏而言,以上设计中并未包含在数据存储在S3上后,出现并发写的场景考虑。如果有此场景的需求,需要一些应用逻辑或者架构调整。