其他分享
首页 > 其他分享> > 以“前浪微博”场景为例,谈谈架构设计流程四步曲

以“前浪微博”场景为例,谈谈架构设计流程四步曲

作者:互联网

1场景设定

让我们结合复杂度来源和架构设计原则,通过一个模拟的设计场景“前浪微博”,和你一起看看在实践中究竟如何进行架构设计。

我们假想一个创业公司,名称叫作“前浪微博”。前浪微博的业务发展很快,系统也越来越多,系统间协作的效率很低,例如:

新来的架构师在梳理这些问题时,结合自己的经验,敏锐地发现了这些问题背后的根源在于架构上各业务子系统强耦合,而消息队列系统正好可以完成子系统的解耦,于是提议要引入消息队列系统。经过一分析二讨论三开会四汇报五审批等一系列操作后,消息队列系统终于立项了。其他背景信息还有:

2架构设计第 1 步:识别复杂度

架构设计的本质目的是为了解决软件系统的复杂性,所以在设计架构时,首先就要分析系统的复杂性。

针对前浪微博的消息队列系统,采用“排查法”来分析复杂度,具体分析过程是:

1. 这个消息队列是否需要高性能

我们假设前浪微博系统用户每天发送 1000 万条微博,那么微博子系统一天会产生 1000 万条消息,我们再假设平均一条消息有 10 个子系统读取,那么其他子系统读取的消息大约是 1 亿次。

1000 万和 1 亿看起来很吓人,但对于架构师来说,关注的不是一天的数据,而是 1 秒的数据,即 TPS 和 QPS。我们将数据按照秒来计算,一天内平均每秒写入消息数为 115 条,每秒读取的消息数是 1150 条;再考虑系统的读写并不是完全平均的,设计的目标应该以峰值来计算。峰值一般取平均值的 3 倍,那么消息队列系统的 TPS 是 345,QPS 是 3450,这个量级的数据意味着并不要求高性能。

虽然根据当前业务规模计算的性能要求并不高,但业务会增长,因此系统设计需要考虑一定的性能余量。由于现在的基数较低,为了预留一定的系统容量应对后续业务的发展,我们将设计目标设定为峰值的 4 倍,因此最终的性能要求是:TPS 为 1380,QPS 为 13800。TPS 为 1380 并不高,但 QPS 为 13800 已经比较高了,因此高性能读取是复杂度之一。

注意,这里的设计目标设定为峰值的 4 倍是根据业务发展速度来预估的,不是固定为 4 倍,不同的业务可以是 2 倍,也可以是 8 倍,但一般不要设定在 10 倍以上,更不要一上来就按照 100 倍预估。

2. 这个消息队列是否需要高可用性

对于微博子系统来说,如果消息丢了,导致没有审核,然后触犯了国家法律法规,则是非常严重的事情;对于等级子系统来说,如果用户达到相应等级后,系统没有给他奖品和专属服务,则 VIP 用户会很不满意,导致用户流失从而损失收入,虽然也比较关键,但没有审核子系统丢消息那么严重。

综合来看,消息队列需要高可用性,包括消息写入、消息存储、消息读取都需要保证高可用性。

3. 这个消息队列是否需要高可扩展性

消息队列的功能很明确,基本无须扩展,因此可扩展性不是这个消息队列的复杂度关键。

为了方便理解,这里我只排查“高性能”“高可用”“扩展性”这 3 个复杂度,在实际应用中,不同的公司或者团队,可能还有一些其他方面的复杂度分析。例如,金融系统可能需要考虑安全性,有的公司会考虑成本等。

综合分析下来,消息队列的复杂性主要体现在这几个方面:高性能消息读取、高可用消息写入、高可用消息存储、高可用消息读取。

3架构设计第 2 步:设计备选方案

确定了系统面临的主要复杂度问题后,方案设计就有了明确的目标,我们就可以开始真正进行架构方案设计了。

1. 备选方案 1:采用开源的 Kafka

Kafka 是成熟的开源消息队列方案,功能强大,性能非常高,而且已经比较成熟,很多大公司都在使用。

2. 备选方案 2:集群 + MySQL 存储

首先考虑单服务器高性能。高性能消息读取属于“计算高可用”的范畴,单服务器高性能备选方案有很多种。考虑到团队的开发语言是 Java,虽然有人觉得 C/C++ 语言更加适合写高性能的中间件系统,但架构师综合来看,认为无须为了语言的性能优势而让整个团队切换语言,消息队列系统继续用 Java 开发。由于 Netty 是 Java 领域成熟的高性能网络库,因此架构师选择基于 Netty 开发消息队列系统。

由于系统设计的 QPS 是 13800,即使单机采用 Netty 来构建高性能系统,单台服务器支撑这么高的 QPS 还是有很大风险的,因此架构师选择采取集群方式来满足高性能消息读取,集群的负载均衡算法采用简单的轮询即可。

同理,“高可用写入”和“高性能读取”一样,可以采取集群的方式来满足。因为消息只要写入集群中一台服务器就算成功写入,因此“高可用写入”的集群分配算法和“高性能读取”也一样采用轮询,即正常情况下,客户端将消息依次写入不同的服务器;某台服务器异常的情况下,客户端直接将消息写入下一台正常的服务器即可。

整个系统中最复杂的是“高可用存储”和“高可用读取”,“高可用存储”要求已经写入的消息在单台服务器宕机的情况下不丢失;“高可用读取”要求已经写入的消息在单台服务器宕机的情况下可以继续读取。架构师第一时间想到的就是可以利用 MySQL 的主备复制功能来达到“高可用存储“的目的,通过服务器的主备方案来达到“高可用读取”的目的。

具体方案:

图片

简单描述一下方案:

3. 备选方案 3:集群 + 自研存储方案

在备选方案 2 的基础上,将 MySQL 存储替换为自研实现存储方案,因为 MySQL 的关系型数据库的特点并不是很契合消息队列的数据特点,参考 Kafka 的做法,可以自己实现一套文件存储和复制方案(此处省略具体的方案描述,实际设计时需要给出方案)。

可以看出,高性能消息读取单机系统设计这部分时并没有多个备选方案可选,备选方案 2 和备选方案 3 都采取基于 Netty 的网络库,用 Java 语言开发,原因就在于团队的 Java 背景约束了备选的范围。通常情况下,成熟的团队不会轻易改变技术栈,反而是新成立的技术团队更加倾向于采用新技术。

上面简单地给出了 3 个备选方案用来示范如何操作,实践中要比上述方案复杂一些。架构师的技术储备越丰富、经验越多,备选方案也会更多,从而才能更好地设计备选方案。例如,开源方案选择可能就包括 Kafka、ActiveMQ、RabbitMQ;集群方案的存储既可以考虑用 MySQL,也可以考虑用 HBase,还可以考虑用 Redis 与 MySQL 结合等;自研文件系统也可以有多个,可以参考 Kafka,也可以参考 LevelDB,还可以参考 HBase 等。限于篇幅,这里就不一一展开了。

4架构设计第 3 步:评估和选择备选方案

在完成备选方案设计后,如何挑选出最终的方案也是一个很大的挑战。有时候我们要挑选最简单的方案,有时候要挑选最优秀的方案,有时候要挑选最熟悉的方案,甚至有时候真的要领导拍板。因此关键问题是:这里的“有时候”到底应该怎么判断?

我的答案就是 “360 度环评”! 具体的操作方式为:列出我们需要关注的质量属性点,然后分别从这些质量属性的维度去评估每个方案,再综合挑选适合当时情况的最优方案。

针对上期提出的 3 个备选方案,架构师组织了备选方案评审会议,参加的人有研发、测试、运维、还有几个核心业务的主管。

1. 备选方案 1:采用开源 Kafka 方案

2. 备选方案 2:集群 + MySQL 存储

3. 备选方案 3:集群 + 自研存储系统

针对 3 个备选方案的讨论初步完成后,架构师列出了 3 个方案的 360 度环评表:

图片

列出这个表格后,无法一眼看出具体哪个方案更合适,于是大家都把目光投向架构师,决策的压力现在集中在架构师身上了。

架构师经过思考后,给出了最终选择备选方案 2,原因有:

针对备选方案 2 的缺点,架构师解释是:

最后,大家针对一些细节再次讨论后,确定了选择备选方案 2。

通过“前浪微博”这个案例我们可以看出,备选方案的选择和很多因素相关,并不单单考虑性能高低、技术是否优越这些纯技术因素。业务的需求特点、运维团队的经验、已有的技术体系、团队人员的技术水平都会影响备选方案的选择。因此,同样是上述 3 个备选方案,有的团队会选择引入 Kafka,有的会选择自研存储系统。

5架构设计第 4 步:详细方案设计

完成备选方案的设计和选择后,我们终于可以长出一口气,因为整个架构设计最难的一步已经完成了,但整体方案尚未完成,架构师还需继续努力。接下来我们需要再接再励,将最终确定的备选方案进行细化,使得备选方案变成一个可以落地的设计方案。

虽然我们在“前浪微博”消息队列的架构设计挑选了备选方案 2 作为最终方案,但备选方案设计阶段的方案粒度还比较粗,无法真正指导开发人员进行后续的设计和开发,因此需要在备选方案的基础上进一步细化。

下面我列出一些备选方案 2 典型的需要细化的点供参考,有兴趣的同学可以自己尝试细化更多的设计点。

1. 细化设计点 1:数据库表如何设计?

2. 细化设计点 2:数据如何复制?

直接采用 MySQL 主从复制即可,只复制消息存储表,不复制日志表。

3. 细化设计点 3:主备服务器如何倒换?

采用 ZooKeeper 来做主备决策,主备服务器都连接到 ZooKeeper 建立自己的节点,主服务器的路径规则为“/MQ/server/ 分区编号 /master”,备机为“/MQ/server/ 分区编号 /slave”,节点类型为 EPHEMERAL。

备机监听主机的节点消息,当发现主服务器节点断连后,备服务器修改自己的状态,对外提供消息读取服务。

4. 细化设计点 4:业务服务器如何写入消息?

5. 细化设计点 5:业务服务器如何读取消息?

6. 细化设计点 6:业务服务器和消息队列服务器之间的通信协议如何设计?


考虑到消息队列系统后续可能会对接多种不同编程语言编写的系统,为了提升兼容性,传输协议用 TCP,数据格式为 ProtocolBuffer。

当然还有更多设计细节就不再一一列举,因此这还不是一个完整的设计方案,我希望可以通过这些具体实例来说明细化方案具体如何去做。


标签:架构设计,读取,方案,队列,四步曲,前浪,消息,服务器,备选
来源: https://blog.51cto.com/15057858/2691751