分布式/高性能/高可用

分布式✅

CAP理论

CAP理论是分布式系统设计中的一个重要理论。

  • 一致性(Consistency):所有节点访问同一份最新的数据副本
  • 可用性(Availability):非故障的节点在合理的时间内返回合理的响应(不是错误或者超时的响应)。
  • 分区容错性(Partition Tolerance):分布式系统出现网络分区的时候,仍然能够对外提供服务。

网络分区:分布式系统中的多节点网络原本是连通的,但因为故障导致某些节点间不连通了,网络分为几块区域。

当网络发生分区后,如果要继续服务的话,P是前提,必须要实现。然后在C和A之间二选一。因此分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。

如果网络分区正常的话(系统在绝大部分时候所处的状态),也就说不需要保证 P 的时候,C 和 A 能够同时保证。

为什么要首先保证P

  • 分布式系统的本质:分区容错性(P)是指系统在网络分区发生时,仍然能够继续提供服务的能力。在分布式系统中,网络分区是不可避免的,因此分区容错性是分布式系统必须具备的基本属性。
  • 保证系统的高可用性:如果不保证分区容错性,那么一旦网络分区发生,系统可能会因为无法处理分区而崩溃或停止服务,这将严重影响系统的可用性和稳定性。通过P确保系统的高可用性。
  • 符合分布式系统的设计理念:分布式系统的设计初衷就是为了提高系统的可扩展性、可靠性和容错性。如果放弃分区容错性,那么分布式系统就失去了其最重要的优势之一。
    • 在分布式系统中,通过将数据和服务分布在多个节点上,可以实现负载均衡、故障转移和容错处理等功能。这些功能的实现都依赖于分区容错性的支持。
  • 实际应用高并发的需求:在实际应用中,分布式系统往往需要处理大量的并发请求和数据,同时还需要面对各种复杂的网络环境和故障情况。
    • 如果不保证分区容错性,那么系统在面对这些挑战时可能会显得力不从心,无法满足实际应用的需求。

RPC框架

RPC(Remote Procedure Call)框架是一种允许程序调用另一个地址空间(通常是网络上的另一台机器)上的过程或函数,就像调用本地程序中的函数一样。RPC 框架隐藏了网络通信的复杂性,使得开发者可以更加专注于业务逻辑的实现,而不是底层的网络通信和序列化/反序列化等细节。

RPC 框架通常包含以下几个关键组件:

  • 客户端(Client):发起远程过程调用的程序。客户端负责将调用请求发送给服务器,并等待服务器的响应。
  • 服务端(Server):提供远程过程或函数供客户端调用的程序。服务端接收来自客户端的请求,执行相应的操作,并将结果返回给客户端。
  • 通信协议(Protocol):客户端和服务端之间通信所遵循的规则。这包括数据的编码方式、传输方式(如TCP/IP)、请求和响应的格式等。
  • 序列化/反序列化(Serialization/Deserialization):由于客户端和服务端可能运行在不同的机器上,它们之间的数据交换需要通过网络进行。序列化是将数据结构或对象状态转换成可以存储或传输的格式的过程,反序列化则是其逆过程。RPC 框架需要处理这些数据的序列化和反序列化。
  • 服务注册与发现(Service Registry and Discovery):在微服务架构中,服务注册与发现是一个重要的组成部分。服务注册允许服务实例向注册中心注册自己的信息,服务发现则允许客户端从注册中心查询所需服务的信息,以便进行远程调用。
  • 负载均衡(Load Balancing):当服务有多个实例时,负载均衡器负责将请求分发到不同的服务实例上,以提高系统的可用性和吞吐量。

在Java中,有多种RPC(远程过程调用)框架可供选择,这些框架为开发分布式系统提供了强大的支持。以下是一些常见的Java RPC框架:

  1. Dubbo
    简介:Dubbo是阿里巴巴开源的高性能RPC框架,具有简单易用、高性能、可扩展等特点。它支持多种协议和负载均衡策略,提供了服务注册、发现和调用的解决方案。
    特点:面向接口的远程方法调用、智能容错和负载均衡、服务自动注册和发现等。
    适用场景:广泛应用于许多大型互联网公司,特别是需要高性能和可扩展性的分布式系统。
  2. Spring Cloud
    简介:Spring Cloud是Spring家族的一个开源项目,它提供了许多分布式系统的解决方案,其中包括了RPC框架。它利用Spring Boot的特性,整合了开源行业中优秀的组件,为微服务架构提供了一整套服务治理的解决方案。
    特点:支持服务注册发现、服务调用、负载均衡等,可以方便地实现RPC通信。
    适用场景:适合需要快速构建微服务架构的Java应用。
  3. Thrift
    简介:Thrift是由Facebook开源的跨语言RPC框架,支持多种编程语言,包括Java。它使用接口描述语言(IDL)定义服务接口,通过生成和序列化代码来实现跨语言的通信。
    特点:高效的序列化和传输机制,支持多种传输协议和压缩算法,适用于各种复杂的分布式应用场景。
    适用场景:当系统需要支持多种编程语言,且对性能和效率有较高要求时,Thrift是一个很好的选择。
  4. gRPC
    简介:gRPC是由Google开源的高性能RPC框架,它使用Protocol Buffers作为接口描述语言,并使用HTTP/2作为传输协议。gRPC支持多种编程语言,包括Java。
    特点:简单易用、高效可靠,适用于构建微服务和移动应用后端服务。
    适用场景:当系统需要高性能的RPC调用,并且希望使用HTTP/2协议和Protocol Buffers作为序列化方式时,gRPC是一个不错的选择。
  5. Apache CXF
    简介:Apache CXF是一个开源的全功能的服务框架,它支持多种和Web服务相关的标准和协议,包括SOAP、REST和WS-*协议。Apache CXF提供了丰富的功能和扩展点,可以方便地实现RPC调用。
    特点:支持多种传输协议和安全机制,适用于构建复杂的分布式系统。
    适用场景:当系统需要支持多种Web服务标准和协议时,Apache CXF是一个很好的选择。
  6. 其他RPC框架
    除了上述几种常见的Java RPC框架外,还有一些其他的框架如RMI(基于JRMP通信协议)、Hessian(基于二进制RPC协议)等,它们各自具有不同的特点和适用场景。

Java中的RPC框架有多种选择,开发人员可以根据具体需求选择合适的框架来实现远程过程调用。这些框架的出现极大地简化了分布式系统的开发,提高了开发效率和系统性能。

高性能✅

CDN工作原理详解

CDN(Content Delivery Network/Content Distribution Network),内容分发网络,其将静态资源分发到多个不同的地方以实现就近访问,进而加快静态资源的访问速度,减轻服务器以及带宽的负担。

CDN和全站加速不同,全站加速既可以加速静态资源又可以加速动态资源,CDN主要针对静态资源。

  1. 基本概念
    • CDN节点:CDN节点是部署在不同地理位置的服务器。它们可以缓存内容并处理用户请求。
    • 源站:源站是内容的原始服务器,即CDN未介入时用户直接访问的服务器。
    • POP(Point of Presence):指的是一个CDN服务点,通常是一个小型的数据中心,包含多个CDN节点。
    • 缓存:CDN节点会缓存从源站获取的内容,以减少对源站的请求频率和用户的访问延迟。
  2. CDN的基本工作流程
    1. 用户请求内容:当用户访问一个使用CDN的网页时,用户的请求首先会被重定向到离用户最近的CDN节点。
    2. 查找缓存内容:
      • 缓存命中:如果该CDN节点已经缓存了所请求的内容(缓存命中),则直接将内容返回给用户。
      • 缓存未命中:如果缓存中没有请求的内容(缓存未命中),该节点会向源站发起请求以获取内容,然后将内容返回给用户,同时缓存该内容以供后续请求使用。
    3. 全局负载均衡:CDN利用全局负载均衡系统根据地理位置、服务器负载、网络状况等因素,将用户请求引导至最合适的节点。
    4. 内容刷新与失效:CDN可以根据配置设置内容的缓存时间,过期后需要重新从源站获取。同时,源站也可以主动通知CDN刷新或失效某些内容。
  3. CDN的优化
    • 预热:预热是指在 CDN 上提前将内容缓存到 CDN 节点上。
  4. 使用CDN的优势
    • 降低延迟:由于CDN节点分布在全球各地,用户请求可以在离用户最近的节点上得到响应,减少了物理距离带来的延迟。
    • 提高可用性:CDN通过分布式架构可以在某些节点发生故障时自动切换到其他节点,提高服务的可靠性。
    • 分担源站压力:通过将大量的内容缓存到CDN节点,减少了对源站的直接请求,特别是在流量高峰期,显著减轻了源站的压力。
    • 加速内容分发:CDN可以通过多种优化手段加速内容分发,提升用户体验。
  5. 常见的应用场景
    • 网站加速:静态内容(如图像、CSS、JavaScript文件)的加速分发。
    • 视频点播:将视频内容缓存到各个CDN节点,提高视频播放的流畅性。
    • 实时流媒体:利用CDN的低延迟特点,确保实时视频流的顺畅传输。
    • 下载加速:通过CDN分发大文件(如软件、游戏安装包等),加快下载速度。

如何找到最合适的 CDN 节点?

GSLB(Global Server Load Balance,全局负载均衡)是 CDN 的大脑,负责多个 CDN 节点之间相互协作,最常用的是基于 DNS 的 GSLB。CDN 会通过 GSLB 找到最合适的 CDN 节点:

  1. 浏览器向 DNS 服务器发送域名请求;
  2. DNS 服务器向根据 CNAME(Canonical Name) 别名记录向 GSLB 发送请求;
  3. GSLB 返回性能最好(通常距离请求地址最近)的 CDN 节点(边缘服务器,真正缓存内容的地方)的IP地址给浏览器;
  4. 浏览器根据IP地址直接访问指定的 CDN 节点。

负载均衡

负载均衡指的是将用户请求分摊到不同的服务器上处理,以提高系统整体的并发处理能力以及可靠性。

一般分为服务端负载均衡和客户端负载均衡:

  • 服务端负载均衡主要发生在网关层,可以使用软件(便宜,性能也够用)或者硬件(贵,但是性能好)实现。软件负载均衡通过如Nginx之类的软件实现,可在传输层、应用层实现负载均衡
    • 传输层主要协议是 TCP/UDP,该层能看到数据包里的源端口地址和目的端口地址,会基于这些信息通过一定的负载均衡算法将数据包转发到后端真实服务器,核心就是 IP+端口层面的负载均衡。
    • 应用层主要协议是 HTTP,该层的负载均衡会读取报文的数据部分,根据读取到的(URL、Cookie)做出负载均衡决策。执行第七层负载均衡的设备通常被称为 反向代理服务器。
  • 客户端负载均衡主要应用于系统内部的不同的服务之间。客户端会自己维护一份服务器的地址列表,发送请求之前,客户端会根据对应的负载均衡算法来选择具体某一台服务器处理请求。(通过Spring Cloud Load Balancer)

常见负载均衡算法

  1. 随机法:随机选择一台服务器处理请求。
    • 优点:实现简单,适用于分布较为均匀的场景。
    • 缺点:不保证请求的均匀分配,可能导致短时间内某些服务器负载过重。
  2. 轮询法:将请求依次分配给服务器,以循环的方式逐一选择服务器。
    • 优点:实现简单,能够较为均衡地分配请求。
    • 缺点:对于性能差异较大的服务器,可能导致某些服务器过载。
  3. 两次随机法:随机选择两台服务器,比较它们的负载,将请求分配给负载较轻的那台服务器。
    • 优点:较普通随机法更为均衡,性能也较为优越。
    • 缺点:仍存在一定的随机性,可能不完全均衡。
  4. 哈希法:根据请求的某些特征(如源IP、URL)计算哈希值,并根据哈希值选择相应的服务器。
    • 优点:对相同特征的请求分配到相同的服务器,适合需要会话保持的场景。
    • 缺点:难以应对服务器动态变化,如增加或减少服务器。
  5. 一致性Hash法:将服务器和请求都映射到一个哈希环上,请求沿环顺时针找到最近的服务器。
    • 优点:当服务器增加或减少时,只有一小部分请求会被重新分配,适合于动态变化的分布式系统。
    • 缺点:实现较为复杂,对哈希函数的选择有要求。
  6. 最小连接法:将请求分配给当前连接数最少的服务器。
    • 优点:动态调整负载,适用于长连接的场景。
    • 缺点:需要实时监控服务器的连接数,较为复杂。
  7. 最少活跃法:将请求分配给当前处理请求最少的服务器。
    • 优点:能更精确地反映服务器的实时负载,较为公平地分配请求。
    • 缺点:实现和计算较为复杂。
  8. 最快响应时间法:将请求分配给响应时间最快的服务器。
    • 优点:可以提供更好的用户体验,适用于对响应时间要求较高的场景。
    • 缺点:实现难度较大,需要实时监控和记录每台服务器的响应时间。

消息队列

消息队列是一种存放消息的容器,当需要使用消息的时候,直接从容器中取出使用即可。参与消息传递的双方是生产者和消费者,生产者负责生产并发送消息,消费者负责处理消息。

这里提到的消息队列不是操作系统进程通信中的消息队列,而是各个服务以及系统内部各个组件/模块之前的通信,属于一种中间件。
中间件(英语:Middleware),又译中间件、中介层,是一类提供系统软件和应用软件之间连接、便于软件各部件之间的沟通的软件,应用软件可以借助中间件在不同的技术架构之间共享信息与资源。中间件位于客户机服务器的操作系统之上,管理着计算资源和网络通信。

高可用✅

什么是高可用

高可用描述的是一个系统在大部分时间都是可用的,可以提供服务的。高可用代表系统即使在发生硬件故障或者系统升级的时候,服务仍然是可用的。

  • 99.9999% 就是代表该系统在所有的运行时间中只有 0.0001% 的时间是不可用的,这样的系统就是非常非常高可用的了
  • 系统的可用性还可以用某功能的失败次数与总的请求次数之比来衡量,比如对网站请求 1000 次,其中有 10 次请求失败,那么可用性就是 99%。

哪些情况会导致系统不可用?

  • 黑客攻击;
  • 硬件故障,比如服务器坏掉。
  • 并发量/用户请求量激增导致整个服务宕掉或者部分服务不可用。
  • 代码中的坏味道导致内存泄漏或者其他问题导致程序挂掉。
  • 网站架构某个重要的角色比如 Nginx 或者数据库突然不可用。
  • 自然灾害或者人为破坏。
  • ……

提高系统高可用的方法

注重代码质量,测试严格把关
代码质量有问题比如比较常见的内存泄漏、循环依赖都是对系统可用性极大的损害。比较实际可用的提高代码质量方法就是 CodeReview。

使用集群,减少单点故障
比如使用一个 Redis 实例作为缓存的时候,这个 Redis 实例挂了之后,整个缓存服务可能就挂了。使用了集群之后,即使一台 Redis 实例挂了,不到一秒就会有另外一台 Redis 实例顶上。

限流
流量控制,其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

超时和重试机制设置
一旦用户请求超过某个时间的得不到响应,就抛出异常。这个是非常重要的,很多线上系统故障都是因为没有进行超时设置或者超时设置的方式不对导致的。在读取第三方服务的时候,尤其适合设置超时和重试机制。一般使用一些 RPC 框架的时候,这些框架都自带的超时重试的配置。如果不进行超时设置可能会导致请求响应速度慢,甚至导致请求堆积进而让系统无法再处理请求。重试的次数一般设为 3 次,再多次的重试没有好处,反而会加重服务器压力(部分场景使用失败重试机制会不太适合)。

熔断机制
超时和重试机制设置之外,熔断机制也是很重要的。 熔断机制说的是系统自动收集所依赖服务的资源使用情况和性能指标,当所依赖的服务恶化或者调用失败次数达到某个阈值的时候就迅速失败,让当前系统立即切换依赖其他备用服务。 比较常用的流量控制和熔断降级框架是 Netflix 的 Hystrix 和 alibaba 的 Sentinel。

异步调用
异步调用的话我们不需要关心最后的结果,这样就可以用户请求完成之后就立即返回结果,具体处理我们可以后续再做,秒杀场景用这个还是蛮多的。

使用缓存
如果系统属于并发量比较高的话,如果单纯使用数据库的话,当大量请求直接落到数据库可能数据库就会直接挂掉。使用缓存缓存热点数据,因为缓存存储在内存中,所以速度相当地快!

其他

  • 核心应用和服务优先使用更好的硬件
  • 监控系统资源使用情况增加报警设置。
  • 注意备份,必要时候回滚。
  • 灰度发布:将服务器集群分成若干部分,每天只发布一部分机器,观察运行稳定没有故障,第二天继续发布一部分机器,持续几天才把整个集群全部发布完毕,期间如果发现问题,只需要回滚已发布的一部分服务器即可
  • 定期检查/更换硬件: 如果不是购买的云服务的话,定期还是需要对硬件进行一波检查的,对于一些需要更换或者升级的硬件,要及时更换或者升级。
  • ……

冗余设计

冗余设计是保证系统和数据高可用的最常的手段。

  • 对于服务来说,冗余的思想就是相同的服务部署多份,如果正在使用的服务突然挂掉的话,系统可以很快切换到备份服务上,大大减少系统的不可用时间,提高系统的可用性。
  • 对于数据来说,冗余的思想就是相同的数据备份多份,这样就可以很简单地提高数据的安全性。

高可用集群(High Availability Cluster,简称 HA Cluster)、同城灾备、异地灾备、同城多活和异地多活是冗余思想在高可用系统设计中最典型的应用。

  • 高可用集群:同一份服务部署两份或者多份,当正在使用的服务突然挂掉的话,可以切换到另外一台服务,从而保证服务的高可用。
  • 同城灾备:一整个集群可以部署在同一个机房,而同城灾备中相同服务部署在同一个城市的不同机房中。并且,备用服务不处理请求。这样可以避免机房出现意外情况比如停电、火灾。
  • 异地灾备:类似于同城灾备,不同的是,相同服务部署在异地(通常距离较远,甚至是在不同的城市或者国家)的不同机房中
  • 同城多活:类似于同城灾备,但备用服务可以处理请求,这样可以充分利用系统资源,提高系统的并发。
  • 异地多活:将服务部署在异地的不同机房中,并且,它们可以同时对外提供服务。

常见限流算法

固定窗口计数器算法

原理是将时间划分为固定大小的窗口,在每个窗口内限制请求的数量或速率,即固定窗口计数器算法规定了系统单位时间处理的请求数量。
假如规定系统中某个接口 1 分钟只能被访问 33 次的话,使用固定窗口计数器算法的实现思路如下:

  • 将时间划分固定大小窗口,这里是 1 分钟一个窗口。
  • 给定一个变量 counter 来记录当前接口处理的请求数量,初始值为 0(代表接口当前 1 分钟内还未处理请求)。
  • 1 分钟之内每处理一个请求之后就将 counter+1 ,当 counter=33 之后(也就是说在这 1 分钟内接口已经被访问 33 次的话),后续的请求就会被全部拒绝。
  • 等到 1 分钟结束后,将 counter 重置 0,重新开始计数。

优点:实现简单,易于理解。
缺点:

  • 限流不够平滑。例如限制某个接口每分钟只能访问 30 次,假设前 30 秒就有 30 个请求到达的话,那后续 30 秒将无法处理请求,这是不可取的,用户体验极差!
  • 无法保证限流速率,因而无法应对突然激增的流量。例如限制某个接口 1 分钟只能访问 1000 次,该接口的 QPS 为 500,前 55s 这个接口 1 个请求没有接收,后 1s 突然接收了 1000 个请求。然后,在当前场景下,这 1000 个请求在 1s 内是没办法被处理的,系统直接就被瞬时的大量请求给击垮了。

滑动窗口计数器算法

滑动窗口计数器算法限流的颗粒度更小,其把固定窗口算法中的固定窗口再次划分为若干片。

例如接口限流每分钟处理 60 个请求,可以把 1 分钟分为 60 个窗口。每隔 1 秒移动一次,每个窗口一秒只能处理不大于 60(请求数)/60(窗口数)的请求,如果当前窗口的请求计数总和超过了限制的数量的话就不再处理其他请求。很显然,当滑动窗口的格子划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。

优点:

  • 相比于固定窗口算法,滑动窗口计数器算法可以应对突然激增的流量。
  • 相比于固定窗口算法,滑动窗口计数器算法的颗粒度更小,可以提供更精确的限流控制。

缺点:

  • 与固定窗口计数器算法类似,滑动窗口计数器算法依然存在限流不够平滑的问题。
  • 相比较于固定窗口计数器算法,滑动窗口计数器算法实现和理解起来更复杂一些。

漏桶算法

可以把发请求的动作比作成注水到桶中,处理请求的过程可以比喻为漏桶漏水。往桶中以任意速率流入水,以一定速率流出水。当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。如果想要实现这个算法的话也很简单,准备一个队列用来保存请求,然后定期从队列中拿请求来执行就好了(和消息队列削峰/限流的思想是一样的)。

漏桶算法

优点:

  • 实现简单,易于理解。
  • 可以控制限流速率,避免网络拥塞和系统过载。

缺点:

  • 无法应对突然激增的流量,因为只能以固定的速率处理请求,对系统资源利用不够友好。
  • 桶流入水(发请求)的速率如果一直大于桶流出水(处理请求)的速率的话,那么桶会一直是满的,一部分新的请求会被丢弃,导致服务质量下降。

实际业务场景中,基本不会使用漏桶算法。

令牌桶算法

和漏桶算法算法一样,过现在桶里装的是令牌了,请求在被处理之前需要拿到一个令牌,请求处理完毕之后将这个令牌丢弃(删除)。根据限流大小,按照一定的速率往桶里添加令牌。如果桶装满了,就不能继续往里面继续添加令牌了。
令牌桶算法

优点:

  • 可以限制平均速率和应对突然激增的流量。
  • 可以动态调整生成令牌的速率。

缺点:

  • 如果令牌产生速率和桶的容量设置不合理,可能会出现问题比如大量的请求被丢弃、系统过载。
  • 相比于其他限流算法,实现和理解起来更复杂一些。

针对什么来进行限流?

实际项目中,还需要确定限流对象,也就是针对什么来进行限流。常见的限流对象如下:

  • IP :针对 IP 进行限流,适用面较广,简单粗暴。
  • 业务 ID:挑选唯一的业务 ID 以实现更针对性地限流。例如,基于用户 ID 进行限流。
  • 个性化:根据用户的属性或行为,进行不同的限流策略。例如, VIP 用户不限流,而普通用户限流。根据系统的运行指标(如 QPS、并发调用数、系统负载等),动态调整限流策略。例如,当系统负载较高的时候,控制每秒通过的请求减少。

单机限流怎么做

可使用令牌桶算法

分布式限流怎么做

分布式限流针对的分布式/微服务应用架构应用,在这种架构下,单机限流就不适用了,因为会存在多种服务,并且一种服务也可能会被部署多份。
分布式限流常见的方案:

  • 借助中间件限流:可以借助 Sentinel 或者使用 Redis 来自己实现对应的限流逻辑。
  • 网关层限流:比较常用的一种方案,直接在网关层把限流给安排上了。不过,通常网关层限流通常也需要借助到中间件/框架。就比如 Spring Cloud Gateway 的分布式限流实现RedisRateLimiter就是基于 Redis+Lua 来实现的,再比如 Spring Cloud Gateway 还可以整合 Sentinel 来做限流。

如果你要基于 Redis 来手动实现限流逻辑的话,建议配合 Lua 脚本来做。为什么建议 Redis+Lua 的方式?主要有两点原因:

  • 减少了网络开销:可以利用 Lua 脚本来批量执行多条 Redis 命令,这些 Redis 命令会被提交到 Redis 服务器一次性执行完成,大幅减小了网络开销。
  • 原子性:一段 Lua 脚本可以视作一条命令执行,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰。

降级

降级指的是在服务压力过大或部分功能出现故障时,主动减少或关闭某些非核心功能,从而确保核心功能的正常运行。通过降级,系统可以在不影响主要功能的情况下,减轻负载,避免因部分功能故障导致整个系统不可用。

应用场景:

  • 系统负载过高:当系统承受的请求量过大,可能会导致性能下降。此时,可以通过关闭一些耗资源的非关键功能,确保核心服务的响应速度。
  • 依赖服务异常:如果系统依赖的某个外部服务发生故障或延迟过大,可以选择临时关闭与该服务相关的功能,而不是完全停掉系统的服务。
  • 业务需求波动:在某些特殊时期(如促销活动),为了应对突增的流量,可以提前降级部分非核心功能。

实现方式:

  • 关闭某些功能:通过开关、配置中心等手段,临时禁用部分功能或模块。
  • 提供默认值:在依赖服务不可用时,返回默认数据或缓存数据。
  • 减少服务质量:降低服务的质量,例如降低图像分辨率、减少查询结果数量等。

优势:

  • 保障系统核心功能的可用性。
  • 减轻系统负载,避免雪崩效应。

熔断

熔断是一种故障隔离机制,当系统某个组件(如外部服务)出现问题时,熔断器会自动切断对该组件的请求,从而避免故障蔓延到整个系统。熔断器在一段时间后会尝试恢复连接,如果故障消失,系统会恢复正常调用。

熔断的状态:

  • 关闭状态(Closed):正常情况下,所有请求都通过,熔断器处于关闭状态。
  • 打开状态(Open):当外部服务持续出现故障,超过一定阈值时,熔断器切换到打开状态,直接拒绝请求并返回错误响应。
  • 半开状态(Half-Open):熔断器在一段时间后尝试恢复连接,允许少量请求通过,测试外部服务是否恢复正常。如果请求成功,熔断器切回到关闭状态;如果失败,继续保持打开状态。

应用场景:

  • 依赖服务不可用:当依赖的外部服务出现异常或性能下降时,频繁的调用失败会导致资源浪费和系统阻塞。此时,熔断器可以及时切断这些无效请求,保护系统的其他部分不受影响。
  • 防止级联故障:在分布式系统中,如果一个服务的故障导致下游服务的负载激增,可能引发连锁反应。熔断可以防止这种级联故障的发生。

实现方式:

  • 错误率监控:根据外部服务的错误率或响应时间来判断是否触发熔断。
  • 超时设置:如果外部服务的响应时间超过设定的阈值,触发熔断。
  • 自动恢复:熔断器在一段时间后自动尝试恢复连接。

优势:

  • 防止故障扩散,保障系统的稳定性。
  • 提高系统的容错能力和恢复能力。

降级与熔断的区别

  • 目标不同:降级的目标是保障核心功能在高负载或异常情况下仍然可用;熔断的目标是防止系统因依赖的某个组件故障而出现更大范围的故障。
  • 触发条件不同:降级通常是基于系统的负载、请求量等情况进行主动调整;熔断则是基于对外部服务的健康状态监控进行被动触发。
  • 恢复方式不同:降级通常需要手动干预恢复,比如流量减少后手动恢复被关闭的功能;熔断则具有自动恢复机制,当外部服务恢复正常后熔断器会自动切换到关闭状态。

超时机制

超时机制用于防止请求长时间等待而不返回结果。它为每个请求设定一个最大等待时间,一旦超过这个时间,系统就会认为该请求失败,从而采取相应的措施(如重试、降级或直接返回错误)。

关键点:

  • 超时的设定:超时时间应根据具体业务需求、网络延迟和系统性能来合理设定。超时过短可能导致误判,超时过长又可能影响用户体验和系统响应时间。
  • 超时的作用:防止资源的长期占用,减少系统的线程或连接被长时间挂起,从而保持系统的响应能力。
  • 分级超时:在复杂的分布式系统中,不同的服务或组件可以有不同的超时设置,以适应各自的性能特点和业务需求。

示例:
在微服务架构中,假设服务 A 需要调用服务 B,B 可能由于各种原因(如高负载、网络抖动)无法及时响应。A 可以设置一个超时时间(如 2 秒),如果 B 在 2 秒内没有响应,A 会认为调用失败并处理该情况。

重试机制

重试机制用于在请求失败时自动重试,以应对临时性故障。重试可以显著提高成功率,特别是在分布式系统中,网络故障、资源争用等问题可能只是暂时的。

关键点:

  • 重试策略:重试机制需要设计合理的策略,包括:
    • 重试次数:设定最大重试次数,防止无限重试导致系统过载。
    • 重试间隔:设置重试之间的等待时间,可以是固定时间间隔,也可以是指数退避(每次重试间隔逐渐增加)。
    • 重试条件:明确哪些错误或状态需要重试,如网络超时、连接中断等。
  • 幂等性考虑:重试机制要求操作是幂等的,即同一操作多次执行不会产生副作用。如果操作不可避免地产生副作用(如扣款操作),需要设计幂等处理逻辑。如购买商品时判断是否已经购买过了。

示例:
在支付系统中,用户支付请求可能由于网络抖动而失败。系统可以在支付失败后自动重试 3 次,每次间隔 1 秒。如果第 3 次重试后仍然失败,则返回错误给用户。

超时与重试的协作

超时和重试通常结合使用,以实现更高的可用性:

  • 超时后重试:请求在超时后进行重试,直到达到最大重试次数。
  • 分布式场景中的挑战:在分布式系统中,超时和重试可能会放大问题,例如请求风暴或级联故障。因此,在设计时要特别注意这些可能的副作用。

总结:

  • 超时机制防止请求长时间挂起,提升系统的资源利用效率。
  • 重试机制则通过自动化的重试操作,提高请求的成功率和系统的容错能力。
  • 两者结合使用时,需要精心设计超时和重试策略,以确保系统的高可用性和稳定性。

性能测试

性能测试

性能测试方法是通过测试工具模拟用户请求系统,目的主要是为了测试系统的性能是否满足要求。通俗地说,这种方法就是要在特定的运行条件下验证系统的能力状态。性能测试是你在对系统性能已经有了解的前提之后进行的,并且有明确的性能指标。

负载测试

对被测试的系统继续加大请求压力,直到服务器的某个资源已经达到饱和了,比如系统的缓存已经不够用了或者系统的响应时间已经不满足要求了。负载测试说白点就是测试系统的上限。

压力测试

不去管系统资源的使用情况,对系统继续加大请求压力,直到服务器崩溃无法再继续提供服务。

稳定性测试

模拟真实场景,给系统一定压力,看看业务是否能稳定运行。

常见性能优化策略

性能优化之前需要对请求经历的各个环节进行分析,排查出可能出现性能瓶颈的地方,定位问题。

  1. 系统是否需要缓存?
  2. 系统架构本身是不是就有问题?
  3. 系统是否存在死锁的地方?
  4. 系统是否存在内存泄漏?(Java 的自动回收内存虽然很方便,但是,有时候代码写的不好真的会造成内存泄漏)
  5. 数据库索引使用是否合理?

相关指标

  • QPS(Query Per Second):服务器每秒可以执行的查询次数;
  • TPS(Transaction Per Second):服务器每秒处理的事务数(这里的一个事务可以理解为客户发出请求到收到服务器的过程);
  • RT:响应时间RT(Response-time)就是用户发出请求到用户收到系统处理结果所需要的时间。
  • 并发数:可以简单理解为系统能够同时供多少人访问使用也就是说系统同时能处理的请求数量。
  • 吞吐量:吞吐量指的是系统单位时间内系统处理的请求数量。

QPS(TPS) = 并发数/平均响应时间(RT)
并发数 = QPS * 平均响应时间(RT)

QPS vs TPS:QPS 基本类似于 TPS,但是不同的是,对于一个页面的一次访问,形成一个 TPS;但一次页面请求,可能产生多次对服务器的请求,服务器对这些请求,就可计入“QPS”之中。如,访问一个页面会请求服务器 2 次,一次访问,产生一个“T”,产生 2 个“Q”。

处理上限为100的接口,突然10000个请求过来了,怎么办?

当接口突然接收到超出其处理能力的大量请求时,需要采取一些策略来防止系统过载,并确保服务的可用性。

  • 限流:通过限制每个时间单位内允许处理的请求数量来防止过载。常见的限流算法包括漏桶算法和令牌桶算法。
  • 负载均衡:使用负载均衡器将请求分配到多个服务器上,以均衡负载。可以使用软件解决方案(如 Nginx 或 Apache)。
  • 缓存:缓存可以减轻数据库和后端服务的压力。对于一些可以缓存的请求结果,可以使用 Redis 进行缓存。
  • 降级:在高负载时,可以对某些非关键功能进行降级,例如延迟处理或返回默认值。
  • 消息队列:将请求放入消息队列中进行异步处理,如使用 Kafka、RabbitMQ 或 ActiveMQ。
  • 扩展资源:根据实际情况扩展服务器资源,包括水平扩展(增加服务器数量)和垂直扩展(增加单个服务器的处理能力)。