Sentinel,中文翻译为哨兵,是为微服务提供流量控制、熔断降级的功能,它和Hystrix提供的功能一样,可以有
效的解决微服务调用产生的“雪崩”效应,为微服务系统提供了稳定性的解决方案。随着Hytrxi进入了维护期,不
再提供新功能,Sentinel是一个不错的替代方案。通常情况,Hystrix采用线程池对服务的调用进行隔离,
Sentinel才用了用户线程对接口进行隔离,二者相比,Hystrxi是服务级别的隔离,Sentinel提供了接口级别的
隔离,Sentinel隔离级别更加精细,另外Sentinel直接使用用户线程进行限制,相比Hystrix的线程池隔离,减
少了线程切换的开销。另外Sentinel的DashBoard提供了在线更改限流规则的配置,也更加的优化。
从官方文档的介绍,Sentinel 具有以下特征:
丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控
制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,
甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、
gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻
辑。例如定制规则管理、适配数据源等。
1、新建项目,添加pom依赖
org.springframework.cloud
spring-cloud-starter-alibaba-sentinel
2、编写配置文件
server:
port: 9003
spring:
application:
name: cloud-sentinel-9003
cloud:
sentinel:
transport:
dashboard: localhost:8080
port: 8719
nacos:
discovery:
server-addr: xxx.xxx.xxx.xxx:8848
management:
endpoints:
web:
exposure:
include: "*"
***在这里,sentinel运行在本地,尝试使用docker,但是获取不到服务的详细链路,原因是sentinel主动向服务拉取信息,阿里云服务器无法访问到本机,因此失败。
3、启动项目后,访问两个端口/testA和/testB,刷新sentinel即可看到
每秒如果访问超过五次,就会抛出异常
当每秒访问HelloA超过一次后,将对HelloB进行限流,比如支付服务满了以后对订单服务进行限流,防止连带。
链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流;它的功能有点类似于针对 来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度 更细;
创建一个service,添加注解
@SentinelResource(value = "getOrder",blockHandler = "handlerException")
在流量监控处设置:
访问HelloB一秒超过一次就会限流。
注意:sentinel1.7以后需要设置spring.cloud.sentinel.web-context-unify=false即可
详情:https://github.com/alibaba/Sentinel/issues/1213
在预热的5秒内每秒的QPS最多是10/3=3次,如果超过3次就会限流,5秒以后阀值恢复到10
HelloA一秒处理一次请求,超过就排队等待,等待的超时时间为2000毫秒,超过等待毫秒就会限流。
1、RT(平均响应时间,秒级)
平均响应时间超出阀值且在时间窗口内通过的请求次数>=5次,两个条件同时满足后触发降级
窗口期过后关闭断路器,RT最大4900
2、异常比例(秒级)
QPS>=5且异常比例(秒级统计)超过阀值时,触发降级,时间窗口期结束后,关闭降级
3、异常数(分钟级)
异常数(分钟统计)超过阀值时,触发降级;时间窗口结束后,关闭降级
一秒持续进入>=5次请求,平均响应时间如果大于200毫秒,则开启断路器,在时间窗口期2秒后关闭降级
一秒持续进入>=5次请求,如果每秒处理的请求异常比例大于百分之20,则开启断路器,在时间窗口期2秒后关闭降级
异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
在61秒内如果超过异常5次,则开启断路器,在61秒以后关闭断路器
@GetMapping("/HelloE")
@SentinelResource(value = "helloE",blockHandler = "hostKeyHandler")
public String getE(@RequestParam(value = "a",required = false)String a
,@RequestParam(value = "b",required = false)String b)
{
return "------HelloE------";
}
public String hostKeyHandler(String a, String b, BlockException ex){
return ex.getRule().getResource()+",服务正忙,稍后再试~";
}
对HelloE请求,携带的第一个参数,如果QPS超过1就会进行降级,返回自定义信息
添加参数例外项,设置如果第一个参数是2,则阀值是10,第一个参数不是2,则阀值是1
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均
RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达
到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性
Field | 说明 | 默认值 |
---|---|---|
highestSystemLoad | load1 触发值,用于触发自适应控制阶段 |
-1 (不生效) |
avgRt | 所有入口流量的平均响应时间 | -1 (不生效) |
maxThread | 入口流量的最大并发数 | -1 (不生效) |
qps | 所有入口资源的 QPS | -1 (不生效) |
highestCpuUsage | 当前系统的 CPU 使用率(0.0-1.0) | -1 (不生效) |
当任何请求访问系统QPS超过1,都会开启断路器
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource()
{
return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
}
public CommonResult handleException(BlockException exception)
{
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
}
根据SentinelResource注解的Value进行限流
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl()
{
return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
}
根据Url进行限流,但使用的是Sentinel自己的提示信息,不友好
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class,
blockHandler = "handlerException2")
public CommonResult customerBlockHandler()
{
return new CommonResult(200,"按客戶自定义",new Payment(2020L,"serial003"));
}
public class CustomerBlockHandler {
public static CommonResult handlerException2(BlockException exception)
{
return new CommonResult(4444,"按客戶自定义,global handlerException----1");
}
将处理降级提示信息与业务逻辑分离,使用blockHandlerClass和blockHandler指明类名和方法名即可
SentinelResouce无法对private方法进行监控
1、新建两个服务提供者和一个消费者,将其注册到nacos
2、分别通过Ribbion和OpenFeign调用服务
pom依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
com.alibaba.csp
sentinel-datasource-nacos
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
对value进行流控后,会报500错误
只对系统异常进行降级处理,不会对sentinel设置的流控和降级进行兜底
只对sentinel设置的流控和降级进行兜底处理,不会对系统异常进行降级处理
会对异常和sentinel设置的流控和降级进行不同的处理
8.4@SentinelResource中配置exceptionsToIgnore
会忽略设置的异常,不会进行兜底处理,可以自己写全局异常处理类进行处理
新建类实现接口,在重载的方法里进行降级处理,在接口的注解加上fallback = xxx.class即可
注意需要将实现类加上@Component否则会报错
@RestController
@Slf4j
public class OrderController {
@Resource
private OrderService orderService;
@Autowired
private RestTemplate restTemplate;
@Value("${service.url.paymentUrl}")
private String paymentUrl;
// ----------OpenFeign-----------
@GetMapping("/consumer/getByFeign/{id}")
public CommonResult getPayByFeign(@PathVariable("id") Long id){
log.info("**************OpenFeign");
CommonResult result = orderService.paymentSQL(id);
if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
// ----------Ribbion-----------
@GetMapping("/consumer/getByRibbion/{id}")
//@SentinelResource(value = "fallback")
//@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
//@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
@SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",
exceptionsToIgnore = {IllegalArgumentException.class})
public CommonResult getByRibbion(@PathVariable("id") Long id){
log.info("**************Ribbion");
CommonResult result = restTemplate.getForObject(paymentUrl + "/payment/" + id, CommonResult.class);
if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
//本例是fallback
public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment);
}
//本例是blockHandler
public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
}
}
------------------------------------------------------------
@Component
@FeignClient(value = "cloud-sentinel-payment-provider",fallback = OrderSerivceHandler.class)
public interface OrderService {
@GetMapping(value = "/payment/{id}")
public CommonResult paymentSQL(@PathVariable("id") Long id);
}
-----------------------------------------------------------------
@Component
public class OrderSerivceHandler implements OrderService {
@Override
public CommonResult paymentSQL(Long id) {
return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
}
}
将限流配置持久化存储到Nacos里
1、添加pom依赖
com.alibaba.csp
sentinel-datasource-nacos
2、在项目配置文件里添加配置如下
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
port: 8719
datasource:
wx1: #名字随意起
nacos:
server-addr: xxx.xxx.xxx.xxx:8848 #nacos地址
dataId: cloud-alibaba-sentinel-service #配置文件的dataId
groupId: DEFAULT_GROUP #配置文件的分组
data-type: json #配置文件的类型
rule-type: flow #用来定义存储的规则类型,flow代表限流规则
#degrade代表降级
#system代表系统规则
#authority代表授权
3、在Sentinel添加配置文件如下图
4、规则说明
resource:资源名称
limitApp:来源应用
grade:阀值类型,0代表线程数,1代表QPS
count:单机阀值
strategy:流控模式,0代表直接,1代表关联,2代表链路
controlBehavior:流控效果,0代表快速失败,1代表Warm Up(预热),2代表排队等待
clusterMode:是否集群,false代表没有,true代表集群
6、监控数据说明
流控的数据只会保存5分钟,如果想持久化,可以后期使用:
1.实现 MetricsRepository 接口;
2.注册成 Spring Bean 并在相应位置通过 @Qualifier 注解指定对应的 bean name 即可。
1、sentinel数据持久化有五种方式
1.File 2.Redis 3.Nacos 4.Zookeeper 5.apollo
2、sentinel默认监控数据只保存五分钟,如果想要对监控的数据情况持久化有以下两种方式
1.自行扩展实现 MetricsRepository 接口;
2.注册成 Spring Bean 并在相应位置通过 @Qualifier 注解指定对应的 bean name 即可。