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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介: PolarDB分布式版助力《香肠派对》实现百亿好友关系20万QPS的毫秒级查询。

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

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


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


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


在TAPTAP上,《香肠派对》有2.3亿的下载量,有着庞大的玩家群体:

111.png


1. 用户关系定义

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


  1. 关注我的人->我的粉丝(fans)
  2. 我关注的人->我的关注(follow)
  3. 相互关注的人->互关(mutual)
  4. 拉黑的人->加黑(黑名单)

222.png

2. 用户关系系统架构与特征

以《香肠派对》为例,该游戏有比较强的社交属性,其好友功能提供了“关注”、“粉丝”、与推荐(找朋友)功能:

666.jpg

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

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条记录,目前整个关注表达到了百亿的量级。


这张表有以下几种访问模式:

  1. 获取某个用户的关注列表(我关注了谁)
select * from user_focus where uid=xxx;
  1. 获取某个用户的粉丝列表(谁关注了我)
select * from user_focus where focus_uid=xxx;
  1. 圈定一批人,他们关注了谁,谁又关注了他们(用于好友推荐)
select * from user_focus where uid in (xxxx);
select * from user_focus where focus_uid in (xxxx);

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


3. 问题与选型

在这个量级下,传统的单机数据库容易出现以下几类问题:


  1. 索引idx_focus_uid与idx_uid的写入均为随机写入,B+树频繁的SMO(叶子的分裂、合并等),会有各种各样的锁争抢,导致写入RT上升,CPU消耗变多等;
  2. B+树层高变高,查询代价变大等;
  3. 索引太大,Buffer Pool被打爆,产生大量的IO(本质上也是随机写入的问题)等;
  4. 表的DDL耗时过长,时间不可控等。


无论使用哪种选型,核心是将大表进行拆分。在对该表进行分布式改造时,业务团队有几种选择:

1. 使用Redis、Hbase等NoSQL数据库,这类数据库可以解决扩展性问题,但是:

  • 需要业务合理设计Key。如果将uid作为Key,那么无法按照focus_uid进行查询。只能由业务侧将数据写两份,按照uid写一份,按照focus_uid再写一份,并且由业务侧维护两份数据的一致性;
  • 改变了业务之前使用关系型数据库的习惯,需要调整大量的代码。


2. Elasticsearch、MongoDB等文档型数据库:


以ES为例,如果为了支持高性能的查询,需要设计合理的DocumentID,否则,ES中的每次查询都会涉及到所有的节点,非常低效。因此又回到了上面的问题,uid和focus_uid两个维度无法兼得。


本质上,上面几种方案,虽然数据做到了水平拆分,但几种数据库内部只能解决单维度查询,多维度的查询问题只能由应用层解决。


阿里云瑶池数据库旗下的PolarDB分布式版成为了更好的选择,在这个场景中,PolarDB分布式版有着无可比拟的优势:


  1. 最重要的一点,PolarDB分布式版支持全局索引,在对数据做了水平拆分的基础上,还能支持业务的多维度查询的需求;
  2. PolarDB分布式版兼容MySQL的语法和协议,应用从单机MySQL迁移过来无需修改代码,应用研发可以保持以前使用MySQL的思路和习惯。


4. 使用PolarDB分布式版GSI解决多维度查询

在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);

555.png

此时对于uid上的查询,自然是很高效的。为了满足focus_uid上的查询,我们只需要执行一条DDL语句,即可为表创建一个全局二级索引:

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

444.png

全局二级索引本质是一种数据冗余。例如,当执行一条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的性能也做了非常多的优化,例如:

  • 多分片的并行写入;
  • 唯一约束冲突检测下推;
  • 分布式事务的Grouping优化、一阶段提交优化等。


这里不做过多展开,有兴趣可以参考文章《PolarDB-X全局二级索引》

333.png

5. 性能效果

业务压测过程中进行了全局索引的创建,索引添加过程是Online的,不会锁表。数据库的响应时间由创建前的平均数百毫秒,下降到了创建后的1-2ms,同时TPS提升了数百倍

777.jpg

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


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


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

888.png

数据库QPS,峰值20W左右

999.png

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


6. PolarDB分布式版GSI的最佳实践

使用全局索引,会带来查询性能的提升,但也要注意合理使用,让它发挥更大的效果。下面给出一些最佳实践:

  1. 全局索引的数量不宜过多,通常一个表在2个以内。全局索引通常代表某种业务维度,例如,本例中典型的是关注和被关注,对于其他字段的查询加速,应使用本地索引。在某些场景下,我们也可以使用CO_HASH来实现多维度的查询,可参考《从淘宝订单号的秘密说起......》
  2. 在一对多的场景下,使用聚簇的全局索引,可以有效减少回表的代价。关于聚簇的全局索引,请参考:《PolarDB-X全局二级索引》
  3. 时间、日期等字段,不宜建全局索引,通常使用本地索引即可。
  4. 使用索引诊断功能(执行INSPECT INDEX即可),可以找到一些冗余、未使用的全局索引,避免不必要的空间、资源消耗,可参考:《聊聊数据库中的烂索引》《如何进行索引诊断》


7. 总结

从单机到分布式的过程中,全局索引是非常重要的能力,也是衡量分布式数据库的重要标准。使用没有全局索引的数据库,业务将陷入无穷无尽的与多维查询斗争的局面。


PolarDB分布式版的全局索引可以很好地满足类似于游戏领域好友系统这种多维查询的需求:


  1. 它是强一致的并内聚的,不需要业务通过第三方组件来实现,不需要业务去维护索引数据的一致性;
  2. 它的全局索引本身就是分区的,不需要担心全局索引本身的扩展性问题;
  3. 它上线多年,稳定可靠,有超过80%的PolarDB分布式版用户都在使用全局索引;
  4. 它能极大地减轻开发人员的学习成本,让开发人员能将精力集中在满足业务需求上,例如,可以沿用使用单机数据库时SQL优化的经验,通过加索引的手段去改善SQL的执行性能。

PolarDB分布式版云原生分布式弹性能力,在解决了业务多维查询需求的同时,极大地满足了客户随时扩缩容的诉求,实现了降本增效,是游戏行业中好友关系场景下典型方案的代表。”

—— 阿里云游戏行业架构师 范建文(瑛宸)

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
4天前
|
关系型数据库 分布式数据库 数据库
PolarDB常见问题之加了索引但是查询没有使用如何解决
PolarDB是阿里云推出的下一代关系型数据库,具有高性能、高可用性和弹性伸缩能力,适用于大规模数据处理场景。本汇总囊括了PolarDB使用中用户可能遭遇的一系列常见问题及解答,旨在为数据库管理员和开发者提供全面的问题指导,确保数据库平稳运行和优化使用体验。
|
4天前
|
存储 关系型数据库 数据库
postgresql|数据库|提升查询性能的物化视图解析
postgresql|数据库|提升查询性能的物化视图解析
197 0
|
6月前
|
消息中间件 存储 关系型数据库
PostgreSQL技术大讲堂 - 第33讲:并行查询管理
PostgreSQL从小白到专家,技术大讲堂 - 第33讲:并行查询管理
401 1
|
4天前
|
关系型数据库 MySQL 分布式数据库
PolarDB MySQL版并行查询技术探索与实践
PolarDB MySQL版并行查询技术探索与实践 PolarDB MySQL版在企业级查询加速特性上进行了深度技术探索,其中并行查询作为其重要组成部分,已经在线稳定运行多年,持续演进。本文将详细介绍并行查询的背景、挑战、方案、特性以及实践。
230 2
|
4天前
|
存储 SQL 关系型数据库
关系型数据库强大的查询功能
【5月更文挑战第9天】关系型数据库强大的查询功能
9 3
|
4天前
|
缓存 关系型数据库 数据库
关系型数据库高效查询和统计
【5月更文挑战第8天】关系型数据库高效查询和统计
21 7
|
3天前
|
存储 SQL 关系型数据库
关系型数据库结构化数据存储查询方式
【5月更文挑战第10天】关系型数据库结构化数据存储查询方式
17 2
|
4天前
|
存储 Cloud Native 关系型数据库
PolarDB-X 是面向超高并发、海量存储和复杂查询场景设计的云原生分布式数据库系统
【5月更文挑战第14天】PolarDB-X 是面向超高并发、海量存储和复杂查询场景设计的云原生分布式数据库系统
33 2
|
4天前
|
SQL 关系型数据库 数据库
SQL 42501: Postgresql查询中的权限不足错误
SQL 42501: Postgresql查询中的权限不足错误
|
4天前
|
关系型数据库 数据库 开发者
关系型数据库查询避免SELECT *
有时候你可能会遇到需要选择表中的所有列的情况,但这应该是例外而不是常态。在大多数情况下,你应该尽量避免使用 `SELECT *`。
17 1