如何设计高并发系统?
分布式架构
将系统分解成多个模块,采用分布式架构来降低单点故障的风险,并提高系统的可伸缩性和性能。
集群部署
将一个服务通过集群进行部署,来提升系统整体的吞吐量及响应速度,并使用负载均衡技术将请求均衡分配给多个服务器,以提高系统的性能和可用性。
利用缓存
使用缓存、NoSQL等技术,以提高数据读写的性能和可靠性。
异步处理
采用异步处理机制,如使用消息队列、事件驱动等技术,以降低请求响应时间和提高系统吞吐量。
预加载
使用预加载技术来提前加载需要的资源,以减少用户等待时间。
代码优化和调优
对系统代码进行优化和调优,如采用异步I/O、避免锁(减小锁的粒度)、减少循环和递归、避免长事务等,以提高系统性能。
数据库优化
合理的数据库设计和优化,包括合理的索引设计、分库分表、读写分离、缓存优化等,可以有效提高系统的并发度和响应速度。
分库分表
将一个大型的数据库拆分成多个小型的数据库(分库),然后将每个小型数据库中的表再进行拆分(分表),从而减轻单个数据库或表的读写压力,通过分库分表,可以将大量的读写操作分散到多个数据库或表中,从而提高系统的并发度和响应速度。
读写分离
读写分离是一种常用的数据库优化技术,它将读操作和写操作分配到不同的数据库实例上处理。通过读写分离,主库主要负责写操作,从库则负责读操作,从而提高了系统的并发度和可扩展性。同时,读写分离还可以提高系统的可用性和容错能力,因为即使主库出现故障,从库仍然可以提供读服务。
防止雪崩
通过使用限流、熔断、降级等技术,可以防止系统因为某个组件出现故障而导致整个系统崩溃的雪崩效应。
容错和监控
实现容错机制,如备份、容灾、负载降级等,以保障系统的可用性。同时,使用监控工具来实时监测系统的运行状况和性能瓶颈,及时做出调整和优化。
测试和评估
进行全面的性能测试和评估,包括压力测试、负载测试、安全测试等,以发现并解决系统的性能瓶颈和安全隐患。
降级
降级是通过开关配置将某些不重要的业务功能屏蔽掉,以提高服务处理能力。在大促场景中经常会对某些服务进行降级处理,大促结束之后再进行复原。
区别于熔断机制,降级一般并不是彻底功能不可用,而是用一种默认返回、异步执行、延迟处理等方式进行降低处理。
降级的方式
延迟服务
- 在粒度范围内关闭服务(片段降级或服务功能降级)
• 比如关闭相关文章的推荐,直接关闭推荐区
- 页面异步请求降级
• 比如商品详情页上有推荐信息/配送至等异步加载的请求,如果这些信息响应慢或者后端服务有问题,可以进行降级; 页面跳转(页面降级)
比如可以有相关文章推荐,但是更多的页面则直接跳转到某一个地址。
- 写降级
• 比如秒杀抢购,我们可以只进行Cache的更新,然后异步同步扣减库存到DB,保证最终一致性即可,此时可以将DB降级为Cache。
- 读降级
• 比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这种方式适用于对读一致性要求不高的场景;
• 11月11日的零点到11月12日的零点之间无法退款,其实是采用了关闭服务这种降级方式
降级的介入方式
自动开关降级
- 服务超时
• 当访问的数据库/http服务/远程调用响应慢或者长时间响应慢,且该服务不是核心服务的话可以在超时后自动降级;
- 失败次数
• 调用外部服务的时候,除了超时以外,最常见的异常情况就是调用失败。比如详情页中的库存信息,如果是某一次查询请求失败了,那么就可以通过读取缓存数据等方式直接降级掉。
- 发生故障
• 上面提到的失败可能是服务不稳定造成的,过一段时间可以自动恢复的。还有一种情况可能是依赖的服务彻底跪了、或者网络不通了等等。这种情况就可以直接降级了。
当HTTP请求返回固定的错误码、或者一个RPC请求的时候底层服务抛了异常以后,就认为有故障发生,对其进行降级即可。
- 限流降级
• 对于某些功能,设定一个流量阈值,一旦流量达到阈值的话,就进行降级。
• 比如秒杀功能,如果一瞬间流量太大,就可以进行限流降级。对于后续访问的用户直接提示已售空、跳转错误页、或者让他输入验证码重试等。
人工开关降级
- 人工开关降级的方式是指当系统维护人员在发现系统异常之后,通过人工修改参数、关闭服务等方式进行降级的方法。
- Hystrix 的关注点在于以 隔离 和 熔断 为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供 fallback 机制。
- Sentinel 是阿里中间件团队开源的,面向分布式服务架构的轻量级高可用流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性。
降级工具
Hystrix
Sentinel
• Sentinel 的侧重点在于:多样化的流量控制、熔断降级、系统负载保护、实时监控和控制台等
对比
熔断
在服务的依赖调用中,当被调用方出现故障时,出于自我保护的目的,调用方会主动停止调用,并根据业务需要进行相应处理。调用方这种主动停止调用的行为我们称之为熔断。
熔断器模式
熔断器会侦测错误并且“预防”应用程序不断地重试调用一个近乎毫无回应的服务(除非该服务已经安全到可重试连线了)。
包含三种状态
- 关闭
• 熔断器在默认情况下下是呈现关闭的状态,而熔断器本身带有计数功能,每当错误发生一次,计数器也就会进行“累加”的动作,到了一定的错误发生次数断路器就会被“开启”,这个时候亦会在内部启用一个计时器,一旦时间到了就会切换成半开启的状态。
- 开启
• 在开启的状态下任何请求都会“直接”被拒绝并且抛出异常讯息
- 半开启
• 在此状态下断路器会允许部分的请求,如果这些请求都能成功通过,那么就意味着错误已经不存在,则会被切换回关闭状态并重置计数。倘若请求中有“任一”的错误发生,则会恢复到“开启”状态,并且重新计时,给予系统一段休息时间。
- 概要
熔断工具
Hystrix(停更)
resilience4j
- 它是一个轻量、易用、可组装的高可用框架,支持熔断、高频控制、隔离、限流、限时、重试等多种高可用机制
- 系统请求先进入漏桶,再从漏桶中逐一取出请求执行,控制漏桶的流量。
- 统请求会得到一个令牌,从令牌桶中取出一个令牌执行,控制令牌桶中令牌的数量。
Sentinel
限流
限流是一种控制流量的技术,用于保护系统免受突发流量或恶意流量的影响。其基本原理是通过控制请求的速率或数量,确保系统在可承受的范围内运行
限流算法
漏桶算法(常用)
令牌桶算法(常用)
• 令牌和漏桶关系是都有一个固定容量的桶,都是按照固定的速率向桶中添加水(或者令牌),但是他们有一个最大的区别,那就是漏桶的这个桶底部是漏的,它同样会按照固定的速率把水流出,所以漏桶输出流量是匀速的,不管输入流量如何变化。而令牌桶的底部不是漏的,他不会以固定的速率流出,只会以固定的速率向桶中添加令牌。
• 举例
• 漏桶的这个桶,一秒钟流入一滴水,同样一秒钟漏出一滴水。那么,一秒钟就只能处理一个请求,超过的请求会被拒绝掉,达到限流的效果。
• 也就是说,漏桶这种算法,在5秒钟只能可以处理5个请求,并且每秒钟一个。但是如果出现这种情况,前4秒钟都没有请求,第5秒同时来了5个请求,漏桶是无法处理5个请求的,他只能处理1个,因为这一秒钟只会有一滴水漏出来。
• 令牌桶的实现逻辑是同样1秒钟产生一个令牌放到桶中,但是如果这个令牌没有被消费的话,他就会一直在桶中,不会被漏出去。还是刚刚那个例子,前4秒没有请求要处理的话,那么5秒钟就可以积攒5个令牌,这时候第5秒来了5个请求的时候,他去桶中是可以一次取出5个令牌,然后把这5个请求都给处理掉的。这就很好地应对了突发力量的问题。
计数器算法(简单)
- 系统请求被计数,通过比较当前请求数与限流阈值来判断是否限流。
- 当系统达到限流阈值时,不再接受新请求,等到限流阈值降下来再接受请求。
- 与令牌桶算法类似,但是在多个令牌桶之间形成环形结构,以便在不同的请求处理速率之间进行平衡。
- 基于预测每个请求的处理时间,并在处理完请求后进行延迟,以控制请求的速率。
- 基于一个固定大小的时间窗口,允许在该时间窗口内的请求数不超过设定的阈值。这个时间窗口随着时间的推移不断滑动,以适应不同时间段内的请求流量。
可以阻塞算法
令牌环算法
最小延迟算法
滑动窗口(常用)
• 把时间划分成多个连续的时间片段,每一个片段都有一个固定的时间间隔,如1s、1h等
再定义一个时间窗口,比如10s
当时间窗口移动时,需要把上一个时间片段中的请求数减掉,当有新的请求或操作到达系统时,系统会检查窗口内的计数是否已满。如果计数未满,请求被允许执行;如果计数已满,请求被拒绝或进入等待队列,或执行其他限流操作。
固定窗口限流
- 固定窗口限流中,也是需要定义时间片段和时间窗口,只不过在计数上有一个区别,那就是当随着时间的推移,到了下一个时间窗口时,固定窗口限流的计数器的数量会被清零。重新开始计数。
• 主要特点是窗口大小是固定的,不管请求是否均匀分布,每个窗口内的请求数量都是相同的。这可能导致某些时间段内请求过多,而在其他时间段内则很少,不同窗口之间可能出现流量的不平衡
自适应限流
所谓自适应限流,就是限流器结合服务器实例的Load、CPU、内存、接口的RT、QP、并发线程数等指标,进行的一种自适应的流控策略。即通过监控这些指标的变化,来动态的调整限流,来达到保证系统稳定性的目的。
- Sentinal限流框架就支持自适应限流
- 优点是可以在时间内平滑地控制流量,而不是简单地设置固定的请求数或速率。这使得系统可以更灵活地应对突发流量或峰值流量,而不会因为固定速率的限制而浪费资源或降低系统性能。
- Spring其实提供了一系列事件及扩展点,可以让我们在他的生命周期的各个阶段做我们想做的事情。如基于ApplicationReadyEvent、CommandLineRunner、InitializingBean、@PostConstruct等
- 在用户请求到来时,根据用户的访问模式和业务需求,动态地将数据加载到缓存中。这种方式更加灵活,可以根据实际需求选择性地预热缓存。
- 一些缓存框架提供了缓存加载器的机制,可以在缓存中不存在数据时,定时自动调用加载器加载数据到缓存中。这样可以简化缓存预热的逻辑