网课《从零开始学架构》摘录

3 分钟阅读

innodb_buffer_pool_size、sync_binlog、innodb_log_file_size

PPC 与 TPC、Reactor 与 Proactor

幸运的是,最近我在学习的时候,无意中在网络上找到一份非常详尽的关于 Linux 服务器网络模型的详细系列文章。作者通过连载的方式,将 iterative、forking(对应专栏的 PPC 模式)、preforked(对应专栏的 prefork 模式)、threaded(对应专栏的 TPC 模式)、prethreaded(对应专栏的 prethread 模式)、poll、epoll(对应专栏的 Reactor 模式)共 7 种模式的实现原理、实现代码、性能对比都详尽地进行了阐述,完美地弥补了专栏内容没有实际数据对比的遗憾。

Linux Applications Performance: Introduction

一文读懂「中台」的前世今生

从 0 开始学架构

25 | 高可用存储架构:双机架构

存储高可用方案的本质都是通过将数据复制到多个存储设备,通过数据冗余的方式来实现高可用,其复杂性主要体现在如何应对复制延迟和中断导致的数据不一致问题。 因此,对任何一个高可用存储方案,我们需要从以下几个方面去进行思考和分析:

  • 数据如何复制?
  • 各个节点的职责是什么?
  • 如何应对复制延迟?
  • 如何应对复制中断?

双机解决方案: 主备、主从、主备 / 主从切换和主主。

主备复制

主备复制是最常见也是最简单的一种存储高可用方案,几乎所有的存储系统都提供了主备复制的功能,例如 MySQL、Redis、MongoDB 等。

下面是标准的主备方案结构图:

主备复制

其整体架构比较简单,主备架构中的“备机”主要还是起到一个备份作用,并不承担实际的业务读写操作,如果要把备机改为主机,需要人工操作。

优缺点分析

主备复制架构的优点就是简单,表现有:

  • 对于客户端来说,不需要感知备机的存在,即使灾难恢复后,原来的备机被人工修改为主机后,对于客户端来说,只是认为主机的地址换了而已,无须知道是原来的备机升级为主机。
  • 对于主机和备机来说,双方只需要进行数据复制即可,无须进行状态判断和主备切换这类复杂的操作。

主备复制架构的缺点主要有:

  • 备机仅仅只为备份,并没有提供读写操作,硬件成本上有浪费。
  • 故障后需要人工干预,无法自动恢复。人工处理的效率是很低的,可能打电话找到能够操作的人就耗费了 10 分钟,甚至如果是深更半夜,出了故障都没人知道。人工在执行恢复操作的过程中也容易出错,因为这类操作并不常见,可能 1 年就 2、3 次,实际操作的时候很可能遇到各种意想不到的问题。

综合主备复制架构的优缺点,内部的后台管理系统使用主备复制架构的情况会比较多,例如学生管理系统、员工管理系统、假期管理系统等,因为这类系统的数据变更频率低,即使在某些场景下丢失数据,也可以通过人工的方式补全。

主从复制

主从复制和主备复制只有一字之差,“从”意思是“随从、仆从”,“备”的意思是备份。我们可以理解为仆从是要帮主人干活的,这里的干活就是承担“读”的操作。也就是说,主机负责读写操作,从机只负责读操作,不负责写操作。

主从复制

优缺点分析

主从复制与主备复制相比,优点有:

  • 主从复制在主机故障时,读操作相关的业务可以继续运行。
  • 主从复制架构的从机提供读操作,发挥了硬件的性能。

缺点有:

  • 主从复制架构中,客户端需要感知主从关系,并将不同的操作发给不同的机器进行处理,复杂度比主备复制要高。
  • 主从复制架构中,从机提供读业务,如果主从复制延迟比较大,业务会因为数据不一致出现问题。
  • 故障时需要人工干预。

综合主从复制的优缺点,一般情况下,写少读多的业务使用主从复制的存储架构比较多。例如,论坛、BBS、新闻网站这类业务,此类业务的读操作数量是写操作数量的 10 倍甚至 100 倍以上。

双机切换

  1. 设计关键 主备复制和主从复制方案存在两个共性的问题:
  • 主机故障后,无法进行写操作。
  • 如果主机无法恢复,需要人工指定新的主机角色。

双机切换就是为了解决这两个问题而产生的,包括主备切换和主从切换两种方案。 简单来说,这两个方案就是在原有方案的基础上增加“切换”功能,即系统自动决定主机角色,并完成角色切换。 由于主备切换和主从切换在切换的设计上没有差别,我接下来以主备切换为例,一起来看看双机切换架构是如何实现的。 要实现一个完善的切换方案,必须考虑这几个关键的设计点:

主备间状态判断

主要包括两方面:状态传递的渠道,以及状态检测的内容。

状态传递的渠道:是相互间互相连接,还是第三方仲裁?

状态检测的内容:例如机器是否掉电、进程是否存在、响应是否缓慢等。

切换决策

主要包括几方面:切换时机、切换策略、自动程度。

切换时机:什么情况下备机应该升级为主机?是机器掉电后备机才升级,还是主机上的进程不存在就升级,还是主机响应时间超过 2 秒就升级,还是 3 分钟内主机连续重启 3 次就升级等。

切换策略:原来的主机故障恢复后,要再次切换,确保原来的主机继续做主机,还是原来的主机故障恢复后自动成为新的备机?

自动程度:切换是完全自动的,还是半自动的?例如,系统判断当前需要切换,但需要人工做最终的确认操作(例如,单击一下“切换”按钮)。

数据冲突解决

当原有故障的主机恢复后,新旧主机之间可能存在数据冲突。

例如,用户在旧主机上新增了一条 ID 为 100 的数据,这个数据还没有复制到旧的备机,此时发生了切换,旧的备机升级为新的主机,用户又在新的主机上新增了一条 ID 为 100 的数据,当旧的故障主机恢复后,这两条 ID 都为 100 的数据,应该怎么处理?

以上设计点并没有放之四海而皆准的答案,不同的业务要求不一样,所以切换方案比复制方案不只是多了一个切换功能那么简单,而是复杂度上升了一个量级。形象点来说,如果复制方案的代码是 1000 行,那么切换方案的代码可能就是 10000 行,多出来的那 9000 行就是用于实现上面我所讲的 3 个设计点的。

常见架构

根据状态传递渠道的不同,常见的主备切换架构有三种形式:互连式、中介式和模拟式。

互连式

双机切换互联式

你可以看到,在主备复制的架构基础上,主机和备机多了一个“状态传递”的通道,这个通道就是用来传递状态信息的。这个通道的具体实现可以有很多方式:

  • 可以是网络连接(例如,各开一个端口),也可以是非网络连接(用串口线连接)。
  • 可以是主机发送状态给备机,也可以是备机到主机来获取状态信息。
  • 可以和数据复制通道共用,也可以独立一条通道。
  • 状态传递通道可以是一条,也可以是多条,还可以是不同类型的通道混合(例如,网络 + 串口)。

为了充分利用切换方案能够自动决定主机这个优势,客户端这里也会有一些相应的改变,常见的方式有:

  • 为了切换后不影响客户端的访问,主机和备机之间共享一个对客户端来说唯一的地址。例如虚拟 IP,主机需要绑定这个虚拟的 IP。
  • 客户端同时记录主备机的地址,哪个能访问就访问哪个;备机虽然能收到客户端的操作请求,但是会直接拒绝,拒绝的原因就是“备机不对外提供服务”。

互连式主备切换主要的缺点在于:

  • 如果状态传递的通道本身有故障(例如,网线被人不小心踢掉了),那么备机也会认为主机故障了从而将自己升级为主机,而此时主机并没有故障,最终就可能出现两个主机。
  • 虽然可以通过增加多个通道来增强状态传递的可靠性,但这样做只是降低了通道故障概率而已,不能从根本上解决这个缺点,而且通道越多,后续的状态决策会更加复杂,因为对备机来说,可能从不同的通道收到了不同甚至矛盾的状态信息。

中介式

中介式指的是在主备两者之外引入第三方中介,主备机之间不直接连接,而都去连接中介,并且通过中介来传递状态信息,其架构图如下:

双机切换中介式

连接管理更简单:主备机无须再建立和管理多种类型的状态传递连接通道,只要连接到中介即可,实际上是降低了主备机的连接管理复杂度。

例如,互连式要求主机开一个监听端口,备机来获取状态信息;或者要求备机开一个监听端口,主机推送状态信息到备机;如果还采用了串口连接,则需要增加串口连接管理和数据读取。采用中介式后,主备机都只需要把状态信息发送给中介,或者从中介获取对方的状态信息。无论是发送还是获取,主备机都是作为中介的客户端去操作,复杂度会降低。

状态决策更简单:主备机的状态决策简单了,无须考虑多种类型的连接通道获取的状态信息如何决策的问题,只需要按照下面简单的算法即可完成状态决策。

  • 无论是主机还是备机,初始状态都是备机,并且只要与中介断开连接,就将自己降级为备机,因此可能出现双备机的情况。
  • 主机与中介断连后,中介能够立刻告知备机,备机将自己升级为主机。
  • 如果是网络中断导致主机与中介断连,主机自己会降级为备机,网络恢复后,旧的主机以新的备机身份向中介上报自己的状态。
  • 如果是掉电重启或者进程重启,旧的主机初始状态为备机,与中介恢复连接后,发现已经有主机了,保持自己备机状态不变。
  • 主备机与中介连接都正常的情况下,按照实际的状态决定是否进行切换。例如,主机响应时间超过 3 秒就进行切换,主机降级为备机,备机升级为主机即可。

虽然中介式架构在状态传递和状态决策上更加简单,但并不意味着这种优点是没有代价的,其关键代价就在于如何实现中介本身的高可用。如果中介自己宕机了,整个系统就进入了双备的状态,写操作相关的业务就不可用了。这就陷入了一个递归的陷阱:为了实现高可用,我们引入中介,但中介本身又要求高可用,于是又要设计中介的高可用方案……如此递归下去就无穷无尽了。

幸运的是,开源方案已经有比较成熟的中介式解决方案,例如 ZooKeeper 和 Keepalived。ZooKeeper 本身已经实现了高可用集群架构,因此已经帮我们解决了中介本身的可靠性问题,在工程实践中推荐基于 ZooKeeper 搭建中介式切换架构。

模拟式

模拟式指主备机之间并不传递任何状态数据,而是备机模拟成一个客户端,向主机发起模拟的读写操作,根据读写操作的响应情况来判断主机的状态。其基本架构如下:

双机切换模拟式

模拟式切换与互连式切换相比,优点是实现更加简单,因为省去了状态传递通道的建立和管理工作。

简单既是优点,同时也是缺点。因为模拟式读写操作获取的状态信息只有响应信息(例如,HTTP 404,超时、响应时间超过 3 秒等),没有互连式那样多样(除了响应信息,还可以包含 CPU 负载、I/O 负载、吞吐量、响应时间等),基于有限的状态来做状态决策,可能出现偏差。

主主复制

主主复制指的是两台机器都是主机,互相将数据复制给对方,客户端可以任意挑选其中一台机器进行读写操作,下面是基本架构图。

相比主备切换架构,主主复制架构具有如下特点:

  • 两台都是主机,不存在切换的概念。
  • 客户端无须区分不同角色的主机,随便将读写操作发送给哪台主机都可以。

从上面的描述来看,主主复制架构从总体上来看要简单很多,无须状态信息传递,也无须状态决策和状态切换。然而事实上主主复制架构也并不简单,而是有其独特的复杂性,具体表现在:如果采取主主复制架构,必须保证数据能够双向复制,而很多数据是不能双向复制的。

  • 用户注册后生成的用户 ID,如果按照数字增长,那就不能双向复制,否则就会出现 X 用户在主机 A 注册,分配的用户 ID 是 100,同时 Y 用户在主机 B 注册,分配的用户 ID 也是 100,这就出现了冲突。
  • 库存不能双向复制。例如,一件商品库存 100 件,主机 A 上减了 1 件变成 99,主机 B 上减了 2 件变成 98,然后主机 A 将库存 99 复制到主机 B,主机 B 原有的库存 98 被覆盖,变成了 99,而实际上此时真正的库存是 97。类似的还有余额数据。

因此,主主复制架构对数据的设计有严格的要求,一般适合于那些临时性、可丢失、可覆盖的数据场景。例如,用户登录产生的 session 数据(可以重新登录生成)、用户行为的日志数据(可以丢失)、论坛的草稿数据(可以丢失)等。

27 | 如何设计计算高可用架构?

计算高可用的主要设计目标是当出现部分硬件损坏时,计算任务能够继续正常运行。因此计算高可用的本质是通过冗余来规避部分故障的风险,单台服务器是无论如何都达不到这个目标的。所以计算高可用的设计思想很简单:通过增加更多服务器来达到计算高可用。

计算高可用架构的设计复杂度主要体现在任务管理方面,即当任务在某台服务器上执行失败后,如何将任务重新分配到新的服务器进行执行。因此,计算高可用架构设计的关键点有下面两点。

  1. 哪些服务器可以执行任务

第一种方式和计算高性能中的集群类似,每个服务器都可以执行任务。例如,常见的访问网站的某个页面。

第二种方式和存储高可用中的集群类似,只有特定服务器(通常叫“主机”)可以执行任务。当执行任务的服务器故障后,系统需要挑选新的服务器来执行任务。例如,ZooKeeper 的 Leader 才能处理写操作请求。

  1. 任务如何重新执行

第一种策略是对于已经分配的任务即使执行失败也不做任何处理,系统只需要保证新的任务能够分配到其他非故障服务器上执行即可。

第二种策略是设计一个任务管理器来管理需要执行的计算任务,服务器执行完任务后,需要向任务管理器反馈任务执行结果,任务管理器根据任务执行结果来决定是否需要将任务重新分配到另外的服务器上执行。

需要注意的是:“任务分配器”是一个逻辑的概念,并不一定要求系统存在一个独立的任务分配器模块。例如:

  • Nginx 将页面请求发送给 Web 服务器,而 CSS/JS 等静态文件直接读取本地缓存。这里的 Nginx 角色是反向代理系统,但是承担了任务分配器的职责,而不需要 Nginx 做反向代理,后面再来一个任务分配器。

  • 对于一些后台批量运算的任务,可以设计一个独立的任务分配系统来管理这些批处理任务的执行和分配。

  • ZooKeeper 中的 Follower 节点,当接收到写请求时会将请求转发给 Leader 节点处理,当接收到读请求时就自己处理,这里的 Follower 就相当于一个逻辑上的任务分配器。

常见的计算高可用架构:主备、主从和集群

主备

主备架构是计算高可用最简单的架构,和存储高可用的主备复制架构类似,但是要更简单一些,因为计算高可用的主备架构无须数据复制,其基本的架构示意图如下:

高可用主备

主备方案的详细设计:

主机执行所有计算任务。例如,读写数据、执行操作等。

  • 当主机故障(例如,主机宕机)时,任务分配器不会自动将计算任务发送给备机,此时系统处于不可用状态。
  • 如果主机能够恢复(不管是人工恢复还是自动恢复),任务分配器继续将任务发送给主机。
  • 如果主机不能够恢复(例如,机器硬盘损坏,短时间内无法恢复),则需要人工操作,将备机升为主机,然后让任务分配器将任务发送给新的主机(即原来的备机);同时,为了继续保持主备架构,需要人工增加新的机器作为备机。

根据备机状态的不同,主备架构又可以细分为冷备架构和温备架构。

  • 冷备:备机上的程序包和配置文件都准备好,但备机上的业务系统没有启动(注意:备机的服务器是启动的),主机故障后,需要人工手工将备机的业务系统启动,并将任务分配器的任务请求切换发送给备机。
  • 温备:备机上的业务系统已经启动,只是不对外提供服务,主机故障后,人工只需要将任务分配器的任务请求切换发送到备机即可。冷备可以节省一定的能源,但温备能够大大减少手工操作时间,因此一般情况下推荐用温备的方式。

主备架构的优点就是简单,主备机之间不需要进行交互,状态判断和切换操作由人工执行,系统实现很简单。而缺点正好也体现在“人工操作”这点上,因为人工操作的时间不可控,可能系统已经发生问题了,但维护人员还没发现,等了 1 个小时才发现。发现后人工切换的操作效率也比较低,可能需要半个小时才完成切换操作,而且手工操作过程中容易出错。例如,修改配置文件改错了、启动了错误的程序等。 和存储高可用中的主备复制架构类似,计算高可用的主备架构也比较适合与内部管理系统、后台管理系统这类使用人数不多、使用频率不高的业务,不太适合在线的业务。

主从

和存储高可用中的主从复制架构类似,计算高可用的主从架构中的从机也是要执行任务的。任务分配器需要将任务进行分类,确定哪些任务可以发送给主机执行,哪些任务可以发送给备机执行,其基本的架构示意图如下:

高可用主从

主从方案详细设计:

  • 正常情况下,主机执行部分计算任务(如图中的“计算任务 A”),备机执行部分计算任务(如图中的“计算任务 B”)。
  • 当主机故障(例如,主机宕机)时,任务分配器不会自动将原本发送给主机的任务发送给从机,而是继续发送给主机,不管这些任务执行是否成功。
  • 如果主机能够恢复(不管是人工恢复还是自动恢复),任务分配器继续按照原有的设计策略分配任务,即计算任务 A 发送给主机,计算任务 B 发送给从机。
  • 如果主机不能够恢复(例如,机器硬盘损坏,短时间内无法恢复),则需要人工操作,将原来的从机升级为主机(一般只是修改配置即可),增加新的机器作为从机,新的从机准备就绪后,任务分配器继续按照原有的设计策略分配任务。

主从架构与主备架构相比,优缺点有:

  • 优点:主从架构的从机也执行任务,发挥了从机的硬件性能。
  • 缺点:主从架构需要将任务分类,任务分配器会复杂一些。

31 | 如何应对接口级的故障?

解决接口级故障的核心思想:优先保证核心业务和优先保证绝大部分用户。

降级

降级指系统将某些业务或者接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能。例如,论坛可以降级为只能看帖子,不能发帖子;也可以降级为只能看帖子和评论,不能发评论;而 App 的日志上传接口,可以完全停掉一段时间,这段时间内 App 都不能上传日志。

降级的核心思想就是丢车保帅,优先保证核心业务。例如,对于论坛来说,90% 的流量是看帖子,那我们就优先保证看帖的功能;对于一个 App 来说,日志上传接口只是一个辅助的功能,故障时完全可以停掉。

常见的实现降级的方式有:

  • 系统后门降级

简单来说,就是系统预留了后门用于降级操作。例如,系统提供一个降级 URL,当访问这个 URL 时,就相当于执行降级指令,具体的降级指令通过 URL 的参数传入即可。这种方案有一定的安全隐患,所以也会在 URL 中加入密码这类安全措施。

系统后门降级的方式实现成本低,但主要缺点是如果服务器数量多,需要一台一台去操作,效率比较低,这在故障处理争分夺秒的场景下是比较浪费时间的。

  • 独立降级系统

为了解决系统后门降级方式的缺点,将降级操作独立到一个单独的系统中,可以实现复杂的权限管理、批量操作等功能。其基本架构如下:

降级

熔断

熔断和降级是两个比较容易混淆的概念,因为单纯从名字上看好像都有禁止某个功能的意思,但其实内在含义是不同的,原因在于降级的目的是应对系统自身的故障,而熔断的目的是应对依赖的外部系统故障的情况。

假设一个这样的场景:A 服务的 X 功能依赖 B 服务的某个接口,当 B 服务的接口响应很慢的时候,A 服务的 X 功能响应肯定也会被拖慢,进一步导致 A 服务的线程都被卡在 X 功能处理上,此时 A 服务的其他功能都会被卡住或者响应非常慢。这时就需要熔断机制了,即:A 服务不再请求 B 服务的这个接口,A 服务内部只要发现是请求 B 服务的这个接口就立即返回错误,从而避免 A 服务整个被拖慢甚至拖死。

熔断机制实现的关键是需要有一个统一的 API 调用层,由 API 调用层来进行采样或者统计,如果接口调用散落在代码各处就没法进行统一处理了。

熔断机制实现的另外一个关键是阈值的设计,例如 1 分钟内 30% 的请求响应时间超过 1 秒就熔断,这个策略中的“1 分钟”“30%”“1 秒”都对最终的熔断效果有影响。实践中一般都是先根据分析确定阈值,然后上线观察效果,再进行调优。

限流

降级是从系统功能优先级的角度考虑如何应对故障,而限流则是从用户访问压力的角度来考虑如何应对故障。限流指只允许系统能够承受的访问量进来,超出系统访问能力的请求将被丢弃。

限流一般都是系统内实现的,常见的限流方式可以分为两类:基于请求限流和基于资源限流。

  • 基于请求限流

基于请求限流指从外部访问的请求角度考虑限流,常见的方式有:限制总量、限制时间量。

限制总量的方式是限制某个指标的累积上限,常见的是限制当前系统服务的用户总量,例如某个直播间限制总用户数上限为 100 万,超过 100 万后新的用户无法进入;某个抢购活动商品数量只有 100 个,限制参与抢购的用户上限为 1 万个,1 万以后的用户直接拒绝。限制时间量指限制一段时间内某个指标的上限,例如,1 分钟内只允许 10000 个用户访问,每秒请求峰值最高为 10 万。

无论是限制总量还是限制时间量,共同的特点都是实现简单,但在实践中面临的主要问题是比较难以找到合适的阈值,例如系统设定了 1 分钟 10000 个用户,但实际上 6000 个用户的时候系统就扛不住了;也可能达到 1 分钟 10000 用户后,其实系统压力还不大,但此时已经开始丢弃用户访问了。

即使找到了合适的阈值,基于请求限流还面临硬件相关的问题。例如一台 32 核的机器和 64 核的机器处理能力差别很大,阈值是不同的,可能有的技术人员以为简单根据硬件指标进行数学运算就可以得出来,实际上这样是不可行的,64 核的机器比 32 核的机器,业务处理性能并不是 2 倍的关系,可能是 1.5 倍,甚至可能是 1.1 倍。

为了找到合理的阈值,通常情况下可以采用性能压测来确定阈值,但性能压测也存在覆盖场景有限的问题,可能出现某个性能压测没有覆盖的功能导致系统压力很大;另外一种方式是逐步优化,即:先设定一个阈值然后上线观察运行情况,发现不合理就调整阈值。

基于上述的分析,根据阈值来限制访问量的方式更多的适应于业务功能比较简单的系统,例如负载均衡系统、网关系统、抢购系统等。

  • 基于资源限流

基于请求限流是从系统外部考虑的,而基于资源限流是从系统内部考虑的,即:找到系统内部影响性能的关键资源,对其使用上限进行限制。常见的内部资源有:连接数、文件句柄、线程数、请求队列等。

例如,采用 Netty 来实现服务器,每个进来的请求都先放入一个队列,业务线程再从队列读取请求进行处理,队列长度最大值为 10000,队列满了就拒绝后面的请求;也可以根据 CPU 的负载或者占用率进行限流,当 CPU 的占用率超过 80% 的时候就开始拒绝新的请求。

基于资源限流相比基于请求限流能够更加有效地反映当前系统的压力,但实践中设计也面临两个主要的难点:如何确定关键资源,如何确定关键资源的阈值。通常情况下,这也是一个逐步调优的过程,即:设计的时候先根据推断选择某个关键资源和阈值,然后测试验证,再上线观察,如果发现不合理,再进行优化。

排队

排队实际上是限流的一个变种,限流是直接拒绝用户,排队是让用户等待一段时间,全世界最有名的排队当属 12306 网站排队了。排队虽然没有直接拒绝用户,但用户等了很长时间后进入系统,体验并不一定比限流好。

由于排队需要临时缓存大量的业务请求,单个系统内部无法缓存这么多数据,一般情况下,排队需要用独立的系统去实现,例如使用 Kafka 这类消息队列来缓存用户请求。

49 | 谈谈App架构的演进

架构设计理念,可以提炼为下面几个关键点:

  • 架构是系统的顶层结构。
  • 架构设计的主要目的是为了解决软件系统复杂度带来的问题。
  • 架构设计需要遵循三个主要原则:合适原则、简单原则、演化原则。
  • 架构设计首先要掌握业界已经成熟的各种架构模式,然后再进行优化、调整、创新。

架构设计文档模板

备选方案模板

需求介绍

[需求介绍主要描述需求的背景、目标、范围等]

随着前浪微博业务的不断发展,业务上拆分的子系统越来越多,目前系统间的调用都是同步调用,由此带来几个明显的系统问题:

性能问题:当用户发布了一条微博后,微博发布子系统需要同步调用“统计子系统”“审核子系统”“奖励子系统”等共 8 个子系统,性能很低。 耦合问题:当新增一个子系统时,例如如果要增加“广告子系统”,那么广告子系统需要开发新的接口给微博发布子系统调用。 效率问题:每个子系统提供的接口参数和实现都有一些细微的差别,导致每次都需要重新设计接口和联调接口,开发团队和测试团队花费了许多重复工作量。

基于以上背景,我们需要引入消息队列进行系统解耦,将目前的同步调用改为异步通知。

需求分析

[需求分析主要全方位地描述需求相关的信息]

5W

5W 指 Who、When、What、Why、Where。

  • Who:需求利益干系人,包括开发者、使用者、购买者、决策者等。
  • When:需求使用时间,包括季节、时间、里程碑等。
  • What:需求的产出是什么,包括系统、数据、文件、开发库、平台等。
  • Where:需求的应用场景,包括国家、地点、环境等,例如测试平台只会在测试环境使用。
  • Why:需求需要解决的问题,通常和需求背景相关

消息队列的 5W 分析如下:

  • Who:消息队列系统主要是业务子系统来使用,子系统发送消息或者接收消息。
  • When:当子系统需要发送异步通知的时候,需要使用消息队列系统。
  • What:需要开发消息队列系统。
  • Where:开发环境、测试环境、生产环境都需要部署。
  • Why:消息队列系统将子系统解耦,将同步调用改为异步通知。

1H

这里的 How 不是设计方案也不是架构方案,而是关键业务流程。 消息队列系统这部分内容很简单,但有的业务系统 1H 就是具体的用例了,有兴趣的同学可以尝试写写 ATM 机取款的业务流程。如果是复杂的业务系统,这部分也可以独立成“用例文档”

消息队列有两大核心功能:业务子系统发送消息给消息队列。业务子系统从消息队列获取消息。

8C

8C 指的是 8 个约束和限制,即 Constraints,包括性能 Performance、成本 Cost、时间 Time、可靠性 Reliability、安全性 Security、合规性 Compliance、技术性 Technology、兼容性 Compatibility

注:需求中涉及的性能、成本、可靠性等仅仅是利益关联方提出的诉求,不一定准确;如果经过分析有的约束没有必要,或成本太高、难度太大,这些约束是可以调整的。

  • 性能:需要达到 Kafka 的性能水平。
  • 成本:参考 XX 公司的设计方案,不超过 10 台服务器。
  • 时间:期望 3 个月内上线第一个版本,在两个业务尝试使用。
  • 可靠性:按照业务的要求,消息队列系统的可靠性需要达到 99.99%。
  • 安全性:消息队列系统仅在生产环境内网使用,无需考虑网络安全;如消息中有敏感信息,消息发送方需要自行进行加密,消息队列系统本身不考虑通用的加密。
  • 合规性:消息队列系统需要按照公司目前的 DevOps 规范进行开发。
  • 技术性:目前团队主要研发人员是 Java,最好用 Java 开发。
  • 兼容性:之前没有类似系统,无需考虑兼容性。

复杂度分析

[分析需求的复杂度,复杂度常见的有高可用、高性能、可扩展等,具体请参考专栏第 10 期的分析]

注:文档的内容省略了分析过程,实际操作的时候每个约束和限制都要有详细的逻辑推导,避免完全拍脑袋式决策。

高可用

对于微博子系统来说,如果消息丢了,导致没有审核,然后触犯了国家法律法规,则是非常严重的事情; 对于等级子系统来说,如果用户达到相应等级后,系统没有给他奖品和专属服务,则 VIP 用户会很不满意,导致用户流失从而损失收入,虽然也比较关键,但没有审核子系统丢消息那么严重。 综合来看,消息队列需要高可用性,包括消息写入、消息存储、消息读取都需要保证高可用性。

高性能

前浪微博系统用户每天发送 1000 万条微博,那么微博子系统一天会产生 1000 万条消息,平均一条消息有 10 个子系统读取,那么其他子系统读取的消息大约是 1 亿次。 将数据按照秒来计算,一天内平均每秒写入消息数为 115 条,每秒读取的消息数是 1150 条; 再考虑系统的读写并不是完全平均的,设计的目标应该以峰值来计算。峰值一般取平均值的 3 倍,那么消息队列系统的 TPS 是 345,QPS 是 3450,考虑一定的性能余量。 由于现在的基数较低,为了预留一定的系统容量应对后续业务的发展,我们将设计目标设定为峰值的 4 倍,因此最终的性能要求是:TPS 为 1380,QPS 为 13800。 TPS 为 1380 并不高,但 QPS 为 13800 已经比较高了,因此高性能读取是复杂度之一。

可扩展

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

备选方案

[备选方案设计,至少 3 个备选方案,每个备选方案需要描述关键的实现,无须描述具体的实现细节。此处省略具体方案描述,详细请参考专栏第 11 期]

备选方案 1:

直接引入开源 Kafka[此处省略方案描述]

备选方案 2:

集群 + MySQL 存储[此处省略方案描述]

备选方案 3:

集群 + 自研存储[此处省略方案描述]

备选方案评估

[备选方案 360 度环评,详细请参考专栏第 12 期。注意备选方案评估的内容会根据评估会议的结果进行修改,也就是说架构师首先给出自己的备选方案评估,然后举行备选方案评估会议,再根据会议结论修改备选方案文档]

架构设计模板

总体方案

[总体方案需要从整体上描述方案的结构,其核心内容就是架构图,以及针对架构图的描述,包括模块或者子系统的职责描述、核心流程]

架构总览

[架构总览给出架构图以及架构的描述]

架构总览

架构关键设计点:

  • 采用数据分散集群的架构,集群中的服务器进行分组,每个分组存储一部分消息数据。
  • 每个分组包含一台主 MySQL 和一台备 MySQL,分组内主备数据复制,分组间数据不同步。
  • 正常情况下,分组内的主服务器对外提供消息写入和消息读取服务,备服务器不对外提供服务;主服务器宕机的情况下,备服务器对外提供消息读取的服务。
  • 客户端采取轮询的策略写入和读取消息。

核心流程

  • 消息发送流程

[此处省略流程描述]

  • 消息读取流程

[此处省略流程描述]

详细设计

高可用设计
  • 消息发送可靠性

业务服务器中嵌入消息队列系统提供的 SDK,SDK 支持轮询发送消息,当某个分组的主服务器无法发送消息时,SDK 挑选下一个分组主服务器重发消息,依次尝试所有主服务器直到发送成功;如果全部主服务器都无法发送,SDK 可以缓存消息,也可以直接丢弃消息,具体策略可以在启动 SDK 的时候通过配置指定。

如果 SDK 缓存了一些消息未发送,此时恰好业务服务器又重启,则所有缓存的消息将永久丢失,这种情况 SDK 不做处理,业务方需要针对某些非常关键的消息自己实现永久存储的功能。

  • 消息存储可靠性

消息存储在 MySQL 中,每个分组有一主一备两台 MySQL 服务器,MySQL 服务器之间复制消息以保证消息存储高可用。如果主备间出现复制延迟,恰好此时 MySQL 主服务器宕机导致数据无法恢复,则部分消息会永久丢失,这种情况不做针对性设计,DBA 需要对主备间的复制延迟进行监控,当复制延迟超过 30 秒的时候需要及时告警并进行处理。

  • 消息读取可靠性

每个分组有一主一备两台服务器,主服务器支持发送和读取消息,备服务器只支持读取消息,当主服务器正常的时候备服务器不对外提供服务,只有备服务器判断主服务器故障的时候才对外提供消息读取服务。

主备服务器的角色和分组信息通过配置指定,通过 ZooKeeper 进行状态判断和决策。主备服务器启动的时候分别连接到 ZooKeeper,在 /MQ/Server/[group]目录下建立 EPHEMERAL 节点,假设分组名称为 group1,则主服务器节点为 /MQ/Server/group1/master,备服务器的节点为 /MQ/Server/group1/slave。节点的超时时间可以配置,默认为 10 秒。

高性能设计

[此处省略具体设计]

可扩展设计

[此处省略具体设计。如果方案不涉及,可以简单写上“无”,表示设计者有考虑但不需要设计;否则如果完全不写的话,方案评审的时候可能会被认为是遗漏了设计点]

安全设计

消息队列系统需要提供权限控制功能,权限控制包括两部分:身份识别和队列权限控制。

  • 身份识别

消息队列系统给业务子系统分配身份标识和接入 key,SDK 首先需要建立连接并进行身份校验,消息队列服务器会中断校验不通过的连接。因此,任何业务子系统如果想接入消息队列系统,都必须首先申请身份标识和接入 key,通过这种方式来防止恶意系统任意接入。

  • 队列权限

某些队列信息可能比较敏感,只允许部分子系统发送或者读取,消息队列系统将队列权限保存在配置文件中,当收到发送或者读取消息的请求时,首先需要根据业务子系统的身份标识以及配置的权限信息来判断业务子系统是否有权限,如果没有权限则拒绝服务。

  • 其他设计

[其他设计包括上述以外的其他设计考虑点,例如指定开发语言、符合公司的某些标准等,如果篇幅较长,也可以独立进行描述]

消息队列系统需要接入公司已有的运维平台,通过运维平台发布和部署。 消息队列系统需要输出日志给公司已有的监控平台,通过监控平台监控消息队列系统的健康状态,包括发送消息的数量、发送消息的大小、积压消息的数量等,详细监控指标在后续设计方案中列出。

部署方案

[部署方案主要包括硬件要求、服务器部署方式、组网方式等]

消息队列系统的服务器和数据库服务器采取混布的方式部署,即:一台服务器上,部署同一分组的主服务器和主 MySQL,或者备服务器和备 MySQL。因为消息队列服务器主要是 CPU 密集型,而 MySQL 是磁盘密集型的,所以两者混布互相影响的几率不大。

硬件的基本要求:32 核 48G 内存 512G SSD 硬盘,考虑到消息队列系统动态扩容的需求不高,且对性能要求较高,因此需要使用物理服务器,不采用虚拟机。

架构演进规划

[通常情况下,规划和设计的需求比较完善,但如果一次性全部做完,项目周期可能会很长,因此可以采取分阶段实施,即:第一期做什么、第二期做什么,以此类推]

整个消息队列系统分三期实现:

  • 第一期:实现消息发送、权限控制功能,预计时间 3 个月。
  • 第二期:实现消息读取功能,预计时间 1 个月。
  • 第三期:实现主备基于 ZooKeeper 切换的功能,预计时间 2 周。

更新时间: