2 Sentinel 限流熔断降级
Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。我们先来学习Sentinel 核心库的使用,后面再学习Dashboard使用。
在我们项目中,用户请求通过hailtaxi-gateway
路由到hailtaxi-driver
或者hailtaxi-order
,还有可能在hailtaxi-order
中使用feign调用hailtaxi-driver
,所以我们有可能在单个服务中实现熔断限流,也有可能要集成feign调用实现熔断限流,还有可能在微服务网关中实现熔断限流。我们接下来一步一步实现每一种熔断限流操作。
SpringBoot集成:
如果在SpringBoot项目中使用Sentinel,首先需要引入spring-cloud-starter-alibaba-sentinel
依赖,并使用@SentinelResource
标识资源。
在hailtaxi-driver
工程中引入spring-cloud-starter-alibaba-sentinel
依赖,依赖如下:
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
2.1 @SentinelResource定义资源
@SentinelResource
用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource
注解包含以下属性:
value | 资源名称,必需项(不能为空) |
---|---|
blockHandler / blockHandlerClass | blockHandler 对应处理 BlockException 的函数名称,可选项。 ♞ blockHandler 函数访问范围需要是 public; ♞ 返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。 ♞ blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 |
fallback / fallbackClass | fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求: ♞ 返回值类型必须与原函数返回值类型一致; ♞ 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。 ♞ fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 |
defaultFallback(1.6.0 开始) | 默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求: ♞ 返回值类型必须与原函数返回值类型一致; ♞ 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。 ♞ defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 |
exceptionsToIgnore(1.6.0 开始) | 用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。 |
entryType | entry 类型,可选项(默认为 EntryType.OUT) |
blockHandler/blockHandlerClass
在hailtaxi-driver
中找到DriverController
中的info
方法,用户在打车的时候,会查询司机信息,如果司机不存在,此时会报错,代码改造如下:
/****
* 司机信息
*/
@GetMapping(value = "/info/{id}")
//@RequestMapping(value = "/info/{id}")
public Driver info(@PathVariable(value = "id")String id,HttpServletRequest request){
log.info("当前服务占用的端口为:{}",port);
Driver driver = driverService.findById(id);
if (driver==null) {
throw new RuntimeException("司机id="+id+"不存在");
}
return driver;
}
如果此时访问:http://localhost:18081/driver/info/3 查询司机信息,如果没有ID为3的司机信息,会报如下错误,
这种体验非常差,我们可以集成Sentinel使用@SentinelResource
的blockHandler
返回默认错误信息,形成降级!!!
1、Sentinel 支持在程序中抛出它定义的BlockException
异常,该异常会被Sentinel捕获,然后走降级方法,
为info()
方法添加一个@SentinelResource
注解,用来标注资源,表示当前方法需要执行限流、降级,在注解中添加value属性,用来标注资源,说白了就是给当前资源起个名字,blockHandler用来表示当前方法发生BlockException
异常的时候,将处理流程交给指定的方法blockExHandler()
处理,此时blockExHandler()
方法必须和抛出异常的方法在同一个类中,这是一种降级操作,代码如下:
/****
* 司机信息
*/
@SentinelResource(value = "info",blockHandler = "blockExHandler")
@RequestMapping(value = "/info/{id}")
public Driver info(@PathVariable(value = "id")String id) throws BlockException {
log.info("当前服务占用的端口为:{}",port);
Driver driver = driverService.findById(id);
if (driver==null) {
//throw new RuntimeException("司机id="+id+"不存在");
throw new SystemBlockException("info", "司机id="+id+"不存在",null); // 抛出BlockException
}
return driver;
}
/**
* info资源出现BlockException后的降级处理
*/
public Driver blockExHandler(String id,BlockException e) {
Driver driver = new Driver();
driver.setId(id);
driver.setName("系统繁忙,稍后再试");
return driver;
}
注意:
如果blockHandler方法和资源方法不在同一个类中,我们可以在
@SentinelResource
中添加blockHandlerClass
属性,指定降级处理类的方法所在的类,且要求blockHandler方法是静态的,代码如下:@SentinelResource(value = "info",blockHandler = "blockExHandler",blockHandlerClass = "xxx.xxx.Xxxx")
2、启动测试,访问:http://localhost:18081/driver/info/3 测试出错效果如下:
fallback/fallbackClass
1、如果我们希望抛出任何异常都能处理,都能调用默认处理方法,而并非只是BlockException
异常才调用,此时可以使用@SentinelResource
的fallback
属性,代码如下:
/****
* 司机信息
*/
@SentinelResource(value = "info"/*,blockHandler = "blockExHandler"*/,fallback = "exHandler")
@RequestMapping(value = "/info/{id}")
public Driver info(@PathVariable(value = "id")String id) throws BlockException {
log.info("当前服务占用的端口为:{}",port);
Driver driver = driverService.findById(id);
if (driver==null) {
throw new RuntimeException("司机id="+id+"不存在");
// throw new SystemBlockException("info", "司机id="+id+"不存在",null); // 抛出BlockException
}
return driver;
}
/**
* info资源出现任何类型异常后的降级处理
* 方法参数可以添加一个Throwable 类型的参数,也可不添加
*/
public Driver exHandler(String id,Throwable e) {
Driver driver = new Driver();
driver.setId(id);
driver.setName("系统繁忙,稍后再试");
return driver;
}
注意:
如果fallback方法和当前的资源方法不在同一个类中,可以使用
@SentinelResource
注解的fallbackClass
实现,也要求fallback方法是静态的,代码如下:@SentinelResource(value = "info",fallback ="exHandler" ,fallbackClass = "xx.xxx.xxx.xx.X