从0开始学架构-每日习题思考

课程信息

概念

模块与组件的区别

  1. 模块的主要目的是职责分离。从业务维度的划分
  2. 组件的主要目的是单元复用。从技术维度上的复用

架构和框架

架构: 宏观、抽象。解决方案
框架: 具体的落地实践。帮助快速开发。如spring MVC
架构是根据具体的业务场景而设计的,解决需求问题;框架是开发规范和组件的集合,为了提升开发效率、质量、和性能。

架构是什么

架构是顶层的结构、系统组成、系统关系、系统规则
image.png

顶层的结构, 系统组成(角色), 系统关系由系统架构图来体现
系统规则,由 系统时序图 来体现

架构设计的目的

技巧💡

架构设计的主要目的是为了解决软件系统复杂度带来的问题。

复杂度来源有很多,高性能、高可用、可扩展性、低成本、安全、规模等来源。这就要分析,系统的主要复杂度是在哪里,抓住主要的问题。
架构是一个取舍的过程,也是一个复杂的分析、判断和选择的过程

架构复杂度来源-高性能

单机高性能(垂直)

  • 硬件,摩尔定律。
  • 软件: 操作系统,决定了软件运行的环境。其中 线程 和 进程。
    什么是线程、进程

提升措施:

  • 更换位SSD
  • 提升硬件性能
  • 减少内存操作。如果redis基于内存

集群高性能(水平)

通过大量机器、分流来提升性能。但是不是成线性提升的。

  1. 功能分解:基于功能将系统分解为更小的子系统 。 任务分解
    1. 为什么能提升?
      1. 简单系统,更容易做到高性能
      2. 可以针对特定情况,进行扩展。
    2. 要注意,拆解是有上限的。如拆分服务过多,会造成一个完整请求需要调用很多个服务器
  2. 多实例副本:同一组件重复部署到多台不同的服务器。 任务分配,负载均衡
  3. 数据分割:在每台机器上都只部署一部分数据

架构复杂度来源-高可用

技巧💡

1.高性能增加机器目的在于“扩展”处理性能;高可用增加机器目的在于“冗余”处理单元
2.网站高可用的主要技术手段是服务与数据的冗余备份与失效转移
3.高可用,直接减少概率。而不能避免

高可用是不要中断服务,高可靠是数据不丢失。
常说的,3个9,4个9

高可用状态决策

状态决策

系统需要能够判断当前的状态是正常还是异常,如果出现了异常就要采取行动来保证高可用。即如何判断当前系统的状态

  1. 独裁者,即只有1个独立的作为决策者。所有上报者上报信息到独裁者。缺点: 独裁者存在单点问题
  2. 协商式,两个独立的个体通过交流信息,然后根据规则进行决策,最常用的协商式决策就是主备决策。 难点在于,如果两者的信息交换出现问题(比如主备连接中断),此时状态决策应该怎么做
    1. 如果备机在连接中断的情况, 备机变成主机,但是主机并未真的故障,就会存在2个主机。
    2. 如果备机在连接中断的情况,备机没有变成主机,但是主机真的故障,就会没有主机。
    3. 可以考虑,增加更多的连接,降低影响。
  3. 民主式,多个独立的个体通过投票的方式来进行状态决策。如 ZK的ZAB算法,RAFT算法等。通过 多数派,选举出leader节点,进行决策。主要存在问题,脑裂问题。
    1. 通过多数派,使其只有1个主机生效,而不存在多个主机的情况。但是少数派的异常的节点,将不可用

架构复杂度来源-可扩展

技巧💡

可扩展性是指,系统为了应对将来需求变化而提供的一种扩展能力,当有新的需求出现时,系统不需要或者仅需要少量修改就可以支持,无须整个系统重构或者重建。

封装变化

正确预测变化(2年法则),做哪些

  1. 难点在于,决定哪些需要扩展。哪些不用。不可能所有都面面俱到,但是也不能没有设计。
  2. 一做二抄三重构

应对变化,怎么做

  1. 提炼出“变化层”和“稳定层”。 微内核架构
  2. 提炼出“抽象层”和“实现层”。面向抽象方法编程?

架构复杂度来源-低成本、安全、规模

规模:
量变

架构复杂度来源-业务复杂度

拆分业务,微服务

架构设计三原则(合适原则、简单原则、演化原则)

合适优于行业领先,

如瑞幸的mq选择是的kafka,而不是rocketmq。一般认为rockemq经过了阿里双11的考验,认为会更合适。并且有延迟队列功能,而不用额外开发。但是实际情况是,kafaka有技术沉淀。并且已经有自研的延迟队列功能

简单原则

  1. 结构复杂度,如上下游组件依赖多,拆分更多服务,导致调用链关系复杂。
  2. 逻辑复杂,主要指的是业务上的复杂度。当前流行做法,是根据领域进行划分。

演化原则(演化化优于一步到位)

不要贪大求全,解决业务面临的主要问题。而不是过度设计。

三原则案例

架构设计流程: 识别复杂度

技巧💡

将主要的复杂度问题列出来,然后根据业务、技术、团队等综合情况进行排序,优先解决当前面临的最主要的复杂度问题

高性能

nginx:
redis: 读11w/s 写 8w/s
kafka:
zookeeper 读写性能
http请求访问: 2w
mysql数据库: 读 4000 写
kafaka: 几十万(单机)-百万(集群) 360案例:
集群有 100 多台万兆机器,单 topic 的最大峰值 60 万 QPS,集群的峰值大概在 500 万 QPS

架构设计流程: 设计备选方案

基于识别出的复杂度,进行备选方案设计。
常见误区:

  1. 设计出最优秀的方案,要根据适用、简单、演化原则进行设计。
  2. 只设计一个方案,会容易出现 认知陷阱。会为自己方案中的不足与缺陷,找借口,使得其合理化。
    1. 应设计3-5个方案,以3个方案为最合适
    2. 方案差异明显,不应该局限于细节。
    3. 不要局限于成熟的方案,新技术也可以适当考虑
  3. 备案方案,不需要过于细节
    1. 浪费时间,
    2. 过于细节,评审时,会陷入细节。到时候争议较多

架构师设计流程: 评估和选择备选方案

对架构师的要求💡

方案的关键细节,要有深入了解。如选择es时,对es的 索引、副本、集群等技术点要有深入理解。而不能因为es很牛,所以我们用他

如何评估出最优的备选方案呢?根据多个维度,性能、可用性、硬件成本、项目投入、复杂度、安全性、可扩展性、开发周期等维度考虑。

  1. 根据备选方案的优点数量来评估,不可取
  2. 根据 加权平均法。 不可取,因为不好评估每个方案在每个维度的得分,最后会变成 数字游戏
  3. 根据优先级选,如第一优先级都满足,在判断第二个优先级。

架构师设计流程: 详细方案设计(细化)

细化细节,如分库分表时,以什么维度进行分库分表。如何存储数据等。

如何避免在细化设计方案阶段,发现备选方案不合适?

  1. 可能原因是,评估时漏了一个重要维度。如项目开发周期,在细化时,发现项目开发周期过长。
  2. 通过分步骤、分阶段、分系统等方式,尽量降低方案复杂度

架构设计流程: 案例

背景:
假设前浪微博系统用户每天发送 1000 万条微博,那么微博子系统一天会产生 1000 万条消息,我们再假设平均一条消息有 10 个子系统读取,那么其他子系统读取的消息大约是 1 亿。一天内平均每秒写入消息数为 115 条,每秒读取的消息数是 1150 条;再考虑系统的读写并不是完全平均的,设计的目标应该以峰值来计算。峰值一般取平均值的 3 倍,那么消息队列系统的 TPS 是 345,QPS 是 3450.

1. 识别复杂度

  • 高性能:
    均摊到每天: TPS: 115 QPS: 1150
    按峰值为3倍计算: TPS: 345 QPS: 3450
    业务增长4被计算预估(也可10倍): TPS 为 1380,QPS 为 13800

  • 高可用
    消息写入、消息存储、消息读取都需要保证高可用性。不能有消息丢失

  • 可扩展
    无要求

2. 备选方案

依据分析,主要复杂度在于 高性能和高可用。设计3个备选方案

  1. 开源方案,如rabbitmq,rocketmq
  2. 集群 + MySQL 存储,hbase, redis+mysql结合等。
  3. 集群 + 自研存储方案, 可参考开源设计

3. 评估和选择备选方案

评估:
image.png
这里的案例,是选择方案2.
排除备选方案1(注意,这里其实不太同意。这里的选型是kafka,如果是rocektMq,可能最后的方案就是方案1了。而且最新版的kafka很多特性也跟上了。)

  1. 可运维性差,上线后出现问题,无法快速解决。(这个是基于案例中的团队不熟悉scala的情况下)
  2. kafaka的主要设计目标是高性能日志传输,而消息队列的目标是业务消息可靠传输。(这个放到最版的kafaka,这种说法是存在争议的。新版的kafka消息,也是可以可靠传输的)
    排除备案方案3,
  3. 团队技术实力和人员规模,无法支撑自研存储系统
    备选方案2可选原因:
    1. 复杂度不高,可以融入现有运维体系 (简单原则)
    2. 性能能够满足需求,后续可支持平行扩展支持(演化原则)
    3. 缺点是成本。

不同的团队技术实力,规模等,会影响方案的选择。 技术的发展,也是一个影响因素。如果按现在的方案进行设计的话,一般会考虑引入rocketmq,就算是引入kafka,很多特性也是可以满足可靠性传输的。如果引入rocketmq,可靠性传输和可运维性就比较高了。

4. 细化设计方案

数据表设计
  1. 日志数据 ,写入日志数据,就认为写入成功。后续异步将日志表数据写入到消息表。类似mysql预写日志的思想。如 redolog.方式就是文件尾部追加,其实和mysql是独立存在的,只是名字叫日志表其实是一个文件。
  2. 消息表,业务系统从消息表获取消息。消息表数据,定期清理
    为什么要多设计个日志表(可能是一个文件,而不是mysql中的表)?
    类似mysql中的wal,redolog和binlog,先顺序写入日子,后刷盘
数据如何复制

使用mysql的主从复制机制,只复制消息表。

业务服务,如何写入消息(消息流如何运转)
  1. 获取服务器列表,可参考 消息队列中的 服务注册、发现功能
  2. 轮询写入主服务器
  3. 如果无响应或者返回错误,自动请求到下一台服务器。(客户端处理)
业务服务,如何读取消息
  1. 获取服务器列表
  2. 消息队列服务器需要记录每个消费者的消费状态,即当前消费者已经读取到了哪条消息,当收到消息读取请求时,返回下一条未被读取的消息给消费者
通信协议

tcp,propbuf等。可参考 dubbo的 通信协议设计

消息的拉取方式, pull还是push

推拉结合,参考rocketMq

架构模式

高性能架构模式: 读写分离

适用场景💡

读多写少的场景,
思路: SQL优化 —> 缓存 —> 读写分离 —> 分库分表

思考是先优化(如优化sql,新增缓存等),解决不了,在考虑使用读写分离。读写分离,会带来主从延迟问题。会导致很多业务上的不一致。

实现方案:

  1. 客户端sdk实现,走主库还是从库。
  2. 中间件实现,即支持mysql协议的服务端,自动进行读写分离。如mycat

高性能架构模式: 分库分表

分库分表

高性能架构模式: 高性能NOSQL

数据模型
Not ONLY SQL,是作为mysql的补偿。而不是替代。

  • K-V 存储:解决关系数据库无法存储数据结构的问题,以 §redis 为代表。
  • 文档数据库:解决关系数据库强 schema 约束的问题,以 MongoDB 为代表。
  • 列式数据库:解决关系数据库大数据场景下的 I/O 问题,以 HBase 为代表。
  • 全文搜索引擎:解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表。

高性能架构模式: 高速缓存

✨分布式多级缓存

  1. 不是所有业务都要使用缓存,上了缓存就会带来 一致性问题。

单机高性能架构模式: PPC与TTC

技巧💡

实现简单,缺点是都无法支撑高并发的场景,尤其是互联网发展到现在,各种海量用户业务的出现,PPC 和 TPC 完全无能为力

服务器采取的并发模型,的关键设计点:

  • 服务器如何管理连接。
  • 服务器如何处理请求
    什么是线程、进程
    PPC(每个连接fork一个进程) —> prefork(预创建进程) —> ttc(每个连接一个线程) —> prethread(预创建线程)

PPC:

  1. 主线程接受连接 accept
  2. fork子进程
  3. 进程处理连接的读写请求(子进程 read、业务处理、write)
    image.png

prefork:
image.png

TTC(每个连接创建一个线程):

单机高性能架构模式: Reactor与Proactor

PPC与TCC存在的问题,

  1. 每次用完就销毁,资源没复用。可以使用资源池。
  2. read是阻塞操作。当一个连接一个进程时,进程可以采用“read -> 业务处理 -> write”的处理流程,如果当前连接没有数据可以读,则进程就阻塞在 read 操作上。浪费资源

read阻塞优化思路:

  1. 改成非阻塞模式,然后不断轮询多个连接。(轮询浪费cpu,效率低)
  2. io多路复用
    1. 当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待,而无须再轮询所有连接,常见的实现方式有 select、epoll、kqueue
    2. 某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理

I/O 多路复用结合线程池,完美地解决了 PPC 和 TPC 的问题。 又有另外一个名字,叫 reactor.

Proactor💡

来了事件,处理好了,我告诉你

高性能架构模式: 负载均衡分类

  • DNS负载均衡, 地理级别。如南北方分别访问不同的服务器

  • 硬件负载均衡,贵. f5 性能: 从 200 万 / 秒到 800 万 / 秒

  • 软件负载均衡,

    • nginx(软件的 7 层负载均衡) 性能: 5 万 / 秒
    • lvs(Linux 内核的 4 层负载均衡) 性能: 80 万 / 秒
    • 区别:
      • 4 层和 7 层的区别就在于协议和灵活性,Nginx 支持 HTTP、E-mail 协议;
      • 而 LVS 是 4 层负载均衡,和协议无关,几乎所有应用都可以做,例如,聊天、数据库等
        image.png
  • 地理级别负载均衡:www.xxx.com 部署在北京、广州、上海三个机房,当用户访问时,DNS 会根据用户的地理位置来决定返回哪个机房的 IP,图中返回了广州机房的 IP 地址,这样用户就访问到广州机房了。

  • 集群级别负载均衡:广州机房的负载均衡用的是 F5 设备,F5 收到用户请求后,进行集群级别的负载均衡,将用户请求发给 3 个本地集群中的一个,我们假设 F5 将用户请求发给了“广州集群 2”。

  • 机器级别的负载均衡:广州集群 2 的负载均衡用的是 Nginx,Nginx 收到用户请求后,将用户请求发送给集群里面的某台服务器,服务器处理用户的业务请求并返回业务响应。

高性能架构模式: 负载均衡算法

高可用架构模式: CAP定理

高可用故障分析方法论 FMEA

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

技巧💡

存储高可用方案的本质都是通过将数据复制到多个存储设备,通过数据冗余的方式来实现高可用,其复杂性主要体现在如何应对复制延迟和中断导致的数据不一致问题

  • 主备: 备机存在备份作用,不承担实际流量
  • 主从: 从机承担读流量。
  • 双机切换,主备切换和主从切换。解决 主备或者主从,主机挂了。备机/从机无法使用写流量。进行角色切换。 关键考虑点
    • 主备间状态判断
      • 状态传递的渠道:是相互间互相连接,还是第三方仲裁?
        • 互连式,主机和备机多了一个“状态传递”的通道
        • 中介式,都去连接中介,并且通过中介来传递状态信息。
          • 中介的高可用是个问题,导致如此递归下去就无穷无尽了。
          • 中介式成熟的解决方案,例如 ZooKeeperKeepalived
        • 模拟式, 没有状态传递通道,只判断主机是否主机状态。
      • 状态检测的内容:例如机器是否掉电、进程是否存在、响应是否缓慢等。
    • 切换决策
      • 切换时机
        • 什么情况下备机应该升级为主机?是机器掉电后备机才升级,还是主机上的进程不存在就升级,还是主机响应时间超过 2 秒就升级,还是 3 分钟内主机连续重启 3 次就升级等。
      • 切换策略:
        • 原来的主机故障恢复后,要再次切换,确保原来的主机继续做主机,还是原来的主机故障恢复后自动成为新的备机
      • 自动化程度:
        • 切换是完全自动的,还是半自动的?例如,系统判断当前需要切换,但需要人工做最终的确认操作(例如,单击一下“切换”按钮)
    • 数据冲突解决方式
  • 主主复制,双主(实现复杂,一般上很少使用)

高可用架构模式: 集群

集群与主备主从的区别💡

主备、主从、主主架构本质上都有一个隐含的假设:主机能够存储所有数据,但主机本身的存储和处理能力肯定是有极限的。集群就是多台机器组合在一起形成一个统一的系统,这里的“多台”,数量上至少是 3 台;相比而言,主备、主从都是 2 台机器。

集群可以分为两类:数据集中集群、数据分散集群
reids-sentinel: 数据集中集群
redis-cluster: 数据分散集群

  1. 数据集中集群,与主从类似。一般使用zk进行
  2. 数据分散集群。数据分散集群指多个服务器组成一个集群,每台服务器都会负责存储一部分数据;同时,为了提升硬件利用率,每台服务器又会备份一部分数据。就是数据分片。
    1. 均衡性: 数据分片仅可能平均
    2. 容错性: 出错服务器故障,故障服务器的数据分区分配给其他服务器
    3. 可伸缩性: 支持新增服务器后,数据也均衡

区别:

  1. 数据集中集群, 只有主机支持写。 而数据分散集群,所有服务器都支持读写
  2. 数据分散集群,需要一个数据分配到哪个服务器的角色。可以是单独一个服务,也可以是选举其中一个服务器。
    1. 单独服务,如 hdfs的namenode
    2. 选择一个节点,作为数据分配的角色。如 es

hdfs-单独服务作为数据分配角色

image.png

HDFS采用master/slave架构。一个HDFS集群是由一个Namenode和一定数目的Datanodes组成。Namenode是一个中心服务器,负责管理文件系统的名字空间(namespace)以及客户端对文件的访问。集群中的Datanode一般是一个节点一个,负责管理它所在节点上的存储。HDFS暴露了文件系统的名字空间,用户能够以文件的形式在上面存储数据。从内部看,一个文件其实被分成一个或多个数据块,这些块存储在一组Datanode上。Namenode执行文件系统的名字空间操作,比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体Datanode节点的映射。Datanode负责处理文件系统客户端的读写请求。在Namenode的统一调度下进行数据块的创建、删除和复制

es-选举服务作为数据分配角色

Elasticsearch 集群通过选举一台服务器来做数据分区的分配,叫作 master node
image.png

高可用计算架构:

技巧💡

高可用,主要目的在于故障时,计算任务能够继续执行。
计算是无状态的,而存储是有状态的。这也导致 高可用存储比高可用计算复杂。

要想高可用就离不开冗余,无论是计算高可用还是存储高可用都会面对机器状态检测、切换以及机器选择的问题,在这几个方面二者复杂度差别不大。 但对于计算而言,集群中的机器间之间基本上是无交互的,对于需要重试的计算任务,是有任务管理器来维护处理;而存储高可用还会涉及到机器之间数据的同步和一致性问题,在同步时还需要考虑性能、稳定性、同步中断、个别失败、重复同步等问题,这一块就会复杂许多。 因而,总体来看,存储高可用更为复杂。
与高可用存储类似:

  • 主备
  • 主从
  • 集群
    • 对称集群(哪个服务器执行都可以)
    • 非对称集群(如某些任务只能在master服务器执行)

如何应对接口级故障

可扩展架构简介与思想

可扩展,目的是有改动的时候,尽量减少改动范围。即设计模式中的,单一职责,解耦等思想。
主要思路是拆,进行解耦。

  1. 按流程拆,数据流程的流转。数据流程非业务流程.分层架构
  2. 按服务拆,倾向于站在外部视角而言. 微服务,SOA
  3. 按功能拆,倾向于站在内部视角而言。 微内核架构

可扩展架构传统模式: 分层架构SOA

可扩展模式: 微服务

可扩展模式: 微内核架构

架构实战: 互联网演进模式

互联网架构模板

image.png

架构重构内功心法

  • 有的放矢
    • 是否有必要进行重构?如果从0开始设计架构,