系统设计目标

高并发系统设计的三大目标:高性能、高可用、可扩展

  • 高性能:保证系统能够承载高并发的前提。
  • 高可用:防止系统隔三差五宕机维护停机。
  • 可扩展:保证能够在短时间内迅速完成扩容,更加平稳地承担峰值流量。

提升系统性能

性能优化原则

  1. 性能优化一定不能盲目,一定是问题导向的。

    脱离了问题,盲目地提早优化会增加系统的复杂度,浪费开发人员的时间,也因为某些优化可能会对业务上有些折中的考虑,所以也会损伤业务。

  2. 性能优化也遵循“八二原则”。

    在优化过程中一定要抓住主要矛盾,优先优化主要的性能瓶颈点。

  3. 性能优化也要有数据支撑。

    一定要把性能监控系统做好,在优化过程中,时刻关注优化效果。

  4. 性能优化的过程是持续的。

    高并发的系统通常是业务逻辑相对复杂的系统,在优化过程中需要循序渐进,不断寻找性能瓶颈并优化。

性能的度量指标

性能的度量指标主要用于明确性能问题、评估优化效果。

度量性能的指标是系统接口的响应时间,但是单次的响应时间是没有意义的,需要知道一段时间的性能情况是什么样的,主要的统计特征值有:

  • 平均值

    平均值是把这段时间所有请求的响应时间数据相加,再除以总请求数。但是不能真实反应出系统性能。

  • 最大值

    就是这段时间内所有请求响应时间最长的值,但过于极端。

  • 分位值

    分位值有很多种,比如 90 分位、95 分位、75 分位。分位值排除了偶发极慢请求对于数据的影响,能够很好地

    反应这段时间的性能情况,分位值越大,对于慢请求的影响就越敏感。(在实际工作中也应用最多,能真实反

    应性能)

对于系统的响应时间:

  • 200ms:用户是感觉不到延迟的,就像是瞬时发生的一样。
  • 1s:用户可以感受到一些延迟,但却是可以接受的
  • 1s之后:会有明显等待的感觉,等待时间越长,用户的使用体验就越差。

优化基本思路

  1. 提高系统的处理核心数

    提高系统的处理核心数就是增加系统的并行处理能力,这个思路是优化性能最简单的途径。

    但并不是核心数越多越好。随着并发进程数的增加,并行的任务对于系统资源的争抢也会愈发严重。在某一

    个临界点上继续增加并发进程数,反而会造成系统性能的下降,这就是性能测试中的拐点模型。

    image-20230417171408562

  2. 减少单次任务响应时间

    想要减少任务的响应时间,首先要看你的系统是 CPU 密集型还是 IO 密集型的。

    • CPU密集型:

      CPU 密集型系统中,需要处理大量的 CPU 运算,那么选用更高效的算法或者减少运算次数就是这类系

      统重要的优化手段。

    • IO密集型:

      IO 密集型系统指的是系统的大部分操作是在等待 IO 完成,这里 IO 指的是磁盘 IO 和网络 IO。这类系统

      的性能瓶颈可能出在系统内部,也可能是依赖的其他系统。这种情况就只有针对性的进行处理。

提高系统可用性

高可用性(High Availability,HA)它指的是系统具备较高的无故障运行的能力。对于一个高并发大流量的系统是至关重要的,系统出现故障比系统性能低更损伤用户的使用体验。

可用性的度量

可用性的度量主要是MTBFMTTR。

MTBF

是平均故障间隔的意思,代表两次故障的间隔时间,也就是系统正常运转的平均时间。这个时间越长,系统稳定性越高。

MTTR

表示故障的平均恢复时间,也可以理解为平均故障时间。这个值越小,故障对于用户的影响越小。

评估标准:

我们可以用下面的公式表示它们之间的关系:Availability = MTBF / (MTBF + MTTR)

image-20230418164414151
  • 一个九和两个九的可用性是很容易达到的,三个九就降到8个小时需要基本保证的。
  • 四个九之后可能需要建立完善的运维值班体系、故障处理流程和业务变更流程。
  • 五个九之后,故障就不能靠人力恢复了。

一般来说,核心业务系统的可用性,需要达到四个九,非核心系统的可用性最多容忍到三个九

高可用设计的思路

一个成熟系统的可用性需要从系统设计系统运维两方面来做保障。

  1. 系统设计

    在做系统设计的时候,要把发生故障作为一个重要的考虑点,预先考虑如何自动化地发现故障,发生故障之后要如何解决。还需要掌握一些具体的优化方法,比如 failover(故障转移)、超时控制以及降级和限流。

    • 故障转移:

      一般来说,发生 failover 的节点可能有两种情况:

      1. 是在完全对等的节点之间做 failover。

        在这种情况下,如果访问某一个节点失败,那么简单地随机访问另一个节点就好了。

      2. 是在不对等的节点之间,即系统中存在主节点也存在备节点。

        需要在代码中控制如何检测主备机器是否故障(使用最广泛的故障检测机制是“心跳”),以及如何做主备切换(选主的结果需要在多个备份节点上达成一致,所以会使用某一种分布式一致性算法,比方说 Paxos,Raft。)。

    • 超时控制:

      复杂的高并发系统通常会有很多的系统模块组成,同时也会依赖很多的组件和服务,它们之间的调用最怕的就是延迟而非失败(因为调用方就会阻塞在这次调用上,它已经占用的资源得不到释放。当存在大量这种阻塞请求时,调用方就会因为用尽资源而挂掉)。

      超时时间:我建议你通过收集系统之间的调用日志,统计比如说 99% 的响应时间是怎样的,然后依据这个时间来指定超时时间。

    • 降级:

      降级是为了保证核心服务的稳定而牺牲非核心服务的做法。所以当并发较高的情况下,它就有可能成为瓶颈,就会对非核心服务进行降级,关闭其服务,保证主体的流程更加稳定。

    • 限流:

      它通过对并发的请求进行限速来保护系统,对QPS进行限制。

  2. 系统运维

    在系统运维的层面可以从灰度发布、故障演练两个方面来考虑如何提升系统的可用性。

    • 灰度发布:

      灰度发布指的是系统的变更不是一次性地推到线上的,而是按照一定比例逐步推进的。

      例如:我们先在 10% 的机器上进行变更,同时观察 Dashboard 上的系统性能指标以及错误日志。如果运行了一段时间之后系统指标比较平稳并且没有出现大量的错误日志,那么再推动全量变更。

    • 故障演练:

      故障演练指的是对系统进行一些破坏性的手段,观察在出现局部故障时,整体的系统表现是怎样的,从而发现系统中存在的,潜在的可用性问题。

      当然,故障演练是以你的系统可以抵御一些异常情况为前提的。如果你的系统还没有做到这一点,那么我建议你另外搭建一套和线上部署结构一模一样的线下系统,然后在这套系统上做故障演练,从而避免对生产系统造成影响。

提高系统可扩展性

高可扩展性是一个设计的指标,它表示可以通过增加机器的方式来线性提高系统的处理能力,从而承担更高的流量和并发。

提高扩展性的困难点

集群系统中,不同的系统分层上可能存在一些“瓶颈点”,这些瓶颈点制约着系统的横向扩展能力。

例如:

  • 系统的流量是每秒 1000 次请求,对数据库的请求量也是每秒 1000 次。如果流量增加 10 倍;系统可以通过扩容正常服务,数据库却成了瓶颈。
  • 单机网络带宽是 50Mbps,那么如果扩容到 30 台机器,前端负载均衡的带宽就超过了千兆带宽的限制,也会成为瓶颈点

所以无状态的服务和组件更易于扩展,而像 MySQL 这种存储服务是有状态的,就比较难以扩展。我们要知道系统并发到了某一个量级之后,哪一个因素会成为我们的瓶颈点,从而针对性地进行扩展(例如数据库、缓存、依赖的第三方、负载均衡、交换机带宽等等都是系统扩展时需要考虑的因素)。

高扩展性设计思路

拆分是提升系统扩展性最重要的一个思路,它会把庞杂的系统拆分成独立的,有单一职责的模块。

案例:

假如你要设计一个社区,有5大模块:用户、关系、内容、评论、搜索。

并且采用三层部署架构:

  • 负载均衡负责请求的分发;
  • 应用服务器负责业务逻辑的处理;
  • 数据库负责数据的存储落地。
image-20230419164017552

数据库层面的拆分:

假如存储目前的瓶颈点是容量,那么我们只需要针对关系模块的数据做拆分就好了:

image-20230419164256243

这样拆分了,各个库就不会相互干扰;并且如果某个模块仍然超过单机限制,就需要对单模块进行二次拆分并且根据情况进行预分配避免频繁扩容(这次拆分就要按照数据特征做水平拆分,按照某算法将数据分到各个库中)。

当数据库按照业务和数据维度拆分之后,我们尽量不要使用事务。因为这个协调的成本会随着资源的扩展不断升高,最终达到无法承受的程度。

应用层面的拆分:

一般从三个维度考虑拆分:

  • 业务维度:

    就把同一类的业务拆分为一个单独的业务池,按照业务的维度拆分成用户池、内容池、关系池、评论池、点赞池和搜索池。

    这样当某个业务遇到了瓶颈只需要扩展该业务池子以及确定上下游依赖方就可以了。

  • 重要性维度:

    根据业务接口的重要程度拆分,把业务分为核心池非核心池

    当整体流量上升时优先扩容核心池,降级部分非核心池的接口,从而保证整体系统的稳定性。

  • 请求来源维度:

    还可以根据接入客户端类型的不同做业务池的拆分。例如小程序和网页端的拆分。