一、分布式系统遇到的问题
1. 服务的可用性问题
服务雪崩效应
: 因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程,最终导致整个系统瘫痪。
- 在一个高度服务化的系统中,我们实现的一个
业务逻辑通常会依赖多个服务
- 如果其中的
下单服务不可用
,就会出现线程池里所有线程都因等待响应而被阻塞
,从而造成整个服务链路不可用,进而导致整个系统的服务雪崩
。
二、 服务不可用的原因
1. 流量激增
- 激增流量导致CPU飙高,无法正常处理请求。
- 激增流量打垮冷系统(数据库连接未创建,缓存未预热)。
- 消息投递速度过快,导致消息处理积压。
例如: 突然有成千上万客户来访问或黑客故意攻击,导致CPU飙升。
2. 被其他不稳定服务拖垮
- 慢SQL。
- 第三方服务响应慢。
- 业务调用链很长导致响应超时。
例如: A服务调用B服务,B服务被其他用户大量访问或黑客故意攻击该业务功能,导致B服务延迟或拖垮服务崩溃,从而使A服务调用B服务部分功能无法使用,进一步发展服务雪崩效应。
3. 异常没处理
在服务提供者不可用的时候,会出现大量重试的情况:
用户重试
代码逻辑重试
大量请求线程同步等待造成的资源耗尽
这些重试最终导致:进一步加大请求流量。 当服务调用者使用同步调用时,会产生大量的等待线程占用系统资源。一旦线程资源被耗尽服务调用者提供的服务也将处于不可用状态
,于是服务雪崩效应
产生了。
三、解决方式: 稳定性(Reliabilitly)&& 可靠性(Resilience)
1. 超时机制
在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽。 加入超时机制,一旦超时,就释放资源。由于释放资源速度较快,一定程度上可以抑制资源耗尽的问题。
2. 服务限流
QPS
: 服务限制用户访问请求限制次数,可以根据时间来进行控制。
例如:设置最大访问量为每秒500次数,但是当前访问量每秒800次数,300个访问量被拒绝。
- 线程数隔离:
用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理
,用户的请求不会被阻塞,至少可以看到一个执行结果
(例如返回友好的提示信息),而不是无休止的等待或者看到系统崩溃。
- 信号量隔离
信号隔离也可以用于限制并发访问
,防止阻塞扩散
,与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过 信号申请)
,如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离降低开销
。信号量的大小可以动态调整,线程池大小不可 以
。
3. 服务熔断
服务熔断
: 远程服务不稳定或网络抖动时暂时关闭。
例如: 现实世界的断路器大家肯定都很了解,断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路 被烧毁。
- 软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数/失败率达到一定阈值,就“跳闸”,断路 器打开——此时,请求直接返回,而不去调用原本调用的逻辑。跳闸一段时间后(例如10秒),断路器会进入半开状 态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不 成功,断路器继续回到打开状态,过段时间再进入半开状态尝试——通过”跳闸“,应用可以保护自己,而且避免浪费资 源;而通过半开的设计,可实现应用的“自我修复“。
- 所以,同样的道理,当依赖的服务有大量超时时,在让新的请求去访问根本没有意义,只会无畏的消耗现有资源。
例如: 我们设置了超时时间为1s,如果短时间内有大量请求在1s内都得不到响应,就意味着这个服务出现了异常,此时就没有必要再让其他的请求去访问这个依赖了,这个时候就应该使用断路器避免资源浪费。
4. 服务降级
- 有服务熔断,必然要有服务降级。
服务降级
: 就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的fallback(回退)回调, 返回一个缺省值。
例如:(备用接口/缓存/mock数据)。这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强,当然这也要看适合的业务场景。