|  | 本文参考孙卫琴,杜聚宾所创作的<<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。
 
   图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。
 
 
   
 图2  为enter资源设定流控规则
 通过浏览器访问消费者微服务,URL为http://localhost:8082/enter/Tom,不断刷新网页,有时会出现图3所示的错误提示页面。该页面的信息是由handleBlock()方法返回的。
 
   图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的错误页面:
   图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()方法返回的。
 
   图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 
 java.lang.NullPointerException
 
 如果你不知道错误发生的原因,请把上面完整的信息提交给本站管理人员。
 |  |