• Sentinel导航


    简介

    最近都在弄微服务的东西,现在来记录下收获。我从一知半解到现在能从0搭建使用最大的感触有两点

    1.微服务各大组件的版本很多,网上很多博客内容不一定适合你的版本,很多时候苦苦琢磨都是无用功

    2.网上博客参差不齐,有些甚至错误的。更离谱的是,好的文章阅读量除非高出天际,不然就都很低,比那些复制粘贴,随便应付的都低(这个搜索推荐算法不知道基于什么的)

    通过这段时间学习,我觉得最重要是从好的博客入手,先不要着急怎么组件怎么使用,而是先了解组件的作用,大概的原理,然后才是使用,这样搭建和尝试的过程中才能更好的定位问题,最后再次回到原理和一些实际问题的处理(不知道实际问题怎样的,直接搜那个组件的面试题往往效果最好)

    接下来的内容,都以导航的形式展现给大家(毕竟优秀的轮子很多,直接看大佬写的不香嘛),再顺带提些自己的理解

    传送门

    更多微服务的介绍可点击下方链接

    微服务介绍Nginx导航Nacos导航Gateway导航Ribbon导航Feign导航Sentinel导航

    博主微服务git练手项目:https://github.com/NiceJason/SpringCloudDemo

    Sentinel简介

    1.限流相关

    1.1限流点

    在聊Sentinel之前,先简单梳理下微服务限流的几个地方:

    1.Nginx限流,系统的最外层限流地点

    2.Gateway网关限流,Gateway可以用内置的限流Filter(RequestRateLimiterGatewayFilterFactory,依赖于redis),或者其他插件的限流Filter(如Redis的Redis RateLimiter),或者自定义Filter(自己实现限流算法),或者结合Sentinel或者Hystrix来限流。

    参考:https://blog.csdn.net/qq_38380025/article/details/102968559(博客的第八点 请求限流)

    3.微服务内部的限流,结合Sentinel或者Hystrix

    4.消息队列的限流,通过控制生产者和消费者的速度

    5.数据库连接池限流,一定时间内只能有一定数量的连接

    当需要限流的时候可以从这5个点去思考

    1.2限流算法

    参考:https://blog.csdn.net/qq_38380025/article/details/102968559(博客的第八点 请求限流)

    文章简述了常用的限流算法,如令牌桶算法、漏桶算法,可以大致了解一下,以后万一有场景需要手动实现的时候就能有个思路

    2.Sentinel相关

    Sentinel十分友好的中文安装使用文档:https://github.com/alibaba/Sentinel/wiki/%E6%96%B0%E6%89%8B%E6%8C%87%E5%8D%97

    里面介绍了基础的安装与使用(十分简单易上手),因为同是阿里系的组件,所以和Nacos结合的特别好,熔断限流等配置直接在Nacos上写即可,具体使用看文档(重点了解“资源”这个概念),这里讲下文档没有或者被忽视的地方

    2.1Json参数配置

    参考(规则种类):https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E8%A7%84%E5%88%99%E7%9A%84%E7%A7%8D%E7%B1%BB

    可以看到一些参数说明,具体参数值如果还不明白的,看该规则的类,里面会有默认参数及注释,一般都是使用RuleConst里的值,而里面的值代表什么意思可以看具体的Rule类

     
    如:限流规则 FlowRule

     

    2.2Sentinel和SpringCloud结合

    有一点比较重要,就是Sentinel和SpringCloud结合开始的地方在哪,在AbstractSentinelInterceptor开始

    public abstract class AbstractSentinelInterceptor implements HandlerInterceptor {
         ...
    }

    所以Sentinel可以把控程序的入口和出口,而掌握不了里面业务的处理(这点单这样听好像没卵用,但在实际功能开发和BUG查找中还是挺有用的,了解来龙去脉)

    2.2.1Sentinel熔断失效,不起作用

    比如:Sentinel设置了熔断,但是@FeignClient设置了Fallback方法对异常进行了处理,那么熔断是不生效的,以Sentinel的视角来看,没抛出异常就是正常执行。

    还有项目中往往@ExceptionHandle进行全局异常处理,这要也会导致熔断失效,下面简单的分析分析

    Sentinel与SpringCloud结合,是从AbstractSentinelInterceptor开始的
     
     1 public abstract class AbstractSentinelInterceptor implements HandlerInterceptor {
     2      @Override
     3     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
     4         throws Exception {
     5         try {
     6             String resourceName = getResourceName(request);
     7 
     8             if (StringUtil.isNotEmpty(resourceName)) {
     9                 // Parse the request origin using registered origin parser.
    10                 String origin = parseOrigin(request);
    11                 ContextUtil.enter(SENTINEL_SPRING_WEB_CONTEXT_NAME, origin);
    12                 Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
    13 
    14                 setEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName(), entry);
    15             }
    16             return true;
    17         } catch (BlockException e) {
    18             handleBlockException(request, response, e);
    19             return false;
    20         }
    21     }
    22 
    23     @Override
    24     public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
    25                                 Object handler, Exception ex) throws Exception {
    26         Entry entry = getEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName());
    27         if (entry != null) {
    28             //跟踪记录异常
    29             traceExceptionAndExit(entry, ex);
    30             removeEntryInRequest(request);
    31         }
    32         ContextUtil.exit();
    33     }
    34     
    35         protected void traceExceptionAndExit(Entry entry, Exception ex) {
    36         if (entry != null) {
    37             //要ex参数不为空才记录,但这个参数是可能为空的
    38             if (ex != null) {
    39                 Tracer.traceEntry(ex, entry);
    40             }
    41             entry.exit();
    42         }
    43     }
    44 }
    可以看出,它就是一个Spring的拦截器,preHandle方法就进行资源绑定(然后进行Sentinel的一连串的Slot判断),通过后才能正常访问资源
    而afterCompletion方法则进行收尾工作,如资源执行时抛出异常则记录,为熔断功能提供数据
    失效的原因恰恰是这个afterCompletion,我们可以看到traceExceptionAndExit方法里,需要ex不为空才记录异常信息,但这个参数是有可能会空的
    这就要到DispatcherServlet的doDispatch里面来,这里面负责调用interceptor,具体看里面的注释
     1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
     2        ...
     3 
     4         try {
     5             ModelAndView mv = null;
     6             Exception dispatchException = null;
     7 
     8             try {
     9                 ...
    10                 
    11                 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    12 
    13                 ...
    14 
    15                 // 这里会调用intercepter过滤链,调用其preHandle方法
    16                 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    17 
    18                 ...
    19             }
    20             catch (Exception ex) {
    21                 //这里捕获了所有异常
    22                 dispatchException = ex;
    23             }
    24             catch (Throwable err) {
    25                 //这里捕获了所有的Throwable
    26                 dispatchException = new NestedServletException("Handler dispatch failed", err);
    27             }
    28             //这个方法很关键,它抛不抛异常,取决于后面的catch能不能执行!!
    29             processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    30         }
    31         catch (Exception ex) {
    32             //调用interceptor的afterCompletion,有没熔断记录就看它有没执行了
    33             triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    34         }
    35         catch (Throwable err) {
    36             //调用interceptor的afterCompletion,有没熔断记录就看它有没执行了
    37             triggerAfterCompletion(processedRequest, response, mappedHandler,
    38                     new NestedServletException("Handler processing failed", err));
    39         }
    40         finally {
    41             ...
    42         }
    43     }

    我们首先来看看非常关键的processDispatchResult方法,这个方法只有两个地方抛出异常,如果这两个地方都没抛出异常,则doDispatch方法就不可能执行43或47行代码,意思是熔断信息将不会被记录,从而导致熔断失效

     1 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
     2             @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
     3             @Nullable Exception exception) throws Exception {
     4 
     5         boolean errorView = false;
     6 
     7         if (exception != null) {
     8             if (exception instanceof ModelAndViewDefiningException) {
     9                 logger.debug("ModelAndViewDefiningException encountered", exception);
    10                 mv = ((ModelAndViewDefiningException) exception).getModelAndView();
    11             }
    12             else {
    13                 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
    14                 //这里是执行异常处理器,如果用了@ExceptionHandler全局异常处理,就会被处理
    15                 //这里是能够抛出异常的第一个地方,但我们全局异常处理的目的就是终止程序抛出的异常
    16                 mv = processHandlerException(request, response, handler, exception);
    17                 errorView = (mv != null);
    18             }
    19         }
    20 
    21         // Did the handler return a view to render?
    22         if (mv != null && !mv.wasCleared()) {
    23             //这里是能够抛出异常的第二个地方,读取视图抛异常
    24             //可是有大部分情况都是返回数据,并不需要查找视图,所以mv经常等于null
    25             //并不会进这个if里面来
    26             render(mv, request, response);
    27             if (errorView) {
    28                 WebUtils.clearErrorRequestAttributes(request);
    29             }
    30         }
    31         else {
    32             if (logger.isTraceEnabled()) {
    33                 logger.trace("No view rendering, null ModelAndView returned.");
    34             }
    35         }
    36 
    37         if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    38             // Concurrent handling started during a forward
    39             return;
    40         }
    41 
    42         //这里注意第三个参数是null,即传入AbstractSentinelInterceptor类里的afterCompletion方法的ex参数为null
    43         //这样是做不了熔断记录的,所以上面的方法必须要抛出异常
    44         if (mappedHandler != null) {
    45             mappedHandler.triggerAfterCompletion(request, response, null);
    46         }
    47     }

    从上述流程可以看出,当我们使用@ExceptionHandler进行全局异常处理的时候,Sentinel并不能成功记录熔断信息,因为代码执行到它这异常已经被处理了,所以视为此次业务逻辑被正确执行

    2.2热点参数的限流原理

     参考:
    主要是要知道,相同参数是根据equal方法来比较是不是同一个对象,所以Object要重写此方法来符合自己的逻辑,不然的话热点参数设置无效

     2.3Sentinel统一降级处理

    参考:https://blog.csdn.net/theOldCaptain/article/details/107756801

    需要注意

    统一降级处理的资源不能用@SentinelResources修饰,否则是不走这个路的,需要自己配置@SentinelResources里的blockHandler和fallback这些方法
    在Sentinel2.0之后,统一降级处理这样写(实践过可行)
     1 import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
     2 import com.alibaba.csp.sentinel.slots.block.BlockException;
     3 import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
     4 import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
     5 import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
     6 import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
     7 import com.alibaba.fastjson.JSONObject;
     8 import com.syni.common.result.ResultJson;
     9 import lombok.extern.slf4j.Slf4j;
    10 import org.springframework.stereotype.Component;
    11 
    12 import javax.servlet.ServletOutputStream;
    13 import javax.servlet.http.HttpServletRequest;
    14 import javax.servlet.http.HttpServletResponse;
    15 import java.nio.charset.StandardCharsets;
    16 
    17 /**
    18  * @Author DiaoJianBin
    19  * @Description 别看idea标着@Component未被使用,实际上是被使用了的
    20  *              去掉的话此类无法生效
    21  * @Date 2021/4/22 10:44
    22  */
    23 @Component
    24 @Slf4j
    25 public class SystemBlockExceptionHandler implements BlockExceptionHandler {
    26     @Override
    27     public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException blockException) throws Exception {
    28         StringBuilder errorMessage = new StringBuilder();
    29         errorMessage.append("资源名称=");
    30         String resourceName = blockException.getRule().getResource();
    31 
    32         if (blockException instanceof FlowException) {
    33             errorMessage.append(" 被限流了");
    34         }else if(blockException instanceof DegradeException){
    35             errorMessage.append(" 被熔断了");
    36         }else if(blockException instanceof SystemBlockException){
    37             SystemBlockException systemBlockException = (SystemBlockException)blockException;
    38             errorMessage.append(" 触发系统保护 limitType=").append(systemBlockException.getLimitType());
    39         }else if(blockException instanceof ParamFlowException){
    40             //具体异常有特殊的方法
    41             ParamFlowException paramFlowException = (ParamFlowException)blockException;
    42             errorMessage.append(" 触发热点参数限流 limitParam=").append(paramFlowException.getLimitParam());
    43         }
    44         errorMessage.insert(5,resourceName);
    45         //日志记录具体原因
    46         log.error(errorMessage.toString());
    47 
    48         //这里返回个前端,不能说明具体原因的
    49         ResultJson resultJson = new ResultJson("102","系统繁忙,稍后再试");
    50 
    51         httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
    52         ServletOutputStream outputStream = httpServletResponse.getOutputStream();
    53         try{
    54             outputStream.write(JSONObject.toJSONString(resultJson).getBytes(StandardCharsets.UTF_8));
    55         }finally {
    56             outputStream.close();
    57         }
    58     }
    59 }
    sentinel降级方式的顺序,源码贴出,代码通俗易懂.
    (1).查看是否有自定义了BlockExceptionHandler异常处理器.
    (2).查看是否配置了降级页面.
    (3).以上都没有,则创建默认的BlockExceptionHandler异常处理器,也就是响应Blocked by Sentinel (flow limiting)的那个类.
    SentinelWebAutoConfiguration.java
     1 @Bean
     2 @ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true)
     3 public SentinelWebMvcConfig sentinelWebMvcConfig() {
     4     SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig();
     5     sentinelWebMvcConfig.setHttpMethodSpecify(properties.getHttpMethodSpecify());
     6 
     7     if (blockExceptionHandlerOptional.isPresent()) {
     8         blockExceptionHandlerOptional
     9                 .ifPresent(sentinelWebMvcConfig::setBlockExceptionHandler);
    10     }
    11     else {
    12         if (StringUtils.hasText(properties.getBlockPage())) {
    13             sentinelWebMvcConfig.setBlockExceptionHandler(((request, response,
    14                     e) -> response.sendRedirect(properties.getBlockPage())));
    15         }
    16         else {
    17             sentinelWebMvcConfig
    18                     .setBlockExceptionHandler(new DefaultBlockExceptionHandler());
    19         }
    20     }
    21 
    22     urlCleanerOptional.ifPresent(sentinelWebMvcConfig::setUrlCleaner);
    23     requestOriginParserOptional.ifPresent(sentinelWebMvcConfig::setOriginParser);
    24     return sentinelWebMvcConfig;
    25 }
     
    在Sentinel 2.0以前,据说是这样处理(未实践过)

    2.4Sentinel与网关结合

    参考:https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81#spring-cloud-gateway

    大体原理(以SpringCloudGateway为例)
    由于SpringCloudGateway里有一系列的Filter组成
    Sentinel会构造一个Filter放入其中,这样就能被Sentinel处理
    同时,Sentinel将路由名称或者一组API定义成资源,用新的路由规则来控制这些资源(明面上是GatewayFlowRule实际就变成了ParamFlowRule),这样就回到了Sentinel本身的代码逻辑,从网关逻辑里分离出来

    小结

    本篇博客围绕着Sentinel的小知识点开始讲,把我在学习了解,使用碰到的知识点分享给大家,希望能帮到大家~

    学习和点赞一样,可不能下次一定啊! 感谢你的三连~
  • 相关阅读:
    AngularJS定时器任务
    ssh常用
    HTTPClient模块的HttpGet和HttpPost
    eclipse下设置tomcat,修改Java代码不必重启tomcat
    【转】调试Release发布版程序的Crash错误
    C/C++ 函数压栈方式
    PHP 安全三板斧:过滤、验证和转义之转义篇 & Blade模板引擎避免XSS攻击原理探究
    让 MySQL 支持 emoji 存储
    Laravel 5.1 中创建自定义 Artisan 控制台命令实例教程
    常见的Web实时消息交互方式和SignalR
  • 原文地址:https://www.cnblogs.com/top-housekeeper/p/14804627.html
Copyright © 2020-2023  润新知