需求
实现一个消息传递应用系统,支持用户点对点交互文本消息。暂不考虑群组功能和其他类型的文件传输。
日均消息发送量:100亿(10billion) 10,000,000,000,一年内翻倍
tps:100亿/3600/24≈1,157,40≈10w/s
峰值tps:50w/s
服务器qps:10000/s
需要服务器数量:50w/10000=500,000/10,000=50台
平均消息大小:100 B
日均消息量:100 B* 100亿 = 100 * 10,000,000,000 = 1 TB/day
年消息累积量以PB为单位
延时需求:90%~95%的消息延迟为几百ms,需要确保数据传输的最终一致性
顶层设计
初步设计
相关组件:客户端设备+负载均衡器+API服务器*50
*若考虑群组情况,消息接收的API调用会远高于消息发送的API调用,应使用100台服务器
弹性自动缩放服务器数量以适应一年后可能翻倍的日均消息发送量, 例如使用云服务平台(如AWS、Azure或GCP)时通过配置自动扩展规则,当CPU使用率超过80%时,自动增加一个实例;当使用率低于20%时,自动减少一个实例。
API设计
发送消息(send message)
url:<domain>/message/v1
method:POST
请求参数:sender_id、recipient_id、text
查看消息(check message)
url:<domain>/message/v1
method:GET
请求响应:消息id的列表
阅读消息(read message)
url:<domain>/message/v1/?message_id=
标记消息:标记为已读
架构设计
数据库设计
sql vs nosql:sql垂直扩展(Scale Up),通过提升单台服务器的硬件性能来扩展系统,比如增加更多的CPU、内存或存储容量,受到硬件的限制;nosql水平扩展(Scale Out),通过增加更多的服务器来分担负载,可以更轻松地应对大规模的数据量和高吞吐量。
解决方案:AWS DynamoDB(上云托管、首选)、Cassandra、MongoDB
AWS DynamoDB:Amazon Web Services(AWS)提供的一个完全托管的 NoSQL 数据库服务,可以在全球范围内自动扩展,并且无需用户管理底层的硬件、服务器或数据库软件。
使用一致性哈希划分数据空间,提供负载均衡算法的容错和良好的扩展性。
DynamoDB 提供了 分区键(Partition Key)和范围键(Sort Key)。根据分区键的哈希值决定数据存储在哪个分区,然后在该分区内使用范围键来排序和区分数据项。每个分区键下可以存储多个项目,每个项目通过范围键来区分。同一个分区键下的项目,范围键值可以重复,但是 分区键 + 范围键 的组合必须唯一。
Messages表:sender_id、message_id(范围键)、timestamp、status、recipend_id/group_id
Users表:sender_id、metadata(sex、phone、name、online)、send_message_ids、read_message_ids、unread_message_ids
架构图
流程描述
用户A发起发送消息给用户B的请求。数据库中message 表新增一条记录,包含消息内容、发送者ID(用户A)、接收者ID(用户B)、发送时间等信息,消息状态初始设置为未读(status='unread'),表示它尚未被接收者用户B查看。消息分发器周期性地轮询数据库中的消息状态。对于每条未读消息,分发器会将其添加到接收者(用户B)User 表中的 unread_message_ids 字段,并依据用户B的状态(在线、离线、静音等)立即通知接收方是否需要推送通知。
当用户B发起查看消息的请求,要求立即获取未读消息。服务器根据用户B的unread_message_ids 字段查询所有未读消息。用户B查看消息后,系统更新这条消息的状态,在数据库中将 message.status 更新为“已读“,并从 unread_message_ids 字段中删除相应的消息ID。
关于实时推送的想法:消息发送后,分发器通过事件驱动立即检查目标用户的在线状态。而不是等到下一次轮询。如果用户B在线,消息立即推送到B的设备。如果B离线,则通过轮询推送。
瓶颈
随着消息量的不断增大,每次消息分发器的轮询时间会变长,导致消息延迟。
→分表(已读消息表和未读消息表)