本文作者:田维繁,网易游戏关系型数据库小组负责人
作为中国游戏开发领域的佼佼者,网易游戏始终站在网络游戏自主研发的前沿。其产品及周边产品线丰富多样,因此,为满足各种业务场景的需求,需要多种不同的数据处理产品。网易游戏的数据库团队致力于向公司内部提供全方位的数据库私有云服务,用丰富的数据库产品,以充分满足网易游戏对数据库产品的多样化需求。
尽管网易游戏已拥有比较全面的数据库产品矩阵,但每种数据库产品在特定业务场景下都有其明显的优势和局限性,鉴于我们内部涵盖的业务场景多样且复杂,我们期望能够根据具体的业务场景需求灵活选择适合的数据库产品。在此背景下,为了解决特定业务问题,我们将 OceanBase 引入了网易游戏。
业务架构及业务特性需求
下图是网易游戏某业务平台当前使用 MySQL 的数据库架构,随着系统承接的业务数据量及请求数的不断增加,该架构逐渐演化为 MySQL 经典的一主多从架构,通过主备库接了十余个从库来负责业务读请求。
然而,这样的业务架构存在以下四个痛点:
- 并发量高,对延迟敏感:在高峰期,主库请求量达到接近十万 QPS,单个从库读的请求也在几万左右,所有从库的总 QPS 在高峰期接近百万,整体并发量高,我们的业务对延迟极为敏感,不能容忍波动。
- 单库存储空间压力大:单节点的存储空间已经超过十几 TB 以上,对于处理高并发 TP 业务的 MySQL 来说,这样的压力是非常巨大的。
- 从库数据实时性要求:从库数据实时性要求很高,一旦从库出现慢查询或其他情况影响到从库延迟,会对业务造成极大影响。
- 运维困难:运维工作也面临很大的挑战,在突发流量的情况下,即使使用顶级配置的 MySQL 只能通过增加实例的方式来缓解压力。另外,如果单个只读从库出现故障,就需要进行实例重建的操作。由于 MySQL 实例的数据量很大,扩容从库、备份恢复都需要耗费大量时间,这对业务来说是难以接受的。
为了解决上述问题,我们迫切需要一款可以平滑横向扩展且性能稳定的分布式数据库。同时,考虑到 MySQL 需要对空间占用大的数据进行归档,并且归档时查询量极大,因此我们需要分布式数据库保证在扩展的同时能够承载大批量查询,且在同一套数据库集群上的业务间互不影响。我们希望这款数据库不仅能够保证性能,还能够降低成本。
经过梳理,我们对分布式数据库的需求优先级从高到低排序如下:
- 性能稳定:业务对查询延迟非常敏感,需要数据库运行稳定,几乎不出现抖动,并且不会有慢查询的问题。
- 支持高并发:满足高峰期主库请求接近十万 QPS,所有从库接近百万读请求的高并发要求。
- 平滑扩展:单机存储空间需要足够大,可支持未来数据持续增长。
- 低延迟:支持业务秒级同步主库。
- 低成本:因数据量较大,期望降低运行成本。
为什么引入OceanBase
在了解 OceanBase 分布式数据库的时候,我们发现到其具备解决上述业务痛点的特性,其中最看重的包括以下几个方面。
第一,稳定性。对于游戏行业在线交易系统业务,数据库的稳定性至关重要,OceanBase 集群三副本的分布式架构支持自动故障切换,能够保证少数节点故障情况下数据不丢失,服务不停顿,满足 RPO=0,RTO<8s 的容灾标准,确保业务连续运行不中断。OceanBase 在支付宝超大规模场景下使用,并且在金融核心场景下经过多年历练,稳定性久经业务考验。
第二,透明可扩展。OceanBase 具有极强的可扩展性,可以在线进行平滑扩容或缩容,并且在扩容后自动实现系统负载均衡,对应用透明无需更改业务代码,确保系统的持续运行,完全能满足网易游戏对存储横向扩展的要求。
第三,数据同步的实时性。业务对数据实时性要求极高,因此在初期引入新的数据库产品时,我们进行了长达一个月的压测和线上跑批测试。通过使用 OceanBase 官方提供的数据迁移工具 OMS,数据同步链路几乎没有出现明显延迟,完全满足查询业务数据实时性的要求。
第四,多租户资源隔离。OceanBase 数据库支持多租户,每一个租户可以看做是 MySQL 的一个实例,单个 OceanBase 集群可以支持创建多个租户,为多个业务提供服务。为了确保业务稳定运行,OceanBase 针对租户间的资源进行了隔离,例如 CPU、内存、IOPS。通过为每个业务创建一个租户来使用 OceanBase,业务间不会相互干扰,交付也可以更加敏捷。
第五,降低成本(存储压缩)。OceanBase 使用基于 LSM-Tree 自研的一套高级压缩的存储引擎,在存储到磁盘中时,默认对微块的数据进行编码和压缩,相比于 MySQL 的 B+ 树存储结构,OceanBase 能够节约 70%-90% 的存储成本,并且即使做了编码,也不会降低查询性能,而是会由于单个微块下存储的数据更多而提升查询效率。
网易游戏迁移了业务数据以后,发现即使 OceanBase 是三副本的情况,也能带来显著的存储节约,并且性能也满足业务需求。
除了上述的能力外,OceanBase 还有一些特性也是我们所关注的,比如:
- MySQL 兼容性,方便业务迁移,无需修改业务代码,花费太多人力做适配测试;
- HTAP 能力,对于一些 TP、AP 兼有的业务后期可以尝试将业务放到一套 OceanBase 集群上,无需维护一整套 TP+AP 的架构。
引入OceanBase的前期测试
在决定引入一个新的数据库之前,为了确保其适配性、高性能、高可靠等满足业务需求,我们需要对其进行严格的基准测试、业务测试、灰度测试,只有经过验证才能逐步接入应用环境。在前期测试中,我们主要从架构,高可用,一致性,兼容性,存储成本,性能等方面来做对比,以下是具体的测试情况。
(一)测试环境
(二)测试工具
本次测试使用 Sysbench 完成,分别进行了混合读写(读写比例 8/2)、只读场景、只写场景的测试。
(三)测试结果
- OLTP 性能方面,在小规模数据量(10 张表,每张表 2000 万)时,OceanBase 4.0 版本的性能表现几乎与 MySQL 持平,OceanBase 4.1 版本的性能表现优于单机 MySQL;在数据量较大时(1 亿以上)OceanBase 扩容节点后的性能优势远超单机 MySQL。
- OLAP 性能方面,在多个大表关联或聚合分析的情况下(1 亿以上), OceanBase 4.x 版本整体表现相对平稳,并没有出现较大波动,查询耗时也远低于 MySQL (MySQL 本身不适用于 AP 业务,此对比意义不大)。
- 关于存储数据压缩能力,我们从上游 MySQL 导出 5TB 数据到 OceanBase,三个副本总存储才 2.1TB,单个副本 700GB。以单份数据计算,数据压缩率接近 86%(由于上游 MySQL 存在一定的碎片空间,该数据可能也有略微偏差)。
*注:这里只是说明数据压缩的能力,没有考虑 CPU 以及其他方面的成本,总的成本需要根据不同业务场景单独计算。
(四)多租户资源隔离专项测试
针对多租户和资源隔离,我们同时对两个租户进行性能压测,主要是测试在单个租户由于资源被打满的情况下,另一个租户的请求响应等是否受到影响,测试过程如下:
首先,创建两个租户
1. CPU:2C;内存:2G
create resource unit test_unit max_cpu 2, max_memory '2G', max_iops 128, max_disk_size '2G', max_session_num 128, MIN_CPU=2, MIN_MEMORY='2G', MIN_IOPS=128; create resource pool test_pool unit = 'test_unit', unit_num = 1, zone_list=('zone1','zone2','zone3'); create tenant test_tenant resource_pool_list=('test_pool'), charset=utf8mb4, replica_num=3, zone_list('zone1', 'zone2', 'zone3'), primary_zone=RANDOM, locality='F@zone
1,F@zone2,F@zone3' set variables ob_compatibility_mode='mysql', ob_tcp_invited_nodes='%';
2. CPU:4C;内存
create resource unit test_unit2 max_cpu 4, max_memory '4G', max_iops 1280, max_disk_size 53687091200, max_session_num 128, MIN_CPU=4, MIN_MEMORY='4G', MIN_IOPS=1280; create resource pool sysbench_pool unit = 'test_unit2', unit_num = 1, zone_list=('zone1','zone2','zone3'); create tenant sysbench_tenant resource_pool_list=('sysbench_pool'), charset=utf8mb4, replica_num=3, zone_list('zone1', 'zone2', 'zone3'), primary_zone=RANDOM, locality
='F@zone1,F@zone2,F@zone3' set variables ob_compatibility_mode='mysql', ob_tcp_invited_nodes='%';
租户 2:2048 个线程进行并发压测
在租户 2 压测前和压测中,分别查看租户 1 的响应情况,以及压测过程中租户的CPU 使用情况。
租户 2 压测前,租户 1 的压测性能如下:
租户 2 压测过程中,租户 1 的压测性能如下:
其次,查看租户 2 的资源使用情况
- CPU:
- 内存:
得出测试结论如下:
- 资源稳定性满足期待:高并发线程对租户资源进行压测时,租户的 CPU 和内存资源使用稳定,并没有超过限制的最大值。
- 隔离性满足期待,租户间没有明显影响:当租户 2 高并发线程进行压测时,租户1的请求量略有下降, QPS 降低大概 20%。我们推测可能是由于 IO 资源没有做到充分的限制,出现一定程度的 IO 争用。需要指出的是,3.x 版本并没有限制 IO,但 4.0 版本已经实现了对 IO 的限制。就整体影响而言,从 CPU 和内存的使用情况来看,整体没有明显的影响。
综合以上测试结果,我们认为 OceanBase 在性能、稳定性、资源隔离、降本等方面均符合网易游戏的预期,因此,我们决定与业务方共同在应用环境中正式采用 OceanBase。
引入OceanBase的架构收益
引入 OceanBase 后,我们的业务架构如见下图所示,业务痛点得到有效解决。目前,OMS 工具会将 MySQL 主库所有数据同步到 OceanBase 集群中,上游 MySQL 主库会依据业务逻辑定期清理大量数据,以降低 MySQL 的存储空间,而 OMS 的链路设置了忽略 MySQL 中清理数据的 DML、DDL 操作,保证 OceanBase 集群中有一份完整数据,供业务查询。
整个架构替换以后,我们获得看以下几点收益:
- 业务查询稳定:OceanBase 运行稳定,在承担了原只读从库 MySQL QPS 的 15% 流量后,时延稳定,抖动小。
- 灵活扩展降低高并发压力:OceanBase 的扩展能力可以高效支持业务未来高并发请求,原来 MySQL 只读从库的 QPS 请求,迁移到OceanBase 后压力骤降,风险得到有效控制。
- 数据实时性得到有效控制:OMS 的逻辑复制方式读取 MySQL Binlog 操作,回放到 OceanBase 集群中,不会出现 MySQL 常见的主从延迟问题。如果出现数据同步延迟、慢查询,OceanBase 都有对应的监控报告。
- 单从存储成本大幅下降:与 MySQL 单副本对比,OceanBase 整体存储成本下降超过 80%,将上游 MySQL 数据量进行大量归档后,存储成本压缩了 30% 以上,存储压力大幅减少。通过 OMS 迁移 MySQL 数据到 OceanBase 集群,并定期清理 MySQL 的业务数据,大幅降低 MySQL 因数据量过大带来的各种风险。
- 运维更加简单:出现突发流量时,可以通过动态调整租户、集群资源的方式临时应急。出现突发流量或业务上线 SQL 出现慢 SQL 时,可以使用白屏化的 SQL 限流功能。如果需要水平扩容,整个扩容操作对应用无感。同时,在故障场景下,集群的 Paxos 高可用模式基本保障对应用无感。备份和恢复效率大幅提升,因整体压缩超过 30%,后台替换故障节点时,需要恢复的数据量相对 MySQL 更少,备份恢复时间也更短,效率提升了 3 倍以上。
引入OceanBase的最佳实践建议
原生分布式数据库与中间件模式的分布式数据库,在使用操作层面有一定的不同。在引入 OceanBase 的过程中,网易游戏积累了一些最佳实践建议,供大家参考。
建议 1:优化数据同步性能
使用 OMS 同步 MySQL 数据到 OceanBase 初期,我们遇到了一些挑战。特别是在进行增量迁移时,我们发现迁移的延时较大,导致迁移的性能较低。通过查询日志,我们发现写入 OceanBase 的语句是 replace into,手动执行测试 SQL 时发现速度确实较慢。经过SQL诊断排查,我们确认了一个关键问题,OB_GAIS_PUSH_AUTO_INC_RE 函数引起了自增列 RPC 耗时较高。
在与 OceanBase 团队的沟通中,我们了解到这个问题的主要原因在于 OceanBase 中的这张表是分区表,创建的自增列属性是 order。因此,在 OceanBase 执行 replace into 和 update 操作时,会进行全局同步更新自增列,这导致了 RPC 的代价较高。特别是在数据量大的情况下,性能就收到比较明显的影响。
🧰 解决方法:
- 可以满足业务的情况下,可以考虑去掉自增列属性,这样可以避免 OceanBase 更新自增属性时的全局扫描。我们最终采用了这个方案解决,由上游 MySQL 来控制 id 的唯一性。
- 另一个方法是将自增列的 AUTO_INCREMNT_MODE 属性由 ORDER 改成NOORDER,然后 replace into 里不指定自增列的值,让 replace into 使用 autoincrement_service 产生的自增值。
建议 2:合理设计分区表
在引入 OceanBase 时,利用其分布式的扩展能力,因此在 MySQL 原业务中单表几十亿行数据的情况下,迁移到 OceanBase 中,我们考虑建成分区表。前期测试过程中,考虑到现阶段的查询性能和未来的扩展能力,结合业务特性(大部分业务查询都会带交易 id 号的条件),我们将迁移的表结构设计成交易 id 号的 512 个 hash 分区,尽可能做到存储和性能平衡。
在业务灰度测试过程中,有少部分业务查询没有带交易 id 号的条件,而是使用了其他字段去做匹配查询,虽然这一类 SQL 在所有的业务请求中占比不算高,但实际也有几千 QPS。由于这类查询没有走分区键,所以每次请求都会去扫描分散在所有 OBServer 节点的 512 个分区数据,导致 RPC 多次请求,造成过多的网络延迟,反应出来的结果就是 SQL 执行非常缓慢,影响整个业务。
🧰 解决方法:
- 针对这类不走分区键的查询请求,选择合适的过滤条件列,创建全局索引,通过走全局索引方式,显著减少扫描的数据量。但这里要注意,如果这类查询请求过多,过滤条件列又不一样的情况下,该分区表可能需要创建多个全局索引,会带来额外的维护代价,一般不建议一张表上建太多个全局索引。
- 减少分区表的分区数,由原来的 512 个降到 10+ 个,在满足横向 Balance 的同时,即使扫所有的分区,RPC 延迟也相对降低一些。我们最终采用了这个方案来解决。
建议 3:确保上游 MySQL 到 OceanBase 的事务原子性
在业务试运行过程中,存在一种业务场景,比如卖家批量卖东西给买家,一次会生成上百个订单,加上各种资金结算,对应到数据库中,一个事务里可能会有几百个不同的 DML 语句需要执行。事务的 DML 执行在 MySQL 侧,业务的查询请求是在 OceanBase 集群上,有时候会发现在 OceanBase 查询的时候,读取到了一个事务中部分 SQL 完成的结果,不太符合事务的原子性。
查看 OMS 的日志发现,增量同步的链路里有明显提示(maxRecords is 64, cut it),将大事务默认切分成了几个小事务执行,原因在于 OMS 同步上游 Binlog 的时候,有一个默认参数 splitThreshold 来控制解析上游事务的记录数,当事务大小超过这个参数限制时,就会切分成几个小事务来执行,这种情况就导致在读取 OceanBase 中的数据时,这几个小事务可能只完成了一部分,对业务来说就是读到了 MySQL 中一个事务的中间状态。
🧰 解决方法:
可将 OMS 中的 JDBCWriter.sourceFile.splitThreshold 调大,比如我们调整到了 1024,保证数据同步到 OceanBase 过程时,也是一个事务完成。但这里要注意的是,这个参数不建议过大,设置得越大,OMS 占用的资源越多。
建议 4:设计主键或唯一键,保证数据同步的数据一致性
在 OMS 同步 MySQL 到 OceanBase 的过程中,查询 OceanBase 的表数据,存在重复的行数据。经过排查,该分区表没有主键和唯一键,导致同步过程中,无法进行一致性校验,在 OMS 同步过程中,如果出现重启、重试等情况时,就有可能重复同步数据,引起数据冲突。
🧰 解决方法:
对 OceanBase 的表加上主键或者唯一键,即使重试,也会保证同步的数据一致。
总结和展望
网易游戏引入 OceanBase 已近半年,总体表现非常稳定,未出现任何性能抖动和同步延迟问题,有效解决了业务痛点。
未来,随着 OceanBase 稳定运行,其可靠性和性能得到进一步验证,网易游戏将继续探索 OceanBase 的应用,我们计划逐步减少更多的 MySQL 从库,并考虑将全部业务迁移到 OceanBase。
同时,我们正在将 OceanBase 的生态纳入网易 SaaS DB 平台,以丰富我们的服务能力,这将使我们能够为更多的产品提供数据库层面的支持,助力网易游戏更多产品和关键业务发展。