文章目录
前言
本篇内容为学习Sentinel 的课后总结,本文大致包括以下内容:
- Sentinel的介绍;
- Sentinel的安装配置;
- Sentinel的请求限流、隔离与降级;
- Sentinel 的授权规则;
- Sentinel 的持久化方案
整个过程记录详细,每个步骤亲历亲为,实测可用。
第一小节,我们将介绍一些微服务中的雪崩问题以及对应的解决方案。
一、初识Sentinel
1. 微服务的雪崩问题
我i们的业务中通常存在多个服务之间的相互调用。例如:当服务A调用服务D时,如果服务D 出现了故障,那么服务A 就会一致等待,Tomcat 的资源无法释放。此时,就会出现,服务D的故障 级联引起 的服务A也发生故障。
以上,只是对单一服务的举例。在我们微服务架构的项目中,大量存在这样的调用关系,因此,就会出现一个微服务出现故障而导致的整个项目的所有微服务都无法正常使用。这就称为 微服务的雪崩问题!
2. 雪崩问题的解决方法
常见的解决雪崩问题有四种方式:
-
超时处理
:设定超时时间,请求超过一定时间没有响应就返回错误消息,避免长时间等待。
-
舱壁模式(线程隔离)
:限定每个业务使用的线程数(类似为每个业务开辟单独的线程池),避免一个微服务宕机导致其他微服务不可用的情况。
-
熔断降级
:统计业务执行的异常比例,超过指定阈值则会 熔断 该业务,拦截访问该业务的一切请求。
-
请求限流
:限制业务访问的QPS(Query Per Second),避免服务引流量的突增而故障。
注意: 请求限流 主要是用来 预防高并发 引起的 雪崩问题,而引起雪崩问题还可能存在其他的原因(网络故障等)。因此,还需要线程隔离 和 熔断降级 来应对出现雪崩问题的情况。
3. Sentinel 和 Hystrix 的介绍
本篇文章就是围绕着 sentinel 实现各种措施来解决雪崩问题 。在Sentinel
之前,还存在一款Hystrix
来实现微服务的保护。不过Hystrix 目前也暂停维护了,使用最多的还是Sentinel。
Sentinel 是由阿里巴巴 开发的一款微服务保护框架,能够有效预防和解决雪崩问题,且经过多次的双十一高并发证明,Sentinel还是非常耐打的。
在上一小节中,我们了解了雪崩问题以及对应的解决方案。在下一小节,我们将对Sentinei 进行安装配置,为后面的实战做基础。
二、Sentinel 的安装配置
1. 安装Sentinel 控制台
Sentinel 官方提供了UI 控制台,方便我们对系统做限流设置。下载地址:Sentinel
a) 将下载后的 jar包放在任意无中文的目录下:
b) 在安装目录下,执行java -jar sentinel-dashboard-1.8.1.jar
运行jar 包。
项目默认运行在8080 端口:
如果要修改Sentinel的默认端口等信息,可以通过以下配置:
例如:java -jar sentinel-dashboard-1.8.1.jar -Dserver.port=8900
c) 启动成功后,我们即可在浏览器查看。
2. 微服务与Sentinel 链接
a) 此时,我们的项目结构如下图所示:
b) 此外,我们必须先了解一个概念:簇点链路
簇点链路 就是项目中服务间的调用链路,称这个调用链路中的每个微服务接口为一个资源 。Sentinel 就是对这些资源进行流控管理的。默认情况下,Sentinel 会识别SpringMVC中的每一个端点(Endpoint)为一个资源(也就是Controller 中每个暴露接口的方法)。
c) 快速入门
① 导入sentinel 相关依赖
<!-- 引入 Sentinel 的依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
② 配置Sentinel 控制台地址:
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # sentinel 控制台地址
③ 访问微服务的任意端点(浏览器访问Controller 中的任意接口),触发sentinel 监控。
④ 给指定的资源,添加流控规则:
- 资源名:对哪个资源进行监控;
- 针对来源:(这个可暂时不用管)。
- 阈值类型:QPS 每秒中获取的请求信号量;
- 单机阈值:设置QPS 最大为。
设置成功后,当我们在浏览器一秒之内手动刷新两次及以上时,请求就会被限流:
在上一小节中,我们完成了对Sentinei 进行安装配置。在下一小节,我们将介绍Sentinel 的请求限流。
三、Sentinel 请求限流
1. 流控的三种模式
在添加限流规则时,点击高级选项,可以选择三种流控模式:
直接
:统计当前资源的请求,触发阈值则直接对当前资源限流。(默认方式)关联
:统计与当前资源相关的另一个资源,另一个资源触发阈值时,对当前资源限流。(身份的含金量)链路
:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流。
之前我们演示了直接限流的方式,接下来将对另外两种模式进行实际操作使用:
a) 关联模式
使用场景:比如用户支付时需要修改订单状态,同时用户要查询订单。查询和修改操作会争抢数据库锁。因此,当修改订单业务触发阈值时,需要对查询订单进行限流。(这恒河里)
当 /write 资源访问量触发阈值时,就会对 /read 资源限流,避免影响 /write 资源。
满足下面条件可以使用关联模式:
- 两个资源存在竞争关系;
- 一个资源优先级较高、一个优先级较低。
b) 链路模式
链路模式只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。
例如:现存在两条链路
- /test1 → /common
- /test2 → /common
如果只希望统计从 /test2 进入到 /common 的请求,则可以这样配置:
此外,Sentinel 默认会将Controller 中的方法做context 整合(也就是所有的资源都在context 的路径下),此时就只存在一条链路,导致链路模式的流控失效。
可以通过 修改sentinel 相关配置 解决此问题:
spring:
cloud:
sentinel:
web-context-unify: false # 关闭 context 整合
况且,我们在前面提到过 Sentinel 默认只将Controller 中的方法标记为资源,如果需要标记 链路模式中的公用方法,则需要在指定的方法前加上@SentinelResource()
注解:
@SentinelResource("goods")
public void queryGoods(){
System.err.println("查询商品");
}
c) 总结
流控模式有哪几种?
- 直接:对当前资源限流;
- 关联:高优先级资源触发阈值,对低优先级资源限流;
- 链路:只统计从指定路径的请求,进行限流。
2. 流控的三种效果
在流控配置的高级选项中,还有另外一项值得我们注意:流控效果。
流控效果 是指请求达到阈值后,具体应该采取的措施,主要包括三种:
- 快速失败:当请求达到阈值后,新的请求会被 立即拒绝 并抛出FlowException异常,返回
429
的状态码。(默认采取的处理方式) - warm up:预热模式,对超过阈值的请求同样采取拒绝2并抛出异常的方式。但是此模式的阈值是会动态变化的。
- 排队等待:让所有接收到的请求按照先后次序排队执行。进入队列等待前,先计算此请求的等待时间,如果等待时间超过所规定的时间,则拒绝该请求。(相较于前两者,较为缓和一些)
在前面的案例中使用的就是默认的快速失败模式,此处我们将对另外两者模式进行实际操作讲解。
a) warm up 模式
warm up 也称预热模式,是应对服务 冷启动 的一种方案。服务冷启动是指 服务刚开始运行时,处理请求的最大数无法直接到达阈值,如果在启动时就接收到大量的请求,则容易导致服务故障。因此,在服务启动时,请求阈值较小,随着时间的推移,逐渐提高到 threshold 的值(请求最大阈值)。
例如:请求阈值的初始值为:threshold / coldFactor
,coldFactor 的默认值为3。我们可以设置的就是threshold
值和预热时间。
b) 排队等待模式
排队等待与其他两种模式不同,该种模式将所有请求放入一个队列种,然后按照阈值允许的时间间隔 1 seond/单机阈值
依次执行。如果请求的预期等待时间超过最大时长,则会被拒绝请求。
例如:QPS为5(每200ms 处理一个队列中的请求),timeout = 2000(预期等待时间超过2000ms的请求会被拒绝并抛出异常)。
借助排队等待模式,我们可以实现对突增的高并发进行削峰填谷。
c) 总结
- 快速失败:QPS超过阈值时,拒绝新的请求;
- warm up:QPS超过阈值时,拒绝新的请求;QPS阈值是逐渐提高的,可以有效避免冷启动时高并发导致的服务宕机。
- 排队等待:请求进入队列,按照阈值允许的时间间隔依次执行请求;如果请求的预期等待时间大于超时时间,则直接拒绝。
3. 热点参数限流
之前的限流都是直接针对访问某个资源的所有请求,判断是否超过阈值。热点参数限流则是分别统计参数值相同的请求,判断是否QPS阈值。
使用场景: 在商品浏览业务中,我们可以给热门的商品设置较高的QPS 阈值,提高用户访问热门商品的业务员处理能力。
配置示例:
- 参数索引:指对 hot 这个资源的 0号参数(第一个参数)做统计;
- 单机阈值:指最大的请求数;
- 统计窗口时长:指在一秒内的最大请求数量为5。
在热点参数限流的高级选项中,可以对部分参数设置例外配置(增大热门商品的QPS值):
(注意此处的热点参数的高级配置必须通过左侧栏跳转到专门的热门参数管理页面后,再点击新增热门参数,才会有高级设置。算是一个小BUG)
此外,热点参数限流对默认的SpringMVC资源无效。 (带个参数就不行,莫名奇妙的)需要我们手动对Service中的方法进行资源注册。例如:
@SentinelResource("hot")
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根据id查询订单并返回
return orderService.queryOrderById(orderId);
}
在上一小节中,我们初步了解了Sentinel 的请求限流。在下一小节,我们继续学习Sentinel 中的隔离和降级。
三、Sentinel 的隔离和降级
虽然限流能够有效避免高并发引起的服务故障,但服务故障的原因可远远不止这一种。而要将这些故障控制在一定范围内,避免雪崩,就要依靠舱壁模式(线程隔离) 和 熔断降级 了。
1. Feign整合Sentinel
首先,我们必须明确,雪崩问题的原由就是服务的调用者由于服务的提供者故障而引起调用者自身的级联故障。因此,我们舱壁模式和熔断降级都是基于调用者而实现的。
在本次微服务的学习过程中,我们是基于 Feign
来实现服务的远程调用的。因此,做客户端保护必须整合Feign和Sentinel。
a) 修改配置文件
开启feign 对sentinel的支持,开启后,feing的远程调用就被 sentinel 当作一个资源进行管理
feign:
sentinel:
enabled: true
b) 给FeignClient 调用失败后的处理逻辑
- 方式一:FallbackClass,无法对远程调用的异常做处理。(使用较少)
- 方式二:FallbackFactory,可以对远程调用的异常做出了。(经常使用)
c) 在feign-api 项目中定义类,实现FallbackFactory 接口:
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
// 创建userClient 接口实现类,实现其中的方法,编写失败降级后的处理逻辑。
return new UserClient() {
@Override
public User findById(Long id) {
// 编写业务逻辑
// 记录异常信息
log.error("查询用户异常",throwable);
// 根据业务需求返回默认的数据。
return new User();
}
};
}
}
d) 将 UserClientFallbackFactory 注册称为一个Bean:
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}
e) UserClient 接口使用UserClientFallbackFactory:
@FeignClient(value = "userservice",fallbackFactory = UserClientFallbackFactory.class) // 指定服务名称
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable Long id);
}
2. 线程隔离
线程隔离有两种方式实现:线程池隔离 和 信号量隔离 (Sentinel默认采用的方法)
这里将两种方法做个对比:
信号量隔离 | 线程池隔离 | |
---|---|---|
优点 | 基于计数器;轻量级,无额外开销 | 支持主动超时;支持异步调用;隔离性强 |
缺点 | 不支持主动超时;不支持异步调用 | 线程的额外开销比较大 |
场景 | 高频调用:高扇出 | 低扇出 |
在添加限流规则时,可以选择两种阈值类型:
- QPS:每秒的请求数。
- 线程数:该资源能够使用的Tomcat 线程数的最大值。也就是 通过限制线程数量,实现舱壁模式。
3. 熔断降级
a) 熔断器的三个阶段
熔断降级是解决雪崩问题的重要手段。其思路是由 断路器 统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断 该服务。即拦截访问该服务的一切请求;当服务恢复时,断路器则会放行该服务的请求。
因此,熔断器主要共有三个阶段:
- Closed:此时熔断器不工作,正常处理请求;
- Open:当服务的异常比例到达失败阈值时,断路器会进入Open 阶段,此时拒绝所有的请求。
- Half-Open:等待熔断时间结束后,断路器会进入Half-Open 阶段。此时,熔断器会尝试放行一次请求,如果请求被正常处理,则重新进入Close阶段正常接收请求;如果依然失败,则再次进入Open阶段,拒绝请求。
熔断器的三个阶段的互相转换即实现了当服务异常时,拒绝请求;当服务恢复正常时,再重新放行请求。
b) 熔断器的三个策略
断路器策略有三种:慢调用、异常比例、异常数。
① 慢调用:业务的响应时间大于指定的请求时间 则会被认定为慢调用请求。在统计时间内,慢调用请求超过一定比例,则触发熔断。例如:
解读: 如果响应时间超过500ms则被认定为慢调用请求,统计10000ms内的请求,如果请求数量超过10次,且慢调用比例大于0,5,则触发熔断,熔断时间为5秒。5秒后进入half-open状态,放行一次做测试。
② 异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,且出现异常的比例达到设定的比阈值(或异常数超过指定阈值),则触发熔断。例如:
解读: 统计1000ms内的请求,如果请求数超过10次,并且异常比例不低于0.5,则触发熔断,熔断时长为5秒。然后进入half-open 状态,放行一次请求做测试。
在上一小节中,我们学习了Sentinel 的隔离和降级。在下一小节,我们继续学习Sentinel 中的授权规则。
四、Sentinel 授权规则
1. 授权规则
授权规则可以对调用方的来源做控制(防止间谍直接泄露内部地址,然后通过浏览器直接访问),有白名单和黑名单两种方式:
- 白名单:来源(origin) 在白名单内的调用者允许访问;
- 黑名单:来源(origin) 在黑名单内的调用者不允许方位。
例如, 我们只允许从网关来的请求访问order-service,那么流控应用中就填写网关的名称:
那么,如何判断请求是哪个来源的呢?Sentinel是通过ReqquestOriginParse
这个接口的parseOrigin 来获取请求的来源的。
我们此处从 request
中获取一个名为origin 的请求头,作为Origin 的值:
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
// 1. 获取请求头
String origin = request.getHeader("origin");
// 2. 非空判断
if(StringUtils.isEmpty(origin)){
origin = "blank";
}
return origin;
}
}
还需要在 gateway
服务中,利用网关的默认过滤器添加名为 gateway 的origin 头。
spring:
cloud:
gateway:
default-filters: # 配置所有服务的过滤器
- AddRequestHeader=origin,gateway
再给 /order/{orderId}
配置授权规则:
此时,我们直接在浏览器是无法访问 /order/{orderId} 查询接口的:
通过网关访问服务是OK 的:
2. 自定义异常结果
默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。往往返回的异常数据对用户来说不太友好,此时我们可以自定义异常返回结果对用户进行友好型提示。需要实现BlockExceptionHandler 接口:
BlockException 包含多个子类,分别对应不同的场景:
具体实现代码: 定义SentinelBlockHandler 类,实现BlockExceptionHandler 接口:
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
String msg = "未知异常";
int status = 429;
if(e instanceof FlowException){
msg = "请求被限流啦";
}else if (e instanceof ParamFlowException){
msg = "请求被热点参数限流啦";
} else if (e instanceof DegradeException) {
msg = "请求被降级啦";
} else if (e instanceof AuthorityException) {
msg = "没有权限访问";
status = 401;
}
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.setStatus(status);
httpServletResponse.getWriter().println("{\"message\": \"" + msg + "\", \"status\": "+ status + "}");
}
}
总结:
1. 获取请求来源的接口:RequestOriginParse
2. 处理BlockException 的接口是什么:BlockExceptionHandler
在上一小节中,我们学习了Sentinel 的 授权规则。在下一小节,我们继续学习Sentinel 中规则的持久化。
五、Sentinel 的规则持久化
1. Sentinel的三种配置管理模式
当我们在每次重启某个服务时,我们的sentinel 中的相关流控规则等都会被清除,需要我们重新创建。这实在是太不优雅了。
此时,我们就需要了解Sentinel 中的三种配置管理模式:
- 原始模式:将配置保存在内存中。因此,重新启动服务会导致失效,这也是sentinel 配置的默认管理模式。
- pull模式:将配置保存在本地文件或数据库中,定时去读取。
- push模式:将配置保存到nacos 中,监听变更做到实时更新。
接下来,我们将注重与pull 与 push 模式。
2. pull 模式
pull 模式相较于原始模式,把浏览器的控制台配置的规则推送到Sentinel 客户端,客户端会将这些配置规则保存在本地文件或数据库中。然后定时去本地文件或数据库中查询,更新本地规则。此种方案的弊端也十分明显:实时性较差。
3. push 模式
push模式是指sentinel控制台将配置规则推送到远程配置中心,例如nacos。Sentinel客户端监听nacos,获取配置变更的推送消息,完成本地配置更新。
4. 规则持久化实战
这里选择实时性较好的push 模式来作为sentinel 的配置管理模式。选择Nacos 作为远程配置中心。
可惜的是,Sentinel 默认只支持原始模式。因此,需要我们修改Sentinel 源码来实现push 模式。(真是可恶啊 zzz)
不过这里已经有现成的啦(qaq)
(如果有需要的话,可以联系笔者。笔者也可以单独水一篇… 哦不,写一篇博客)
运行时,一般需要通过参数来指定nacos.server 的地址(如果是在本地执行的话,就不用了)
启动Sentinel (修改后的)
java -jar -D sentinel-dashboard.jar -Dnacos.server=localhost:8848
启动成功后,清除浏览器缓存,再强制刷新,重新登录Sentinel 浏览器控制台,此时就会多出一个流控规则-NACOS 。此时,在此页面创建的规则 就实现了持久化存储啦,下次登录还可以继续使用之前定义的规则。实在是太tm 的优雅了。
以上就为本篇文章的全部内容啦!
如果本篇内容对您有帮助的话,请多多点赞支持一下呗!