游戏行业:PolarDB闪电助攻,《香肠派对》百亿好友关系实现毫秒级查询

Highlight

  • 透明分布式

  • 全局二级索引

  • MySQL兼容性

客户介绍

真有趣(So Funny)成立于2012年8月,致力于为全球用户提供健康有趣快乐的游戏体验与服务。目前已推出《香肠派对》、《不休的乌拉拉》、《仙侠道》等9款游戏,累计服务2亿多用户。这里聚集着一群有趣的人,秉持用户第一、热爱创作、讲逻辑的理念,为赢得百万人热爱而奋斗!

《香肠派对》是由真有趣游戏开发、由心动网络发行的一款“吃鸡”游戏,“开局一根肠,装备全靠捡”,曾连续三年获得“金翎奖”,拥有非常成熟的职业联赛。

业务痛点

用户关系是游戏类应用非常普遍的场景,需要存储用户或者玩家之间的相互关系,通过社交关系提升用户的活跃度以及黏性,帮助玩家及时找到有关联的好友。在用户关注关系中,主要包含几种状态:

  • 关注我的人->我的粉丝(fans);

  • 我关注的人->我的关注(follow);

  • 相互关注的人->互关(mutual);

  • 拉黑的人->加黑(黑名单);

其核心表“关注表”对应的表结构示意:

create table user_focus(
  id bigint primary key,
  uid bigint, -- 用户ID
  focus_uid bigint, -- 关注的用户ID
  extra varchar(1024), -- 其他业务属性
  index idx_focus_uid(focus_uid),
  index idx_uid(uid)

在业务场景上也分为:

  • 我关注了谁

select * from user_focus where uid=xxx;
  • 谁关注了我

select * from user_focus where focus_uid=xxx;
  • 玩家圈选,他们关注了谁,谁有关注了他们

select * from user_focus where uid in (xxxx);
select * from user_focus where focus_uid in (xxxx);

很容易理解,该表存在uid与focus_uid两种查询维度。

我们通过一张单表存储用户信息以及关注人的信息,上述这些业务场景的本质就是对基于这张表上不同列的多维查询。当前这张表的行数已经超过了百亿,传统关系数据库的能力捉襟见肘。

  • 响应性能达不到预期;

  • DDL变更的成本非常高;

  • 传统文档型数据库Elasticsearch、MongoDB的方案也只能解决单一维度查询的问题。

解决方案

  • 透明分布式

    image

    PolarDB分布式版兼容MySQL的语法和协议,应用从单机MySQL迁移过来无需修改代码,应用研发可以保持以前使用MySQL的思路和习惯。通过指定分区键的方式,轻松完成数据拆分,业务无感且透明,不再有任何分库分表的限制,分区键、分区数均可自由调整。

    PolarDB分布式版中,我们使用分区表的语法对表进行水平拆分,在本例中,我们对表按照uid进行分区:

    create table user_focus(
      id bigint primary key,
      uid bigint, -- 用户ID
      focus_uid bigint, -- 关注的用户ID
      extra varchar(1024), -- 其他业务属性  
      index idx_focus_uid(focus_uid),
      index idx_uid(uid)
    ) partition by hash(uid);
  • 全局二级索引

    PolarDB分布式版支持全局索引,在对数据做了水平拆分的基础上,还能支持业务的多维度查询的需求。通过创建全局二级索引,空间换时间,通过数据冗余的方式。

    为了满足focus_uid上的查询,我们只需要执行一条DDL语句,即可为表创建一个全局二级索引:

    create global index gsi_focus_uid on user_focus(focus_uid) partition by hash(focus_uid);

    image

    全局二级索引本质是一种数据冗余。例如,当执行一条SQL:

    INSERT INTO user_focus (id,uid,focus_uid,extra) VALUES (1,99,1000,"xxx");

    可以简单理解为,会分别往主表与gsi_focus_uid写入一条记录:

    INSERT INTO user_focus (id,uid,focus_uid,extra) VALUES (1,99,1000,"xxx");INSERT INTO gsi_focus_uid (id,uid,focus_uid) VALUES (1,99,1000);

    其中user_focus主表的分区键是uid,gsi_focus_uid的分区键是focus_uid。

    同时,由于这两条记录大概率不会在一个DN上,为了保证这两条记录的一致性,我们需要把这两次写入封装到一个分布式事务内(这与单机数据库中,二级索引通过单机事务来写入是类似的)。

    当我们所有的DML操作都通过分布式事务来对全局索引进行维护,二级索引和主键索引就能够一直保持一致的状态了。

性能测试

业务压测过程中进行了全局索引的创建,索引添加过程是在线进行的,不会锁表。

数据库的响应时间由创建前的平均数百毫秒,下降到了创建后的1-2ms,同时TPS提升了数百倍

image.webp

数据库响应时间,单位毫秒

同时,全局索引的添加,使得绝大多数查询做到了“本地化”,可以理解为,一个SQL只对一个DN发起请求。这样,整个系统拥有了极高的扩展上限,可以做到线性扩展,也即如果10个节点可以支撑50W的QPS,那么20个节点就可以支撑100W的QPS。

下图是业务上线后的效果,使用8个节点,在峰值达到20W QPS的情况下,依然保持着1ms的响应时间:

image.webp

数据库QPS,峰值20W左右

image.webp

数据库响应时间,单位毫秒

客户收益

  • 百亿数据查询从100毫秒下降到2毫秒级,RT提升50倍。

  • 同等规格下TPS提升100倍,轻松应对百万QPS场景。

  • 分布式改造对业务透明,代码零修改。

客户证言

PolarDB分布式版有极强的线性扩展能力,能够多写多读;它的全局索引能力,是分布式改造的利器,成功解决了传统分布式方案中多维度查询的难题,在《香肠派对》的好友系统上,实现了百亿好友关系20万QPS的毫秒级查询。”

——厦门真有趣《香肠派对》服务端主程 洪光裕