>>分享孙卫琴的Java技术专稿和著作 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 8351 个阅读者 刷新本主题
 * 贴子主题:  【Spring Cloud Alibaba专题】@SentinelResource注解的用法 回复文章 点赞(0)  收藏  
作者:sunweiqin    发表时间:2022-08-25 09:44:07     消息  查看  搜索  好友  邮件  复制  引用

本文参考孙卫琴,杜聚宾所创作的<<Spring Cloud Alibaba微服务开发宝典>>一书,即将出版

对于Sentinel,如果还需要针对特定资源定制客户化规则,则可以使用@SentinelResource注解,它有以下属性:
(1)value属性:指定资源的名字。
(2)blockHandler属性:指定当请求被Sentinel拒绝的处理方法。
(3)blockHandlerClass属性:指定当请求被Sentinel拒绝的处理类。
(4)fallback属性:指定响应过程中产生的异常的处理方法。
(5)fallbackClass属性:指定响应过程中产生的异常的处理类。
(6)exceptionsToIgnore:指定需要被忽略的异常。当出现这些异常时,fallback属性以及fallbackClass属性的设定值不起作用。

当控制器类的一个请求处理方法用@SentinelResource注解标识,它就成为Sentinel的资源,可以为它指定客户化的规则,主要包括三个方面:
(1)指定热点规则。针对热点参数进行限流。
(2)指定当请求被Sentinel拒绝的处理方式。
(3)指定在响应过程中产生异常的处理方式。

1.  热点规则

问题:“有一条道路进行交通管制,十分钟内只允许通过一辆车。假如执行紧急灭火任务的一组消防车要通过这条道路,这个规则是否适用呢?”
答案:“应该有所变通,比如允许一分钟内通过五辆消防车。”

同样,对请求进行限流,也会遇到特例。比如某个购物网站的一位VIP用户,频繁光顾网站,大量购物。如果对该用户进行常规限流,就是断了网站的财路。另一方面,对于故意到网站捣乱的信用不良的用户,则需要进行比常规限流更严格的管控。
对于消费者模块,假定资源/enter/{username}的username参数为热点参数,需要对用户Tom和Mike进行专门的限流。

为了配置热点规则,首先在消费者模块的HelloConsumerController类中,为sayHello()方法加上@SentinelResource注解,该注解的value属性把资源名设为enter:

@SentinelResource(value="enter")
@GetMapping(value = "/enter/{username}")
public String sayHello(@PathVariable String username) {……}

接下来在Sentinel控制台的管理页面新增热点规则,参见图1,资源名设为enter,与@SentinelResource注解的value属性值必须一致。
图1的参数索引指定参数位于请求数据中的序号。假定一个资源的URL路径为:/test/{param1}/{param2}。在这个URL路径中有两个参数:param1和param2。其中param1参数的索引为0,param2参数的索引为1。
点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小
图1  设置热点规则
图1设置的热点规则如下:
(1)普通请求的限流阈值为3,即在统计窗口时长一秒内只允许通过3个请求。
(2)如果请求中索引为0的参数(即第1个参数)的值为Tom,那么限流阈值为20。
(3)如果请求中索引为0的参数的值为Mike,那么限流阈值为1。

由此可见,热点规则允许对不同的热点参数值,设定不同的限流阈值。
通过浏览器访问消费者微服务的以下URL,不断刷新网页,会看到限流的效果不一样:

http://localhost:8080/enter/Jack   //限流阈值为3
http://localhost:8080/enter/Tom    //限流阈值为20
http://localhost:8080/enter/Mike   //限流阈值为1

2.  请求被拒绝的处理

当Sentinel依据特定的规则拒绝一个请求时,会产生BlockException,对这种异常的默认处理方式为返回一个拒绝页面。这种页面带来的用户体验不是很友好。如果需要定制请求被拒绝的处理方式,可以指定@SentinelResource注解的blockHandler属性。

以下代码中的@SentinelResource注解设定了blockHandler属性,它指定由handleBlock()方法来处理请求被拒绝的情况:

@SentinelResource(value="enter",
                      blockHandler="handleBlock")
@GetMapping(value = "/enter/{username}")
public String sayHello(@PathVariable String username) {……}

public String handleBlock(String username,
                              BlockException ex) {
  ex.printStackTrace();
  username+",request is blocked.";
}

handleBlock()方法也在HelloConsumerController类中定义,它的参数和返回类型必须和sayHello()方法相同,此外,还需要增加一个BlockException类型的参数,表示当前的BlockException对象。
在Sentinel控制台为以上@SentinelResource注解所标识的enter资源设定一个流控规则,参见图2。

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

图2  为enter资源设定流控规则
通过浏览器访问消费者微服务,URL为http://localhost:8082/enter/Tom,不断刷新网页,有时会出现图3所示的错误提示页面。该页面的信息是由handleBlock()方法返回的。
点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小
图3  handleBlock()方法返回的错误信息
如果希望在单独的类中处理请求被拒绝的情况,可以设定@SentinelResource注解的blockHandler属性和blockHandlerClass属性:

@SentinelResource(value="enter",
           blockHandler="handleBlock",
           blockHandlerClass=MyBlockHandler.class)
@GetMapping(value = "/enter/{username}")
public String sayHello(@PathVariable String username){……}

以上@SentinelResource注解指定由MyBlockHandler类的handleBlock()方法处理请求被拒绝的情况。例程1是MyBlockHandler类的源代码。

/* 例程1  MyBlockHandler.java */
public class MyBlockHandler {
  public static String handleBlock(String username,
                                    BlockException ex) {
    ex.printStackTrace();
    return username+",request is blocked.";
  }
}

值得注意的是,当以上handleBlock()方法位于单独的MyBlockHandler类中,必须是静态方法。

3.  对异常的处理

Sentinel拒绝请求时会抛出BlockException,为了与这种异常区别,本书把由HelloConsumerController类的sayHello()方法处理业务逻辑抛出的异常称为服务异常。默认情况下,当出现服务异常,会返回如图4的错误页面:
点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小
图4  sayHello()方法抛出异常时返回的错误页面
以上错误页面带来的用户体验不是很友好。如果需要定制对异常的处理方式,可以设置@SentinelResource注解的fallback属性。
以下代码的@SentinelResource注解设定了fallback属性,它指定由handleException()方法处理异常:

@SentinelResource(value="enter",
                     fallback="handleException")
@GetMapping(value = "/enter/{username}")
public String sayHello(@PathVariable String username) {
  if(username.equals("Monster"))
    throw new IllegalArgumentException(
                                   "Illegal Argument");

  return helloFeignService.sayHello(username);
}

public String handleException(String username,
                                            Throwable ex) {
  ex.printStackTrace();
  return username+",something is wrong.";
}

handleException()方法也在HelloConsumerController类中定义,它的参数和返回类型必须和sayHello()方法相同,此外,还需要增加一个Throwable类型的参数,表示当前产生的异常对象。

如果没有设定@SentinelResource注解的blockHandler属性,那么对于Sentinel拒绝请求而产生的BlockException,也是由fallback属性指定的方法来处理。

通过浏览器访问http://localhost:8082/enter/Monster,就会出现图5所示的错误提示页面。该页面的信息就是由handleException()方法返回的。
点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小
图5  handleException()方法返回的错误信息
如果希望在单独的类中处理异常,可以设定@SentinelResource注解的fallback属性和fallbackClass属性:

@SentinelResource(value="enter",
          fallback="handleException",
          fallbackClass = MyExceptionHandler.class)
@GetMapping(value = "/enter/{username}")
public String sayHello(@PathVariable String username) {……}

以上@SentinelResource注解指定由MyExceptionHandler类的handleException()方法处理异常。例程2是MyExceptionHandler类的源代码。

/* 例程2  MyExceptionHandler.java */
public class MyExceptionHandler {
  public static String handleException(
                   String username, Throwable ex) {
    ex.printStackTrace();
    return username+",something is wrong.";
  }
}

值得注意的是,当以上handleException()方法位于单独的MyExceptionHandler类中,必须是静态方法。
@SentinelResource注解还有一个exceptionsToIgnore属性,指定所有被忽略的异常,例如:

    @SentinelResource(value="enter",
            blockHandler="handleBlock",
            blockHandlerClass=MyBlockHandler.class,
            fallback="handleException",
            fallbackClass = MyExceptionHandler.class,
            exceptionsToIgnore={NullPointerException.class})

以上代码表明,当出现NullPointerException异常时,MyExceptionHandler类的handleException()方法不会处理该异常。



程序猿的技术大观园:www.javathinker.net
网站系统异常


系统异常信息
Request URL: http://www.javathinker.net/WEB-INF/lybbs/jsp/topic.jsp?postID=4209&pages=4

java.lang.NullPointerException

如果你不知道错误发生的原因,请把上面完整的信息提交给本站管理人员