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的方案也只能解决单一维度查询的问题。
解决方案
透明分布式
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);
全局二级索引本质是一种数据冗余。例如,当执行一条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提升了数百倍:
数据库响应时间,单位毫秒
同时,全局索引的添加,使得绝大多数查询做到了“本地化”,可以理解为,一个SQL只对一个DN发起请求。这样,整个系统拥有了极高的扩展上限,可以做到线性扩展,也即如果10个节点可以支撑50W的QPS,那么20个节点就可以支撑100W的QPS。
下图是业务上线后的效果,使用8个节点,在峰值达到20W QPS的情况下,依然保持着1ms的响应时间:
数据库QPS,峰值20W左右
数据库响应时间,单位毫秒
客户收益
百亿数据查询从100毫秒下降到2毫秒级,RT提升50倍。
同等规格下TPS提升100倍,轻松应对百万QPS场景。
分布式改造对业务透明,代码零修改。
客户证言
“PolarDB分布式版有极强的线性扩展能力,能够多写多读;它的全局索引能力,是分布式改造的利器,成功解决了传统分布式方案中多维度查询的难题,在《香肠派对》的好友系统上,实现了百亿好友关系20万QPS的毫秒级查询。”
——厦门真有趣《香肠派对》服务端主程 洪光裕