目录
微服务的问题
- 微服务架构中,服务之间通过网络调用紧密协作。这导致单个服务的故障极易通过调用链扩散,最终导致整个系统瘫痪,这就是所谓的 “雪崩效应”。
- 简单说就是在微服务群中,如果一个微服务调用另一个微服务出现问题,比如服务夯机,请求超时,那么影响的不仅仅是这两个微服务,而是可能所有的微服务,因为微服务之间是相互依赖的。
- 引发雪崩的场景包括:
场景 | 说明 |
---|---|
流量激增 | 突发的高并发,如热点事件或秒杀活动,可能瞬间压垮服务 |
依赖服务响应缓慢 | 某个上游服务处理变慢或长时间无响应,会持续占用下游服务的线程、连接等资源,导致资源耗尽 |
依赖服务故障 | 当一个服务因代码bug、宕机或网络问题完全不可用时,如果调用方没有妥善处理,可能会导致请求堆积、线程阻塞,进而将故障向上蔓延 |
微服务保护策略
微服务保护主要通过以下三种方式来预防和应堆微服务的雪崩问题,这三种方式都属于服务降级的范畴,旨在通过牺牲部分非核心功能或体验来换取系统的整体健壮性和可用性。
方案 | 核心思想 | 类比 | 主要目的 |
---|---|---|---|
请求限流 | 控制单位时间内进入系统的请求数量,平滑流量曲线 | 水库大坝,控制下游水流速度,避免洪峰 | 防止因突发流量导致服务过载和崩溃 |
线程隔离 | 将系统资源(如线程池)划分成多个独立的单元,限制每个业务可用的最大资源 | 轮船的舱壁,一个舱室进水不会导致整艘船沉没 | 将故障影响范围隔离在单个 服务内,避免扩散 |
服务熔断 | 统计依赖服务的异常情况,当失败率达到阈值时,自动切断调用,直接执行降级逻辑 | 电路保险丝,电流过大时自动熔断,保护电器 | 快速失败,防止不断尝试调用可能已故障的服务 |
请求限流
- 服务故障最重要原因,就是并发太高!解决了这个问题,就能避免大部分故障。当然,接口的并发不是一直很高,而是突发的。因此请求限流,就是限制或控制接口访问的并发流量,避免服务因流量激增而出现故障。
- 请求限流往往会有一个限流器,数量高低起伏的并发请求曲线,经过限流器就变的非常平稳。这就像是水电站的大坝,起到蓄水的作用,可以通过开关控制水流出的大小,让下游水流始终维持在一个平稳的量。
- QPS (每秒查询率,Queries Per Second) 是衡量服务器性能的一个重要指标,表示服务器每秒能够处理的查询次数。QPS通常用于评估Web系统的吞吐率,即系统在单位时间内完成的用户或系统请求数量。
线程隔离
当一个业务接口响应时间长,而且并发高时,就可能耗尽tomcat的线程资源,导致服务内的其它接口受到影响。所以我们必须把这种影响降低,或者缩减影响的范围。线程隔离正是解决这个问题的好办法
- 线程隔离的思想来自轮船的舱壁模式:
- 轮船的船舱会被隔板分割为N个相互隔离的密闭舱,假如轮船触礁进水,只有损坏的部分密闭舱会进水,而其他舱由于相互隔离,并不会进水。这样就把进水控制在部分船体,避免了整个船舱进水而沉没。
- 为了避免某个接口故障或压力过大导致整个服务不可用,我们可以限定每个接口可以使用的资源范围,也就是将其“隔离”起来。
如图所示,我们给查询购物车业务限定可用线程数量上限为20,这样即便查询购物车的请求因为查询商品服务而出现故障,也不会导致服务器的线程资源被耗尽,不会影响到其它接口。
服务熔断
- 线程隔离虽然避免了雪崩问题,但故障服务依然会拖慢服务调用方的接口响应速度,而且也会导致调用方服务接口故障如果不处理的话,所以,我们要做两件事情:
- 编写服务降级逻辑:就是服务调用失败后的处理逻辑,根据业务场景,可以抛出异常,也可以返回友好提示或默认数据。
- 异常统计和熔断:统计服务提供方的异常比例,当比例过高表明该接口会影响到其它服务,应该拒绝调用该接口,而是直接走降级逻辑。
Sentinel
Sentinel 是阿里开源的流量治理与服务保护组件,它能很好地实现上述三种方案:请求限流、线程隔离、服务熔断。
Sentinel的核心概念
- 资源 (Resource): Sentinel 保护的基本单元。一般来说,是springMvc controller接口和服务内的openfeign接口等。
- 规则 (Rule): 定义了保护资源的具体策略,如限流规则、熔断规则等。规则可以动态修改并立即生效。
- 控制台 (Dashboard): 一个独立的Web应用,用于实时监控资源运行状态、动态配置规则、查看调用链路等。
Spring Cloud整合Sentinel
Sentinel的使用分为两部分,首先需要在服务器或者本地安装Sentinel控制台web应用,然后在微服务项目中引入Sentinel依赖,配置控制台ip、端口等信息与控制台建立连接,最后在浏览器访问Sentinel Web服务,就能够在控制台中监控服务的资源运行状态、动态地配置规则、查看调用链路等信息。
安装Sentinel控制台
- 下载Sentinel控制台jar包,其程序也是基于java开发的,下载地址:https://github.com/alibaba/Sentinel/releases
- 在Sentinel jar包的同级目录中运行程序,启动控制台web应用
java -Dserver.port=8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
微服务配置
- 引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.6</version> <!-- 请注意使用最新版本 -->
</dependency>
- 配置控制台信息
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8090
http-method-specify: true # 开启请求方式前缀
openFeign降级配置
触发限流或熔断后的请求不一定要直接报错,也可以返回一些默认数据或者友好提示,用户体验会更好。
给FeignClient编写失败后的降级逻辑,我们主要通过FallbackFactory这种方式,可以对远程调用的异常做处理。
- 修改微服务的application.yml文件,开启Feign的sentinel功能
feign:
sentinel:
enabled: true # 开启feign对sentinel的支持
- 创建一个类实现 FallbackFactory接口,其中 T是你的 Feign 客户端接口。必须为接口中的每一个方法提供降级实现。
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
// 实现 FallbackFactory 接口,泛型参数为你的 Feign 客户端接口
public class UserServiceFallbackFactory implements FallbackFactory<UserServiceClient> {
@Override
public UserServiceClient create(Throwable cause) {
// 返回一个匿名实现类,这个类需要实现 UserServiceClient 接口的所有方法
return new UserServiceClient() {
@Override
public User getUserById(Long id) {
log.error("根据ID获取用户失败,用户ID: {}, 原因: ", id, cause);
// 这里可以根据不同的异常类型返回不同的降级结果
if (cause instanceof FeignException.NotFound) {
return new User(id, "未知用户", "服务暂不可用,请稍后重试");
} else if (cause instanceof DegradeException) {
return new User(id, "降级用户", "触发熔断,请稍后再试");
}
return new User(id, "默认用户", "系统繁忙,请稍后再试");
}
@Override
public List<User> getAllUsers() {
log.error("获取所有用户列表失败,原因: ", cause);
return Collections.emptyList(); // 返回空列表作为降级
}
@Override
public String createUser(User user) {
log.error("创建用户失败,原因: ", cause);
throw new BusinessException("创建用户服务调用失败,请检查后重试", cause); // 对于写操作,有时选择抛出业务异常更合适
}
// ... 必须实现 UserServiceClient 接口中定义的所有方法
};
}
}
- 在 Feign 客户端接口的 @FeignClient注解中,通过 fallbackFactory属性关联刚才创建的工厂类。
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@FeignClient(name = "user-service",
fallbackFactory = UserServiceFallbackFactory.class) // 指定 FallbackFactory
public interface UserServiceClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);
@GetMapping("/users")
List<User> getAllUsers();
@PostMapping("/users")
String createUser(@RequestBody User user);
// ... 其他方法
}
控制台配置降级限流
浏览器访问http://localhost:8090页面,就可以看到sentinel的控制台了,初始用户名、密码都是sentinel
点击簇点链路,就可以看到当前sentinel服务监控的所有微服务资源,包括controller接口和openfeign接口资源
选择任意一个受监控的簇点,我们可以配置它的流控(包括请求限流和线程隔离)、熔断策略,流控就是QPS和并发线程数,这个很好理解
Sentinel中的熔断机制靠的是断路器,断路器不仅可以统计某个接口的慢请求比例,还可以统计异常请求比例。当这些比例超出阈值时,就会熔断该接口,即拦截访问该接口的一切请求,降级处理;当该接口恢复正常时,再放行对于该接口的请求。
断路器的工作状态切换有一个状态机来控制:
状态 | 说明 |
---|---|
closed | 关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态 |
open | 打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态持续一段时间后会进入half-open状态 |
half-open | 半开状态,放行一次请求,根据执行结果来判断接下来的操作,请求成功就切换到close状态,失败就切回open状态 |
- 我们可以在控制台通过点击簇点后的熔断按钮来配置熔断策略
这种是按照慢调用比例来做熔断,上述配置的含义是:
- RT超过200毫秒的请求调用就是慢调用
- 统计最近1000ms内的最少5次请求,如果慢调用比例不低于0.5,则触发熔断
- 熔断持续时长20s