前言
随着现代软件系统日益复杂和用户规模的不断增长,分布式架构成为了保持系统高可用性与高性能的标准解决方案。然而,随之而来的是对系统容量治理的新挑战。在这样的背景下,容量治理成为了分布式系统设计和运维中不可或缺的一环。要确保系统既能够应对高峰期的巨大访问压力,又能在用户较少时优化资源使用,我们必须掌握并运用容量治理的"三板斧":扩容、限流与降级。本篇文章将深入介绍这三种常见的架构理念,帮助读者理解它们的工作原理及应用场景,以及如何有效地将这些理念整合到我们的分布式架构策略中。
正文
在我们进行分布式架构设计的时候,需要对当前业务流量进行评估,可以从容量治理三板斧:扩容、限流与降级方面来建立系统的冗灾体系,将潜在的系统故障带来的损失,降到最低。
扩容
当我们系统采用的是微服务的架构的时候,我们理论上可以通过增加机器的方式,来提高我们整个系统处理业务请求的吞吐量。在进行扩容的时候我们需要了解负载均衡和预热,从而达到扩容提升系统的并发能力的目标。
负载均衡
负载均衡通常有两种方式:分别是四层负载均衡与七层负载均衡,它们的的区别在于:
- 四层交换机通过解析TCP头等协议的内容,来决定分流的目的地;
- 七层交换机则通过解析软件应用层的内容来决定分流的目的地。
- 四层是基于IP+端口号进行负载均衡的,七层是基于URL进行负载均衡的。
- 七层对负载均衡的设备要求更高,性能方面比四层弱。
- 七层负载均衡更加智能化,安全性上更加有保障。
负载均衡实现方案
常见负载均衡的方法有:
- 利用Http重定向负载均衡:即利用nginx中的rewirte模块来实现。(客户端行为,不需要服务器转发响应,这种方法性能比较差)
- DNS域名解析负载均衡:DNS负载均衡的控制权在域名服务商中,通常作为第一级负载均衡手段。(不需要服务器转发响应)
- 反向代理负载均衡:即利用nginx中的upstream模块来实现(配置简单,但性能也有局限性,响应要通过反向代理服务器返回给客户端)
- IP负载均衡:即利用网关服务器实现负载均衡(性能有提升,响应要通过网关服务器,受限于其网卡带宽)
- 数据链路层负载均衡:在通讯协议的数据链路层修改mac地址进行负载均衡。(不需要服务器转发响应,最常见的是LVS)
负载均衡算法
- 轮询:所有请求被依次分发到每台应用服务器上,即每台服务器需要处理的请求数目都相同,适合于所有服务器硬件都相同的场景。
- 加权轮询:在轮询的基础上,按照配置的权重将请求分发到每个服务器,高性能的服务器能分配更多的请求。
- 随机:请求被随机分配到各个应用服务器,即使应用服务器配置不同,可以加权随机算法。(这种方法最常用)
- 最小连接:记录每一个应用服务器正在处理的连接数,将新的请求分发到最少连接的服务器,也支持加权最小连接。
- Hash选择:对请求的IP地址进行hash计算,实现同一个IP地址的请求总在一个服务器上处理。(实现对有状态应用服务器高可用的一种方式)
预热
缓存预热
缓存预热的策略
- 静态预热:系统启动时,根据历史访问模式或预期的访问模式,加载最常访问的数据到缓存中。
- 动态预热:通过监控工具来捕捉最近的访问模式和频率,动态地预热数据。
- 标准化脚本:编写脚本按照预定规则提前加载缓存数据。
- 后台处理:通过设置后台任务,定期地进行数据预热,例如在系统流量较低时预加载第二天特别活动的相关数据。
- 热点数据预热:针对访问频率高的数据(热点数据),在缓存失效前主动更新缓存。
机器预热
JIT技术
在Java中,解释执行模式通常指Java程序运行时,Java虚拟机(JVM)会逐条将字节码指令转换(解释)成机器码然后执行,而不是将整个程序编译成机器语言再执行。
JVM实现引入了即时编译(Just-In-Time, JIT)技术,即在程序运行时动态地将字节码编译成机器码,这样热点代码(经常执行的代码)就可以直接以编译后的机器码运行,从而提高了执行效率。
使用-XX:CICompilerCount参数可以来设置编译线程数目,这个值默认是2。
机器预热的方式
-
程序主动预热:在启动完成后,程序主动的访问热点的代码,确保主要的热点代码已被编译成机器码后再放入流量,可通过-XX:+PrintCompilation来确认。
-
复制流量预热:通过tcpcopy软件拷贝一份线上nginx的流量进行预热,完成之后再导入线上流量。
限流
限流算法
我们常见的限流算法有:
- 漏桶算法
- 令牌桶算法
- 滑动窗口算法
漏桶算法
nginx的limit_conn_zone模块底层就是漏桶算法,它的特点是:
- 它可以简单的比作就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。
- 它能够强行限制数据的传输速率,但是由于流速是恒定的,对突发特性的流量是无法处理的 。
令牌桶算法
- 能够在限制数据的平均传输速率的同时还允许某种程度的突发传输。
- 令牌桶算法是最常用的限流算法,通常配合redis来实现。
令牌桶算法的组件
- 计数器:实时地统计接口的调用速率,记录单位时间内调用了多少次。
- 限流器:当令牌桶中令牌已经无法放入时,使用限流器进行限流。
- 阈值配置:用于配置时间窗口,平均速率以及突发情况下允许的最大速率
- 阈值更新:通过一个参数来实时调整对阈值的配置
- 令牌桶:通过一个工具类根据配置的时间窗口、平均速率、突发最大速率来模拟向桶中放置令牌的操作,如果溢出了,则触发限流器操作。
它的工作原理可以简单概括为:
- 所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
- 根据限流大小,设置按照一定的速率往桶里添加令牌;
- 桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
- 请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
- 令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流;
知识点:
a.在某种极端情况下存在请求量两倍进入:
- 令牌桶比如以 1个/1秒 速率进入时,请求又是以 2个/1秒进入,这个时候令牌桶并没有满,请求数就是令牌数的两倍,
- 如果超过平均速率的突发流量持续的时间过长,并且令牌桶的令牌没有得到释放,那么存在请求数是令牌桶大小的两倍。
滑动窗口算法
我们熟知的Sentinel高可用流量控制框架采用的就是滑动窗口的方式进行流量控制。
- 它将时间窗口划分为更小的时间片段,每过一个时间片段,我们的时间窗口就会往右滑动一格,每个时间片段都有独立的计数器。
- 我们在计算整个时间窗口内的请求总数时会累加所有的时间片段内的计数器。
- 时间窗口划分的越细,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。
简单举个例子:我们设置一个窗口有两个方案 - 设置 A :10s 100条请求
- 设置 B :1s 10条请求
相对而言设置B的系统相对而言更加稳定。
总得来说:
- 令牌桶算法:能够保证自身系统的流量均匀,令牌桶的填满时间,是由桶的自身容量、令牌漏出速率(桶下面的水管)、超过平均速率的突发流量持续的时间三个方面共同决定的。
- 漏桶算法:保证被调用系统(目标系统)流量均匀,适合请求到达频率稳定、需要严格控制处理速率的场景,而令牌桶适合允许突发请求的情况。
实际的生产中通常采用 漏桶算法+令牌桶算法的方式来对网络流量进行高效地控制。
限流设计
我们在评估系统业务的流量模型时,常常可以梳理出类似于下图"漏斗式"的流量图,我们可以根据实际的流量对每一层级的业务接口设置合理的限流值。
sentinel 限流
Sentinel 是面向微服务的轻量级流量控制框架,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel的限流原理
- Sentinel以Bucket(桶)为单位记录一个时间窗口内的请求总数、异常总数、总耗时等指标数据。
- 而一个Bucket可以是记录一秒内的数据,也可以是10毫秒内的数据,我们称这个时间窗口为Bucket的统计单位,由使用者自定义。
所以Sentinel是基于滑动窗口算法来实现的。
降级
缓存降级
使用缓存的方式,是我们最常用的性能优化的方式,某种意义它也是一种业务降级的方式,通过牺牲一定数据一致性换取高性能。
四层缓存
- 基于浏览器等设备的客户端缓存
- 基于CDN加速的网络层缓存,通过CDN能够实现对页面的缓存
- 基于Ngnix等负载均衡组件(proxy_cache和 squid)的路由层缓存
- 基于Redis等的业务层缓存
业务层的缓存可以细分:
- 一级缓存(会话级缓存):在维持一个会话时,查询获取的数据会存放在一级缓存中,下次使用从缓存中获取。
- 二级缓存(应用级缓存):当会话关闭时,一级缓存的数据会保存在二级缓存中。
- 三级缓存(数据库级缓存):可以实现跨jvm,通过远程调用的方式实现数据同步。
业务降级
非核心组件降级
针对核心业务服务的压力剧增,根据当前业务场景和流量对其他非核心服务进行降级处理,比如进行限流、业务组件降级等方式,快速返回等处理,释放资源保证核心任务的正常运行。
设计异常情况下的最短交易路径
当系统发生短时间难以恢复的问题的时候,我们要梳理出"商品下单"的最短交易路径,保证核心链路可用。等待系统逐步恢复正常的时候,我们可以采用异步的方式,对一些缺失的数据进行补齐。
总结
在面对不断波动的用户请求与系统负载时,一个精心设计的分布式架构必须具备出色的容量治理能力。在众多治理策略中,扩容、限流与降级无疑是最为有效的"三板斧"。它们不仅确保了在用户访问激增时系统的稳定性和响应速度,也充分考虑了成本效益,最大化了资源的利用效率。扩容提供了处理高峰流量的必要硬件支持,限流则确保了系统不会因超载而崩溃,而降级则在必要时牺牲部分服务质量,保证了核心功能的正常运行。