以“前浪微博”场景为例,谈谈架构设计流程四步曲(一)-英拿科技
作者|李运华编辑|何潇来点实战,不谈虚的。1场景设定
让我们结合复杂度来源和架构设计原则,通过一个模拟的设计场景“前浪微博”,和你一起看看在实践中究竟如何进行架构设计。
我们假想一个创业公司,名称叫作“前浪微博”。前浪微博的业务发展很快,系统也越来越多,系统间协作的效率很低,例如:
用户发一条微博后,微博子系统需要通知审核子系统进行审核,然后通知统计子系统进行统计,再通知广告子系统进行广告预测,接着通知消息子系统进行消息推送……一条微博有十几个通知,目前都是系统间通过接口调用的。每通知一个新系统广粤尊府,微博子系统就要设计接口、进行测试,效率很低,问题定位很麻烦花雨伞官网,经常和其他子系统的技术人员产生分岐,微博子系统的开发人员不胜其烦。
用户等级达到 VIP 后,等级子系统要通知福利子系统进行奖品发放,要通知客服子系统安排专属服务人员,要通知商品子系统进行商品打折处理……等级子系统的开发人员也是不胜其烦。
新来的架构师在梳理这些问题时,结合自己的经验,敏锐地发现了这些问题背后的根源在于架构上各业务子系统强耦合,而消息队列系统正好可以完成子系统的解耦,于是提议要引入消息队列系统。经过一分析二讨论三开会四汇报五审批等一系列操作后,消息队列系统终于立项了。其他背景信息还有:
中间件团队规模不大,大约 6 人左右。
中间件团队熟悉 Java 语言,但有一个新同事 C/C++ 很牛。
开发平台是 Linux,数据库是 MySQL。
目前整个业务系统是单机房部署,没有双机房。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 的主备复制功能来达到“高可用存储“的目的,通过服务器的主备方案来达到“高可用读取”的目的。
具体方案:

简单描述一下方案:
采用数据分散集群的架构,集群中的服务器进行分组,每个分组存储一部分消息数据。
每个分组包含一台主 MySQL 和一台备 MySQL,分组内主备数据复制,分组间数据不同步。
正常情况下,分组内的主服务器对外提供消息写入和消息读取服务,备服务器不对外提供服务;主服务器宕机的情况下,备服务器对外提供消息读取的服务。
客户端采取轮询的策略写入和读取消息。3. 备选方案 3:集群 + 自研存储方案
在备选方案 2 的基础上,将 MySQL 存储替换为自研实现存储方案,因为 MySQL 的关系型数据库的特点并不是很契合消息队列的数据特点,参考 Kafka 的做法,可以自己实现一套文件存储和复制方案(此处省略具体的方案描述,实际设计时需要给出方案)。
可以看出,高性能消息读取单机系统设计这部分时并没有多个备选方案可选,备选方案 2 和备选方案 3 都采取基于 Netty 的网络库,用 Java 语言开发,原因就在于团队的 Java 背景约束了备选的范围。通常情况下,成熟的团队不会轻易改变技术栈,反而是新成立的技术团队更加倾向于采用新技术。
上面简单地给出了 3 个备选方案用来示范如何操作,实践中要比上述方案复杂一些。架构师的技术储备越丰富、经验越多,备选方案也会更多,从而才能更好地设计备选方案。例如,开源方案选择可能就包括 Kafka、ActiveMQ、RabbitMQ;集群方案的存储既可以考虑用 MySQL,也可以考虑用 HBase,还可以考虑用 Redis 与 MySQL 结合等;自研文件系统也可以有多个,可以参考 Kafka,也可以参考 LevelDB,还可以参考 HBase 等。限于篇幅,这里就不一一展开了。
(返回可以查看第二部分)