- 
     
 微服务系列:Spring Cloud Alibaba 之 Sentinel 基本流控规则
 
- 
     
 微服务系列:Spring Cloud Alibaba 之 Sentinel 高级流控规则
 
- 
     
 微服务系列:Spring Cloud Alibaba 之 Sentinel 熔断降级规则
 
- 
     
 微服务系列:Spring Cloud Alibaba 之 Sentinel 热点限流规则
 
    在上面的学习
    
     Sentinel
    
    的各种规则的文章中,我们多次使用到
    
     @SentinelResource
    
    注解,虽然在入门篇
    
     微服务系列:Spring Cloud Alibaba 之 Sentinel 详细入门
    
    中提到过
    
     @SentinelResource
    
    注解,但我觉得还是需要更详细的学习一下这个注解,毕竟我们用来定义
    
     Sentinel
    
    的资源,大概率还是使用此注解来定义,个人觉得,大家现在应该都比较倾向于注解式开发。
   
话不多说,开始今天的学习。
    
    
    一、@SentinelResource 注解
   
    
     @SentinelResource
    
    用于定义资源,并提供可选的异常处理和 fallback 配置项。
    
     @SentinelResource
    
    注解包含以下属性:
   
- 
 value
 
 :资源名称,必需项(不能为空)
- 
 entryType
 
 :entry 类型,可选项(默认为
 
 EntryType.OUT
 
 )
- 
 blockHandler
 
 /
 
 blockHandlerClass
 
 :
 
 blockHandler
 
 对应处理
 
 BlockException
 
 的函数名称,可选项。blockHandler 函数访问范围需要是
 
 public
 
 ,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为
 
 BlockException
 
 。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
 
 blockHandlerClass
 
 为对应的类的
 
 Class
 
 对象,注意对应的函数必需为 static 函数,否则无法解析。
- 
 fallback/fallbackClass
 
 :fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:- 返回值类型必须与原函数返回值类型一致;
- 
       方法参数列表需要和原函数一致,或者可以额外多一个
 
 Throwable
 
 类型的参数用于接收对应的异常。
- 
       fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
 
 fallbackClass
 
 为对应的类的
 
 Class
 
 对象,注意对应的函数必需为 static 函数,否则无法解析。
 
- 
 defaultFallback
 (since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求: - 返回值类型必须与原函数返回值类型一致;
- 
       方法参数列表需要为空,或者可以额外多一个
 
 Throwable
 
 类型的参数用于接收对应的异常。
- 
       defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
 
 fallbackClass
 
 为对应的类的
 
 Class
 
 对象,注意对应的函数必需为 static 函数,否则无法解析。
 
- 
 exceptionsToIgnore
 
 (since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
在前面的学习中有这样一个案例:
@Service
public class IUserServiceImpl implements IUserService {
    public static final String RESOURCE_NAME = "selectUserByName";
    @Override
    @SentinelResource(value = RESOURCE_NAME, blockHandler = "selectUserByNameBlockHandler", fallback = "selectUserByNameFallback")
    public String selectUserByName(String username) {
        return "{\"userName\": " + username + ", \"age\": 25}";
    }
    // 服务流量控制处理,参数最后多一个 BlockException,其余与原函数一致。
    public String selectUserByNameBlockHandler(String username, BlockException ex)
    {
        System.out.println("selectUserByNameBlockHandler异常信息:" + ex.getMessage());
        return "{\"code\":\"500\",\"msg\": \"" + username + "服务流量控制处理\"}";
    }
    // 服务熔断降级处理,函数签名与原函数一致或加一个 Throwable 类型的参数
    public String selectUserByNameFallback(String username, Throwable throwable)
    {
        System.out.println("selectUserByNameFallback异常信息:" + throwable.getMessage());
        return "{\"code\":\"500\",\"msg\": \"" + username + "服务熔断降级处理\"}";
    }
}
直接看上面的文字可能不太直观,我们直接实战来操作一下
    
    
    二、
    
     blockHandler
    
    和
    
     fallback
    
   
blockHandler
fallback
在前面的学习中,我们最常用的就是这两个属性了。
    
    
    1. blockHandler
   
blockHandler
是
Sentinel
的限流兜底方法
@RestController
public class ResourceController {
    @GetMapping("/testResource")
    @SentinelResource(value = "testResource")
    public String testResource(){
        return "testResource....";
    }
}
@GetMapping(“/testResource”)
@SentinelResource(value = “testResource”)
    
     /testResource
    
    和
    
     testResource
    
    一个是按 url 限流,一个是按资源名称限流
   
    
     1.1、给
     
      /testResource
     
     添加一个限流规则
    
   
     
   
     
   
    此时访问地址:
    
     localhost:9201/testResource
    
    会返回
    
     Sentinel
    
    默认的限流提示
   
     
   
    
     1.2、给资源名称
     
      testResource
     
     配置一个流控规则
    
   
    
     /testReource
    
    的流控规则删掉
   
     
   
    再访问地址:
    
     localhost:9201/testResource
    
    会直接报错。
   
     
   
    我们再修改
    
     @SentinelResource(value = "testResource")
    
    属性添加一个限流成功的兜底方法
   
@GetMapping("/testResource")
@SentinelResource(value = "testResource", blockHandler = "resourceBlockHandler")
public String testResource(){
    return "testResource....";
}
public String resourceBlockHandler(BlockException ex){
    return "服务不可用!";
}
    重启项目,重新添加
    
     testResource
    
    资源名称的流控规则,访问地址,触发限流后就进入了我们自定义的 blockHandler 方法中
   
     
   
    
     1.3、blockHandlerClass
    
   
上面 1.2 的配置形式存在的问题:
- 自定义的处理方法和业务代码耦合在一起,不直观;
- 每个业务方法都添加一个兜底方法,代码急剧膨胀;
- 全局统一的处理方法没有体现。
    所以就有了
    
     fallbackClass
    
    属性的配置
   
    **①、**自定义一个全局的 handler
    
     CustomerBlockHandler.java
    
public class CustomerBlockHandler {
    public static String handlerException(@PathVariable int id, BlockException e) {
        return "用户自定义,全局 handlerException---1";
    }
    public static String handlerException2(@PathVariable int id, BlockException e) {
        return "用户自定义,全局 handlerException---2";
    }
}
注意对应的函数必需为 static 函数,否则无法解析。
    **②、**使用
    
     blockHandlerClass
    
    属性
   
@RestController
public class ResourceController {
    @GetMapping("/testResource")
    @SentinelResource(value = "testResource",
            blockHandlerClass = CustomerBlockHandler.class,
            blockHandler = "handlerException2")
    public String testResource(){
        return "testResource....";
    }
}
    
    
    2. fallback
   
fallback
是
Java
自身的运行时异常兜底方法
    
     2.1、定义
     
      testFallback
     
     资源
    
   
@GetMapping("/testFallback/{id}")
@SentinelResource(value = "testFallback")
public String testFallback(@PathVariable int id){
    if (id == 1) {
        throw new IllegalArgumentException("IllegalArgumentException,非法参数异常!");
    } else if (id == 2) {
        throw new NullPointerException("NullPointerException, 空指针异常!");
    }
    return "testFallback....";
}
接口中模拟抛出 Java 运行时异常,此时访问该接口,会直接出现 Error Page
     
   
     
   
    
     2.2、添加
     
      fallback
     
     属性
    
   
@GetMapping("/testFallback/{id}")
@SentinelResource(value = "testFallback", fallback = "handlerFallback")
public String testFallback(@PathVariable int id){
    if (id == 1) {
        throw new IllegalArgumentException("IllegalArgumentException,非法参数异常!");
    } else if (id == 2) {
        throw new NullPointerException("NullPointerException, 空指针异常!");
    }
    return "testFallback....";
}
public String handlerFallback(@PathVariable int id, Throwable e) {
    return "服务器内部错误:" + e.getMessage();
}
    重启项目,访问地址:
    
     localhost:9201/testFallback/1
    
   
     
   
    
     2.3、fallbackClass 属性
    
   
    同样的
    
     fallback
    
    同样有类似
    
     blockHandlerClass
    
    的属性
    
     fallbackClass
    
public class CustomerFallback {
    public static String handlerFallback(@PathVariable int id, Throwable e) {
        return "服务器内部错误:" + e.getMessage();
    }
    public static String handlerFallback2(@PathVariable int id, Throwable e) {
        return "服务器内部错误2:" + e.getMessage();
    }
}
@GetMapping("/testFallback/{id}")
@SentinelResource(value = "testFallback", fallbackClass = CustomerFallback.class, fallback = "handlerFallback2")
public String testFallback(@PathVariable int id){
    if (id == 1) {
        throw new IllegalArgumentException("IllegalArgumentException,非法参数异常!");
    } else if (id == 2) {
        throw new NullPointerException("NullPointerException, 空指针异常!");
    }
    return "testFallback....";
}
    浏览器访问:
    
     localhost:9201/testFallback/1
    
   
     
   
注:1.6.0 之前的版本 fallback 函数只针对降级异常(
DegradeException
)进行处理,
不能针对业务异常进行处理
。
    
    
    3.
    
     blockHandler
    
    VS
    
     fallback
    
   
blockHandler
fallback
    特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出
    
     BlockException
    
    时只会进入
    
     blockHandler
    
    处理逻辑。若未配置
    
     blockHandler
    
    、
    
     fallback
    
    和
    
     defaultFallback
    
    ,则被限流降级时会将
    
     BlockException
    
    
     直接抛出
    
    (若方法本身未定义 throws BlockException 则会被 JVM 包装一层
    
     UndeclaredThrowableException
    
    )。
   
两者都配置
@GetMapping("/testFallback/{id}")
//@SentinelResource(value = "testFallback")
@SentinelResource(value = "testFallback",
        blockHandlerClass = CustomerBlockHandler.class,
        blockHandler = "handlerException2",
        fallbackClass = CustomerFallback.class,
        fallback = "handlerFallback2")
public String testFallback(@PathVariable int id){
    if (id == 1) {
        throw new IllegalArgumentException("IllegalArgumentException,非法参数异常!");
    } else if (id == 2) {
        throw new NullPointerException("NullPointerException, 空指针异常!");
    }
    return "testFallback....";
}
    对资源
    
     testFallback
    
    新增流控规则
   
     
   
    访问地址:
    
     localhost:9201/testFallback/1
    
    ,这时还没有触发限流
   
     
   
    快速刷新该地址,进入到
    
     blockHandler
    
    中
   
     
   
被限流降级而抛出
BlockException
时只会进入
blockHandler
处理逻辑
    
    
    三、exceptionsToIgnore
   
    
     exceptionsToIgnore
    
    这个就比较简单了
   
用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
我们来试一下
@GetMapping("/testFallback/{id}")
@SentinelResource(value = "testFallback",
        exceptionsToIgnore = IllegalArgumentException.class,
        fallbackClass = CustomerFallback.class,
        fallback = "handlerFallback2")
public String testFallback(@PathVariable int id){
    if (id == 1) {
        throw new IllegalArgumentException("IllegalArgumentException,非法参数异常!");
    } else if (id == 2) {
        throw new NullPointerException("NullPointerException, 空指针异常!");
    }
    return "testFallback....";
}
    浏览器访问:
    
     localhost:9201/testFallback/1
    
   
     
   
    
     IllegalArgumentException
    
    确实被排除掉了,没有进入 fallback 逻辑中。
   
至此,本文结束。
    PS: 都看到这里了,点个赞吧,
    
     彦祖
    
    !
   
 
