云原生数据库PolarDB分布式版(PolarDB for Xscale,简称PolarDB-X)有极强的线性扩展能力,能够多写多读;它的全局索引能力,是分布式改造的利器,成功解决了传统分布式方案中多维度查询的难题,在《香肠派对》的好友系统上,实现了百亿好友关系20万QPS的毫秒级查询。
——厦门真有趣《香肠派对》服务端主程 洪光裕
真有趣(So Funny)成立于2012年8月,致力于为全球用户提供健康有趣快乐的游戏体验与服务。目前已推出《香肠派对》、《不休的乌拉拉》、《仙侠道》等9款游戏,累计服务2亿多用户。这里聚集着一群有趣的人,秉持用户第一、热爱创作、讲逻辑的理念,为赢得百万人热爱而奋斗!
《香肠派对》是由真有趣游戏开发、由心动网络发行的一款“吃鸡”游戏,“开局一根肠,装备全靠捡”,曾连续三年获得“金翎奖”,拥有非常成熟的职业联赛。
在TAPTAP上,《香肠派对》有2.3亿的下载量,有着庞大的玩家群体:
用户关系是游戏类应用非常普遍的场景,需要存储用户或者玩家之间的相互关系,通过社交关系提升用户的活跃度以及黏性,帮助玩家及时找到有关联的好友。在用户关注关系中,主要包含几种状态:
以《香肠派对》为例,该游戏有比较强的社交属性,其好友功能提供了“关注”、“粉丝”、与推荐(找朋友)功能:
其核心表“关注表”对应的表结构示意:
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)
)
如果一个用户关注了100个人,那么在这张表里有100条记录,目前整个关注表达到了百亿的量级。
这张表有以下几种访问模式:
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两种查询维度。
在这个量级下,传统的单机数据库容易出现以下几类问题:
无论使用哪种选型,核心是将大表进行拆分。在对该表进行分布式改造时,业务团队有几种选择:
以ES为例,如果为了支持高性能的查询,需要设计合理的DocumentID,否则,ES中的每次查询都会涉及到所有的节点,非常低效。因此又回到了上面的问题,uid和focus_uid两个维度无法兼得。
本质上,上面几种方案,虽然数据做到了水平拆分,但几种数据库内部只能解决单维度查询,多维度的查询问题只能由应用层解决。
阿里云瑶池数据库旗下的PolarDB分布式版成为了更好的选择,在这个场景中,PolarDB分布式版有着无可比拟的优势:
在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);
此时对于uid上的查询,自然是很高效的。为了满足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操作都通过分布式事务来对全局索引进行维护,二级索引和主键索引就能够一直保持一致的状态了。
此外,PolarDB分布式版为GSI的性能也做了非常多的优化,例如:
这里不做过多展开,有兴趣可以参考文章《PolarDB-X 全局二级索引 - 知乎》
业务压测过程中进行了全局索引的创建,索引添加过程是Online的,不会锁表。
数据库的响应时间由创建前的平均数百毫秒,下降到了创建后的1-2ms,同时TPS提升了数百倍:
同时,全局索引的添加,使得绝大多数查询做到了“本地化”,可以理解为,一个SQL只对一个DN发起请求。这样,整个系统拥有了极高的扩展上限,可以做到线性扩展,也即如果10个节点可以支撑50W的QPS,那么20个节点就可以支撑100W的QPS。
下图是业务上线后的效果,使用8个节点,在峰值达到20W QPS的情况下,依然保持着1ms的响应时间:
使用全局索引,会带来查询性能的提升,但也要注意合理使用,让它发挥更大的效果。下面给出一些最佳实践:
从单机到分布式的过程中,全局索引是非常重要的能力,也是衡量分布式数据库的重要标准。使用没有全局索引的数据库,业务将陷入无穷无尽的与多维查询斗争的局面。
PolarDB分布式版的全局索引可以很好地满足类似于游戏领域好友系统这种多维查询的需求:
“PolarDB分布式版的云原生分布式弹性能力,在解决了业务多维查询需求的同时,极大地满足了客户随时扩缩容的诉求,实现了降本增效,是游戏行业中好友关系场景下典型方案的代表。”
—— 阿里云游戏行业架构师 范建文(瑛宸)
页面更新:2024-05-02
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号