前言
分布式系统遇到的问题
在一个高度服务化的系统中,实现一个业务逻辑通常会依赖多个服务;es:商品详情展示服务会依赖商品服务、价格服务、商品评论服务等:
调用三个依赖服务会共享商品详情服务的线程池;若其中的商品评论服务不可用,则会出现线程池里所有线程都因等待响应而被阻塞,从而造成服务雪崩
服务雪崩效应:因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程,就叫服务雪崩效应
导致服务不可用的原因:程序 bug、大流量请求、硬件故障、缓存击穿等大流量请求
:在秒杀和大促开始前,若准备不充分,瞬间大量请求会造成服务提供者的不可用硬件故障
:可能为硬件损坏造成的服务器主机宕机,网络硬件故障造成的服务提供者的不可访问缓存击穿
:一般发生在缓存应用重启,缓存失效时高并发,所有缓存被清空时,以及短时间内大量缓存失效时;大量的缓存不命中,使请求直击后端,造成服务提供者超负荷运行,引起服务不可用
解决方案
超时机制:
在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽;加入超时机制,一旦超时就释放资源;由于释放资源速度较快,一定程度上可以抑制资源耗尽的问题
服务限流(资源隔离):
限制请求核心服务提供者的流量,使大流量拦截在核心服务之外,这样可以更好的保证核心服务提供者不出问题,对于一些出问题的服务可以限制流量访问,只分配固定线程资源访问,这样能使整体的资源不至于被出问题的服务耗尽,进而整个系统雪崩
那么服务之间怎么限流,怎么资源隔离?
es:线程池 + 队列,听过信号量的方式;
服务熔断:
远程服务不稳定或网络抖动时暂时关闭,就叫服务熔断
服务降级:
所谓降级,就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自行准备一个本地的 fallback(回退)回调,返回一个缺省值
Sentinel 分布式系统的流量防卫兵
Sentinel 介绍
随着微服务的流行,服务和服务之间的稳定性变得越来越重要;Sentinel 是面向分布式服务架构的流量控制组件;主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保证微服务的稳定性
源码地址 官方文档
Sentinel 特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,es:秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等
- 完备的实时监控:Sentinel 同时提供实时的监控功能;可在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况
- 广泛的开源生态:Sentinel 提供开箱即用的与其他开源框架的整合模块,只需引入相应的依赖并进行简单的配置即可
- 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展点,可通过实现扩展点快速的定制逻辑
Sentinel 和 Hystrix 对比
Sentinel | Hystrix | |
---|---|---|
熔断策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口 |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持慢启动、均衡器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控 | 不完善 |
常见框架的适配 | Servlet、SpringCloud、Dubbo等 | Servlet、SpringCloudNetflix |
Sentinel 工作原理
基本概念
资源:资源是 Sentinel 的关键概念;它可以是 Java 应用程序中的任何内容;es:应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码;
规则:围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则,所有规则可以动态实时调整
Sentinel 工作主流程
在 Sentinel 中,所有的资源都对应一个资源名称resourceName
,每次资源调用都会创建一个Entry
对象;Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用Sphu
api 显示创建;Entry 创建的时候同时也会创建一系列功能插槽(slot chain),这些插槽有不同的职责:
NodeSelectorSlot
:负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级ClusterBuilderSlot
:用于存储资源的统计信息以及调用者信息,es:该资源的 RT、QPS、thread count 等,这些信息将用作为多维度限流降级的依据StatisticSlot
:用于记录、统计不同维度的 runtime 指标监控信息FlowSlot
:用于根据预设的限流规则以及前面 slot 统计的状态来进行流量控制AuthoritySlot
:根据配置的黑白名单和调用来源信息来做黑白名单控制DegradeSlot
:通过统计信息以及预设的规则来做熔断降级SystemSlot
:通过系统的状态来控制总的入口流量
Sentinel 实战
Entry entry = null;
// 务必保证 finally 会被执行
try {
// 资源名可使用任意有业务语义的字符串 开启资源的保护
entry = SphU.entry("自定义资源名");
// 被保护的业务逻辑 method
// do something...
} catch (BlockException ex) {
// 资源访问阻止,被限流或被降级 Sentinel定义异常 流控规则,降级规则,热点参数规则。。。。 服务降级(降级规则)
// 进行相应的处理操作
} catch (Exception ex) {
// 若需要配置降级规则,需要通过这种方式记录业务异常 RuntimeException 服务降级 mock feign:fallback
Tracer.traceEntry(ex, entry);
} finally {
// 务必保证 exit,务必保证每个 entry 与 exit 配对
if (entry != null) {
entry.exit();
}
Sentinel 资源保护的方式
引入依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.0</version>
</dependency>
模拟代码:
@RestController
@Slf4j
public class HelloController {
private static final String RESOURCE_NAME = "hello";
@RequestMapping(value = "/hello")
public String hello() {
Entry entry = null;
try {
// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
entry = SphU.entry(RESOURCE_NAME);
// 被保护的业务逻辑
String str = "hello world";
log.info("====="+str);
return str;
} catch (BlockException e1) {
// 资源访问阻止,被限流或被降级
//进行相应的处理操作
log.info("block!");
} catch (Exception ex) {
// 若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(ex, entry);
} finally {
if (entry != null) {
entry.exit();
}
}
return null;
}
/**
* 定义流控规则
*/
@PostConstruct
private static void initFlowRules(){
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
//设置受保护的资源
rule.setResource(RESOURCE_NAME);
// 设置流控规则 QPS
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
// Set limit QPS to 20.
rule.setCount(1);
rules.add(rule);
// 加载配置好的规则
FlowRuleManager.loadRules(rules);
}
}
缺点: 业务侵入性强,需要在 Controller 写入非业务代码 配置不灵活,若需要添加新的受保护的资源,需要手动添加 init 方法来添加流控规则
@SentinelResource注解实现
@SentinelResource 注解用来表示资源是否被限流、降级 BlockHandler:定义当资源内部发生了 BlockException 应该进入的方法(捕获的是 Sentinel 定义的异常) Fallback:定义的是资源内部发生了 Throwable 应该进入的方法 ExceptionsTolgnore:配置 fallback 可以忽略的异常 源码入口:com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect
引入依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.0</version>
</dependency>
配置切面支持:
@Configuration
public class SentinelAspectConfiguration {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
模拟代码,添加@SentinelResource,并配置 blockHandler 和 fallback:
@RequestMapping(value = "/findOrderByUserId/{id}")
@SentinelResource(value = "findOrderByUserId",
fallback = "fallback",fallbackClass = ExceptionUtil.class,
blockHandler = "handleException",blockHandlerClass = ExceptionUtil.class
)
public R findOrderByUserId(@PathVariable("id") Integer id) {
//ribbon实现
String url = "http://mall-order/order/findOrderByUserId/"+id;
R result = restTemplate.getForObject(url,R.class);
if(id==4){
throw new IllegalArgumentException("非法参数异常");
}
return result;
}
ExceptionUtil,注:若指定了 class,方法必须是 static 方法:
public class ExceptionUtil {
public static R fallback(Integer id,Throwable e){
return R.error(-2,"===被异常降级啦===");
}
public static R handleException(Integer id, BlockException e){
return R.error(-2,"===被限流啦===");
}
}
流控规则设置可以通过 Sentinel dashboard 配置,客户端需引入 Transport 模块与 Sentinel 控制台进行通信:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.0</version>
</dependency>
启动 Sentinel 控制台:
#启动控制台命令
java -jar sentinel-dashboard-1.8.0.jar
可通过如下参数进行配置: -Dsentinel.dashboard.auth.username=sentinel 用于指定控制台的登录用户名 -Dsentinel.dashboard.auth.password=123456 用于指定控制台的登录密码 默认用户名/密码:sentinel/sentinel -Dserver.servlet.session.timeout=7200 指定 Spring Boot 服务端 session 的过期时间,单位 s,默认 30 分钟
访问 http://localhost:8080/#/login
Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包,所以要确保客户端有访问量
Spring Cloud Alibaba 整合 Sentinel
引入依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
添加 yml 配置,设置 Sentinel 控制台地址:
添加 Sentinel 后,需暴露 /actuator/sentinel 端点,而 SpringBoot 默认是没有暴露该端点的,所以需要设置;测试 http://localhost:8800/actuator/sentinel
server:
port: 8800
spring:
application:
name: mall-user-sentinel-demo
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
sentinel:
transport:
# 添加sentinel的控制台地址
dashboard: 127.0.0.1:8080
# 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
# port: 8719
#暴露actuator端点
management:
endpoints:
web:
exposure:
include: '*'
在 Sentinel 控制台中设置流控规则:
- 资源名:接口的 api
- 针对来源:默认 default,当多个微服务都调用这个资源时,可以配置微服务名来对指定的微服务设置阈值
- 阈值类型:分为 QPS 和线程数
- QPS 类型:只得是每秒访问接口的次数 > 10 就进行限流
- 线程数:为接收请求该资源分配的线程数 > 10 就进行限流