分布式架构理论:从头梳理分布式架构的重难点

@[TOC]

一、分布式架构 - 系统理论

1、分布式一致性与CAP理论

CAP理论,指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性),不能同时成立。 CAP理论告诉我们C、A、P三者不能同时满足,最多只能满足其中两个。

C - 一致性:强一致性。 A - 可用性:99.999%、宕机时间来衡量。 P - 分区容错性:能容忍网络分区,在网络断开的情况下,被分隔的节点仍能正常对外提供服务。

CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。 CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。 AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

理解CAP理论最简单的方式是想象两个副本处于分区两侧,即两个副本之间的网络断开,不能通信。 如果允许其中一个副本更新,则会导致数据不一致,即丧失了C性质。 如果为了保证一致性,将分区某一侧的副本设置为不可用,那么又丧失了A性质。 除非两个副本可以互相通信,才能既保证C又保证A,这又会导致丧失P性质。 一般来说使用网络通信的分布式系统,无法舍弃P性质,那么就只能在一致性和可用性上做一个艰难的选择。

CAP理论的表述很好地服务了它的目的,开阔了分布式系统设计者的思路,在多样化的取舍方案下设计出多样化的系统。在过去的十几年里确实涌现了不计其数的新系统,也随之在一致性和可用性的相对关系上产生了相当多的争论。

在CAP理论提出十二年之后,其作者又出来辟谣。"三选二"的公式一直存在着误导性,它会过分简单化各性质之间的相互关系: 首先,由于分区很少发生,那么在系统不存在分区的情况下没什么理由牺牲C或A。 其次,C与A之间的取舍可以在同一系统内以非常细小的粒度反复发生,而每一次的决策可能因为具体的操作,乃至因为牵涉到特定的数据或用户而有所不同。 最后,这三种性质都可以在程度上衡量,并不是非黑即白的有或无。 可用性显然是在0%到100%之间连续变化的,一致性分很多级别,连分区也可以细分为不同含义,如系统内的不同部分对于是否存在分区可以有不一样的认知。 所以一致性和可用性并不是水火不容,非此即彼的。Paxos、Raft等分布式一致性算法就是在一致性和可用性之间做到了很好的平衡的见证。

2、BASE理论

eBay 的架构师 Dan Pritchett 源于对大规模分布式系统的实践总结,在 ACM 上发表文章提出 BASE 理论,BASE 理论是对 CAP 理论的延伸,核心思想是即使无法做到强一致性(Strong Consistency,CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)

Basically Available (基本可用)分布式系统在出现不可预知故障的时候,允许损失部分可用性。 Soft state (软状态)软状态也称为弱状态,和硬状态相对,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。 Eventually consistent (最终一致性)最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),更具体地说,是对 CAP 中 AP 方案的一个补充。其基本思路就是:通过业务,牺牲强一致性而获得可用性,并允许数据在一段时间内是不一致的,但是最终达到一致性状态

3、分布式一致性算法:Raft

(1)Paxos算法

非常难! 于是退出了一个简化版的Paxos算法:Raft。

(2)Raft算法

三个角色: Leader角色:Replica,只有一个,负责日志写入,日志复制等操作,相当于一个最顶层的角色。 Follower角色:选民角色,可以投票,选出leader。 Candidate角色:候选人角色,可以参与投票,成为Leader。

Raft 使用心跳(heartbeat)触发Leader选举。当服务器启动时,初始化为Follower。Leader向所有Followers周期性发送heartbeat。如果Follower在选举超时时间内没有收到Leader的heartbeat,就会等待一段随机的时间后发起一次Leader选举。 每一个follower都有一个时钟,是一个随机的值,表示的是follower等待成为leader的时间,谁的时钟先跑完,则发起leader选举。

场景一: 集群环境中没有Leader的话,其中一个Follower会成为Candidate,它首先给自己投票并且向各个Follower发送一个指令让大家给自己投票。 Follower的逻辑很简单,谁先让我给他投票我就选谁。 集群中的机器数量n,当Candidate拿到的选票是(N/2) +1,也就是过半,它就会成为Leader。 场景二: 集群中的Leader挂掉之后,有可能两个Follower同时成为了Candidate,并且选票相同怎么办? 一段时间以后,如果这个选举还没结束,就进入加时赛,每一个follower只能有一票投出,这样同时拿到同样票数的概率会非常低,最后胜出的Candidate就会成为leader。

场景三: 假如说之前掉线的Leader自己恢复上线了,并且还自带一个Follower。此时就出现了两个Leader,形成了两个集群,也就是我们所说的脑裂现象。 此时,会有一个版本更老的leader退居Follower,它曾经写下的这些指令统统回滚。

当一个Leader出现之后,就会给自己的这些Follower发送日志复制这个指令。

(3)共识算法:拜占庭将军问题

在分布式计算中,不同的计算机通过通讯交换信息达成共识按照一套协作策略行动。有时候,系统中的成员计算机可能出错而发送错误的信息,用于传递信息的通讯网络也可能导致信息损坏,使得网络中不同的成员关于全体协作的策略得出不同结论,从而破坏系统一致性,这就是拜占庭将军问题。

拜占庭将军问题被认为是容错性问题中最难的问题类型之一。

9 位将军兵分 9 路去打仗,他们各自有权力观测敌情并做出行动判断 ------ 进攻或撤退,他们必须行动一致,即所有军队一起进攻或者一起撤退,否则部分进攻部分撤退会造成灾难性后果。

将军之间只能通过信使互相联系,每位将军将自己的判断发送给其他将军,并接收其他将军发送的判断; 收到信息的将军综合所有的判断,当超过半数都选择进攻时,就决定进攻,当超过半数都选择撤退时就决定撤退;

问题是,将军中间可能出现叛徒,他可能会选择相反的结果进行通信(投票),也可能选择性的发送信息,叛徒要达成的目标是: 选择性的发送信息,欺骗某些将军采取进攻的行动; 促成一个错误的决定,比如将军们不希望进攻时进攻; 迷惑某些将军,使得他们无法做出决定; 如果叛徒达成了其中之一,任何的攻击结果都是注定要失败的,只有完全达成一致的努力才能获得胜利。

比如,可能 9 位将军中有 8 位忠诚的将军和一名叛徒,8 位将军中 4 位选择进攻,4 位选择撤退,叛徒分别给选择进攻的将军发送进攻的信息,给选择撤退的将军发送撤退信息。这样一来,在4 位选择进攻的将军看,共 5 位将军选择进攻,从而发起进攻;而在 4 位选择撤退的将军看,共 5 位将军选择撤退,从而发起撤退,这样各个将军的一致性就遭到了破坏。

并且,叛徒将军可能会伪造其他将军的身份发送信件; 拜占庭将军问题描述的是,在存在信息丢失的不可靠信道上试图通过消息传递的方式达到一致性是不可能的,在系统中除了存在的消息延迟或不可送达故障外,还可能包括消息篡改、节点处理异常等潜在性异常。

所以说,Raft算法定义一个Leader的角色,最终的决议以及日志的写入都是由这个Leader直接发起,而不是让每一个节点自由发挥。 并且Leader在维持自己"大将军"角色时,会不断的给Follower发送一个类似心跳包,确保自己的统治地位。

4、脑裂现象和Lease机制

(1)什么是脑裂现象

在一个高可用系统中,当联系着的节点断开联系时(网络等问题),本来为一个整体的系统,分裂成两个独立节点,两个节点开始争抢共享资源造成系统混乱、数据损坏的现象,成为"脑裂"。

(2)解决方案:全局过半

使用全局过半的方案,上面5台服务器的情况下,Candidate必须得票超过总数的一半才可以当选Leader,这也正是Zookeeper的解决方案。

在这种情况下,左侧的两台机器都是不可用的状态,右边选举出一个新的Leader来继续提供服务。

注意:使用这种解决方案的话,必须保证超过一半的机器处于正常工作状态。

(3)解决方案:Lease机制

颁发者给节点颁发租约,指定其在一定时间内担任Leader,租约一旦颁发出去,在一定时间内就强制生效且不可撤销,过期之后,这个租约也就失效了。 Lease机制,翻译过来即是租约机制,是一种在分布式系统常用的协议,是维护分布式系统数据一致性的一种常用工具。 Lease机制有以下几个特点: 1.Lease是颁发者对一段时间内数据一致性的承诺; 2.颁发者发出Lease后,不管是否被接收,只要Lease不过期,颁发者都会按照协议遵守承诺; 3.Lease的持有者只能在Lease的有效期内使用承诺,一旦Lease超时,持有者需要放弃执行,重新申请Lease。 还是以上面的场景为例,假设在脑裂发生之前,左边的Leader拿到一个契约,指明在1到10秒之间担任Leader角色,即使右边三台机器连不上Leader,它还是会保留Leader的角色。当契约时间过了之后,将会有一个新的Leader产生。

**10s的Lease间隔时间是在实践中检验过,比较合适的 ** 如果拿到租约的Leader挂了,则需要等待下一次租约颁布才能恢复使用,牺牲一点可用性保证了不出现脑裂(强一致性)。

潜在问题: Lease机制依赖于每台服务器的时钟,颁发Lease的机器和接收租约的机器之间的时钟误差会导致集群短暂不可用。

二、分布式架构 - 底层数据设计策略

1、关系型数据库:读写分离和集群扩展

读多写少的场景下: 优点:消除读锁(共享锁)和写锁(排他锁)。读节点宕机不影响master。 缺点:增加开发量。(使用Sharding proxy、ShardingJDBC等中间件来解决) 缓存/搜索引擎,本质上也是一种读写分离,也可以使用中间件来进行binlog同步。

2、关系型数据库:分库分表

(1)分表

垂直分表:一张表(name、price、Blob desc、Blob img)按照字段分到不同的表中,每个表中存储其中的一部分字段(经常查询的放一张表、不经常查询并且占用大量IO的放另一张表)。 垂直分表比较简单,容易实现也没什么技术难度。

水平分表:表的字段是一样的,不同的数据落到不同的表中。 比如说根据id取模(ID%3),按照结果存到不同的表中。(每次扩展数据表,取模的结果可能都会发生变化,所以每次扩展都伴随着数据迁移) 比如说按照时间分片,按照新旧数据将数据存到不同表中。(一月、二月......,但是要注意某个时间的数据非常多或非常少)

(2)分库

水平分库:与水平分表有异曲同工之妙。

(3)其他问题

1、用户的订单做了分库分表,如何查询该用户的所有订单?(用户数据也根据id做分库分表) 2、如何分页? 3、数据如何同步到ES或者redis?

(4)数据迁移和扩容:0宕机备库转主库

扩容时必须成倍扩容,二进四,四进八,八进十六,这样才能保证主库和备库的路由规则可以平滑过渡。 最后,库中额外的数据,删掉就行了。

(5)数据迁移和扩容:增量存量同步

扩容时必须成倍扩容,二进四,四进八,八进十六。 需要首先做增量同步(用CDC工具比如canal),而后做存量数据的迁移(根据取模路由规则),数据迁移完成之后,更改路由表。

3、热点数据处理

(1)什么是热点数据

热点数据:在极短时间内被频繁的高并发访问的数据。(爆款商品、秒杀)

在真正的热点数据面前,即便是缓存也是扛不住的。(单机10万QPS,并且无法所有数据做缓存)

对于秒杀商品,其实是预知的热点,可以预料到的热点其实不存在技术问题。对系统影响最大的是不可预知的热点,比如说微博热搜。

(2)为什么要对热点数据进行隔离

缓存也可以做分区。 热点数据隔离,可以做接口层面、底层数据层面做隔离,防止热点数据将正常数据打崩。

(3)如何处理热点数据

热点数据的特点:数量少、访问频次高。(解决方案:热点散列、热点库、多级缓存)

多级缓存: redis等旁路缓存。 JVM堆内内存 + JVM堆外缓存。(防止大对象热点GC频繁)

热点库: 将热点数据抽离,抽成一个热点库。读取频率高,但是数据量很少。

热点散列(Tair):

(4)如何监听热点数据

热点数据处理:识别热点 - 隔离热点 - 性能优化。 隔离热点:秒杀类的预先知道的热点做缓存预热;做访问单元的隔离、分区;用热点库做数据隔离;接口层面做隔离。 性能优化:缓存;降级熔断;限流。

识别热点:已知热点数据和未知热点数据(比如说微博爆搜)。

难点:识别未知的热点数据。 从入口处,rpc或者gateway,查询某一个数据查询的频率越高,就越可能成为一个热点。 通过日志抓取(RPC或者gateway),聚合分析数据的访问频次,达到某一个阈值的数据,发布热点通知。 同样的,该数据通过聚合分析发现访问频次降低,达到某一个阈值,就发布取消热点的通知。

4、面试题

问:设计一个支撑xxx并发量的应用,从数据库层面有什么考虑? 答:底层数据:读写分离+集群扩展、备库、异构+分库分表。

问:热点数据怎么防范? 答:预知热点:热点库+本地缓存+多级缓存。动态热点:热点数据的侦听+通知送达节点(接口参数聚合、日志埋点统计)

问:分库分表后,业务量增加需要扩容,如何处理? 答:分库分表的迁移和扩容(备库转主库,2N扩容)

三、分布式架构 - 高可用数据

1、数据备份

两地三备份方案:两地间隔1000km以上。 冷备:低成本、不是实时的(定时)。 会有数据丢失问题、数据不一致问题。 最最坏的情况才会用冷备数据做恢复。时效性不高的数据可以采用冷备方式,比如历史订单、业务报表。

热备:成本高、实时性的。 同步热备:写入数据时,同时向多个库写入。(数据一致性难以保证,并不会用作高可用的方案) 主从热备:业务操作写入主库,主库同步到备库。(用的多,比如mysql支持主从,或者使用canal等中间件)

2、失效转移

mysql主从半同步,从Master提交事务之后,只有同步到备库成功了之后,才提示成功。

mysql如果主库挂掉了,配置的fellover就会生效,流量会切换到备库。(失效转移)

3、canal - 数据迁移神器

数据异构、数据同步。 代码层面控制缓存和DB一致性。(使用canal将DB数据同步到redis、es)

canal就是通过读取master的binlog,进行数据同步的。 canal搭建与使用: 使用canal订阅mysql的binlog,springboot使用canal订阅mysql的binlog

4、使用NoSQL

NoSQL数据库有HBase、Redis、MongoDB、Neo4J、ES等。

(1)MongoDB文档

最接近SQL的NoSQL数据库,面向集合(文档,就是JSON格式的数据)。 应用于复杂模型变更场景,高QPS读写场景,大对象存储场景。 主从切换+sharding,在MySQL实现的地方,很大程度都能用MongoDB实现。 跨文档事务场景要小心使用!事务支持不如MySQL。

(2)Redis、Tair、Memcached缓存

典型的key-value数据库。(快速寻址的设计) 只能通过key查找value,而不是像SQL数据库key-value都可以随便查。

适用场景:业务缓存、限流/计数器。

(3)GraphQL、Neo4J图形化数据库

图形化数据库适用于错综复杂的关系网,网状结构。

(4)HBase列数据库

行表是 name+age等字段为一行存储。 列表是name 、age分开存储。 列表天然适合做聚合操作、存储大量数据。

应用场景:数据仓库、数据分析、存储大量数据、并行查询、数据压缩。

(5)ES、Solr搜索引擎

全文检索+分词+倒排索引。

通常用于数据异构方案,数据从MySQL通过canal进行数据同步至ES中。

5、数据冗余:被淘汰的范式

数据库设计三范式:1NF:原子字段、不可分解;2NF&3NF:主键相关、不可冗余。 详细请移步:数据库设计的三范式超详细详解

互联网超高并发的场景,数据库三大范式并不是完全适用,而是使用了大量的冗余、快照。 比如说使用关联表(不建议使用,join操作非常消耗资源)+大宽表冗余。

比如说,一条订单记录,有可能按照订单id维度分库分表存了一份;按照用于id维度分库分表存了一份;按照时间维度分库分表存了一份还有可能使用数据异构的方式,用canal同步到Solr、ES、openSearch中进行查询。

注意要解决数据一致性的问题。(分布式事务+数据同步)

6、阿里系数据变更流程规范

定义数据访问角色。(Owner、DBA有RW权限)(研发有R权限)

研发查询生产库,只有读权限。(让Owner添加数据访问权限) 研发修改生产库,1-研发提交变更申请(sql+预计影响行数),2-Owner审核(少量数据直接审批,数据量大得找主管审批,Owner修改需要让主管审批)3-执行。

数据权限流程并不是很长,可以保证线上问题可以快速响应、修改、解决问题。

7、使用Druid(德鲁伊)监控系统sql状态和效率

xml 复制代码
<dependencies>
    <!-- mysql 对应 driver 版本 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.21</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.13</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
yml 复制代码
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # 数据库连接串
      driverClassName: com.mysql.jdbc.Driver
      # allowPublicKeyRetrieval=true 记得加上
      url: jdbc:mysql://localhost:3306/test?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&useSSL=false
      username: root
      password: root
      # 连接池参数
      initial-size: 10
      max-active: 50
      min-idle: 10
      max-wait: 30000
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      # min-evictable-idle-time-millis: 最小生存时间
      # 检测要关闭的空连接
      time-between-eviction-runs-millis: 120000

      # SQL查询,用来验证从连接池取出的连接,在将连接返回给调用者之前.如果指定
      # 则查询必须是一个SQL SELECT并且必须返回至少一行记录
      validation-query: SELECT 1 FROM DUAL
      # validation-query-timeout: 5000
      # 拿连接的时候校验,归还连接的时候也校验
      test-on-borrow: false
      test-on-return: false
      test-while-idle: true
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      #filters: #配置多个英文逗号分隔(统计,sql注入,log4j过滤)
      # stat是监控统计,wall是防止sql注入
      filters: stat,wall
      # 内置的servlet用来打开监控页面
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
java 复制代码
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;

@org.springframework.context.annotation.Configuration
public class Configuration {

    /**
     * 开启监控统计的dashboard
     */
    @Bean
    public ServletRegistrationBean druidDashboard() {
        ServletRegistrationBean dashboard =
                new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        dashboard.addInitParameter("loginUsername", "root");
        dashboard.addInitParameter("loginPassword", "root");
        return dashboard;
    }

    /**
     * 配置 过滤器的例外情况
     */
    @Bean
    public FilterRegistrationBean statFilter() {
        FilterRegistrationBean filter = new FilterRegistrationBean(new WebStatFilter());

        filter.addUrlPatterns("/*");
        filter.addInitParameter("exclusions", "/druid/*,*.js,");
        return filter;
    }

}

打开localhost:10000/druid就可以打开德鲁伊管控台了。 感兴趣可以深入研究,不过使用云服务器的话,就会有自带的sql监控了。

8、面试题

问:除了主从以外,你还在项目中应用了哪些其他的灾备手段? 答:冷备+热备(数据实时镜像/同步)

问:数据异构方案的设计(数据迁移) 答:canal。

问:场景设计题目 - 底层存储 答:NoSQL,数据冗余。

问:谈谈MySQL的同步方式 答:Binlog方案、Canal方案。

四、分布式架构 - 中间件

1、缓存三大坑

缓存击穿:缓存并不存在(过期或者未加载),用户大量请求该数据,导致热点数据打到数据库,导致数据库崩溃。 解决方案:调整缓存过期策略(永不过期;读写分离架构 - 使用canal做数据异构)、热点缓存策略(动态甄别热点数据,热点数据与普通数据做隔离)、使用互斥锁(查不到缓存时加锁再访问数据库,使用双重锁加载;提前上锁,异步刷缓存)。

缓存穿透:请求一个不存在的数据,会直接读取数据库,大量请求不存在的数据导致数据库崩溃。 解决方案:缓存null值、布隆过滤器。

缓存雪崩:缓存的key在集中时间集体过期,导致一瞬间数据库压力过大。 解决方案:调整缓存过期策略(过期时间散列 = 基础时间 + 动态随机时间)、多级缓存、缓存预热。

2、布隆过滤器 进阶版

布隆过滤器高效地插入和查询,占用空间少,返回的结果是不确定的,一个元素如果判断结果为存在,它不一定存在;不存在时,一定不存在。 一般情况下,先查询Redis缓存,如果Redis中没有,再查询MySQL。当数据库中也不存在这条数据时,每次查询都要访问数据库,这就是缓存穿透。 在Redis前面添加一层布隆过滤器,将缓存中的所有数据添加到布隆过滤器中,这样在查询之前就可以先判断数据是否存在。 请求先在布隆过滤器中判断,如果布隆过滤器不存在时,直接返回,不再反问Redis和MySQL。 如果布隆过滤器中存在时,再访问Redis,再访问数据库。

布隆过滤器(BloomFilter)是解决缓存穿透的有效手段。 布隆过滤器是一段01的二进制码,0代表当前位没有数据,1代表当前位有数据。 通过多个散列函数(HASH函数),对于传入的值有可能hash相同,布隆过滤器有一定的误判率。(增加空间或者增加散列函数来降低误判率,相应的就会增加存储空间和计算时间)。 布隆过滤器无法删除数据,因为删除操作有可能会影响其他商品。 进阶版的布隆过滤器:可删除: 再加一层,计算每一位置有多少个数据存储,删除时进行减一操作:

3、消息组件选型分析

ActiveMQ(过气很久了)、RabbitMQ、RocketMQ、Kafka。 三个性能:可靠性、消息堆积能力、吞吐量、重试、事务能力、消息支持能力(最少消费一次、最多消费一次、一定消费一次)、可扩展性(集群、水平扩展、高可用、故障转移能力)

Kafka:消息堆积能力强(T级)、吞吐量高、顺序读写支持好、Page Cache性能好。但是可靠性不高。 RocketMQ:可靠性高 RabbitMQ:可靠性高、失败消息打入死信队列

4、线上预警

通过日志来对线上监控预警。(需要将关键点打上关键日志)

业务监控:业务量阈值(每分钟采集)、异常拐点、离群点(与平均值相差较大的) 异常监控:失败率(>10个/分钟)、核心接口性能基线(平均RT>1000ms)

Pager Duty:轮班。 出问题之后打电话、钉钉等。

5、业务埋点

审计:对关键资源的操作(后台人员操作)进行埋点。 统计分析:用户画像、AB Test。 线上问题排查:

6、大项目中的PagerDuty机制

三级响应:1-开发运维人员;2-项目管理者、领导者;3-大领导。

通知方式:短信、邮件、电话。

通知到个人、团队之后,需要回馈(ACK) ->标记Root Cause(问题根本原因)->修复问题上线。

从问题响应到问题解决,定级故障单的响应速率。

RCA:总结沉淀会议,避免重复故障产生。确定影响范围、影响实际、资损。

7、预估装机容量和应用水位

计算QPS = (PV * 80%)/3600s * 24H * 20% PV:当前页面访问次数 坊间传言:百分之80的流量在百分之20时间内完成的。 当然,具体业务具体分析,比如双十一,突发流量更高。

容量评估: 需要多少台机器 = QPS评估/单机平均QPS + 弹性容量 但是单机QPS是波动的,而且集群内加机器也不是线性提高QPS的。

应用水位:当前QPS/容量评估承压QPS 弹性:当前QPS/性能基线QPS*机器数

需要根据应用水位做监控预警(水位百分之80或者90)、弹性计算。

总的来说,预估装机量和应用水位,不是通过公式计算的,而是通过经验等等给出一个大概的数。

8、面试题

问:缓存问题 答:缓存雪崩、缓存击穿、缓存穿透、热点缓存淘汰策略、多级缓存、布隆过滤器。

问:为什么在xx场景使用RabbitMQ而不是Kafka? 答:中间件特性、可靠性要求等。

问:业务埋点的场景和用途 答:内部审计、监控预警、关键链路梳理。

问:开放式问题:设计一套轻量级的系统水位监控系统? 答:从这几方面考虑:异步化、基于日志埋点(网络日志&应用日志)

五、分布式架构 - 应用层设计

1、服务集群伸缩性

DNS负载均衡:dns服务器通过负载均衡算法,返回一个不固定的ip,实现负载均衡。

反向代理(HTTP转发):反向代理(nginx等)通过负载均衡算法,将http请求转发给集群中的一台服务器。 (源地址转换技术,可以修改访问者ip)

Mac地址负载均衡(直接路由,数据链路层做的,LVS):给集群中的服务器配置虚ip(和负载均衡服务器一样的ip)。

2、利用消息组件解耦

我们都知道,消息组件可以实现削峰填谷,但是我们此处是以业务维度来考虑,对业务组件进行解耦的场景。

对于长任务、非实时的业务、可异步化、发布订阅(多下游相同语义)的业务场景,就可以使用消息组件解耦。

需要额外给用户设计特殊的用户体验。

3、性能指标和应用层优化策略

(1)性能测试指标

RT:响应时间 QPS:每秒访问次数(吞吐量) 并发数(并发能力)。 其中并发数可以通过集群等水平扩展的方式提高,所以应用层优化主要是做RT和QPS的优化。

(2)复杂业务性能优化

从业务层面考虑: 并行、异步化:Future模式(任务拆分+异步)、Thread(线程池管理、SpringAsync)、MQ。 存储优化:分布式缓存、本地缓存、热点缓存、上ES、数据异构、冗余、SQL调优/hints。

(3)构建性能基线

通过稳定性测试,关注系统性能拐点。关注关键点,在关键点做相应的手段比如降级熔断限流。

4、缓存常用模式

(1)全量缓存场景

使用canal消费数据库的binlog,发布到缓存中。(canal直接发送mq消息,或者直接写java程序连接canal) 全量缓存场景,相当于读写分离场景,缓存资源消耗较大。

方案:1 - 写应用写入数据库; 2 - 使用canal订阅binlog将数据写入缓存; 3 - 读应用只读缓存。

(2)非全量缓存(过期失效)

既然上了缓存,就要有数据不一致的觉悟。数据不一致是一定存在的,只是时间长短的问题。

方案: 1 - 应用写入数据库;2 - 读应用读缓存,缓存没有的话再读数据库;3 - 数据写入缓存。(注意要解决缓存穿透、击穿、雪崩问题) 缓存一致性解决方案------改数据时如何保证缓存和数据库中数据的一致性

5、状态设计 - 状态模式、spring状态机

订单状态:需要用大量的if、switch,解决方案:MQ驱动、Job驱动、状态机。

设计模式之【状态模式】,如何设计一个"状态管理大师"

参考资料

www.zhihu.com/question/64... baijiahao.baidu.com/s?id=175805... cloud.tencent.com/developer/a...

相关推荐
hai4058723 分钟前
Spring Boot中的响应与分层解耦架构
spring boot·后端·架构
Adolf_19932 小时前
Flask-JWT-Extended登录验证, 不用自定义
后端·python·flask
叫我:松哥2 小时前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
海里真的有鱼2 小时前
Spring Boot 项目中整合 RabbitMQ,使用死信队列(Dead Letter Exchange, DLX)实现延迟队列功能
开发语言·后端·rabbitmq
工业甲酰苯胺2 小时前
Spring Boot 整合 MyBatis 的详细步骤(两种方式)
spring boot·后端·mybatis
新知图书3 小时前
Rust编程的作用域与所有权
开发语言·后端·rust
wn5313 小时前
【Go - 类型断言】
服务器·开发语言·后端·golang
希冀1234 小时前
【操作系统】1.2操作系统的发展与分类
后端
GoppViper4 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
爱上语文5 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring