Alibaba Sentinel熔断、限流使用说明

说明

Sentinel官方文档写的不是很详细,坑比较多。本人主要是改了Sentinel的控制台release版本源码,测试环境又使用的是我改源码后打包的jar,有两个功能支持不是很好(参数限流和黑白名单授权),如果有其他问题,请联系我。但是基本上95%的工作都已经被我封装了,大家只需不到5%的工作量,当前熔断、降级支持Hystrix方案,所以以前的代码可以几乎不动(有少量问题下面会说)。

Sentinel控制台:
http://192.168.128.36:8081/#/dashboard/metric/gateway-server
用户名/密码:myyshop

引入pom包

引入下述包即可使用sentinel

        <!-- sentinel自定义封装组件 -->
        <dependency>
            <groupId>com.myyshop.framework</groupId>
            <artifactId>sentinel-spring-boot-starter</artifactId>
            <version>1.0.0-RELEASE</version>
        </dependency>

其他基础包引用版本升级和现有影响请严格按照Confluence->基础组件变更记录->变更时间2020-10-14修改,Confluence地址为:

http://192.168.128.17:8090/pages/viewpage.action?pageId=3768400

Nacos配置

需要引入 global-public-share.yml 共享配置,这样即可开启 Sentinel 和 Nacos 持久化

  spring:
    cloud:
      config:
        server-addr: 192.168.128.20
        file-extension: yml
        group: uaa-center-server
        namespace: 1c53b59e-9f3d-44a4-b2d7-5faaea25b3c6 # dev 环境的命名空间
        extension-configs:
          - data-id: global-public-share.yml
            group: share
            refresh: true

配置Sentinel控制台链路

bootstrap-dev.yml.example 全部配置如下:

spring:
  cloud:
    sentinel:
      transport:
        client-ip: 192.168.128.41  #如果docker 在20上就配置成20。prod 和 test不需要指定该参数
        port: 8733    #这个端口配置不能跟其他服务重复

bootstrap-prod.yml.examplebootstrap-test.yml.example 跟上述dev配置类似,只是不需要指定client-ip,端口跟上面一样

下面注意两点:

  • client-ip 看自己负责微服务所在开发环境ip(参照下图client-ip)
  • port 去 端口总览 -> sentinel端口 找自己的负责微服务端口,端口总览地址点击
client-ip

注: 如果还看不懂的,参照uaa-center-server服务下bootstrap-dev.yml.example、bootstrap-prod.yml.example 和 bootstrap-test.yml.example

使用Feign+Hystrix处理熔断、降级

用过Hystrix的都知道熔断、降级需要指定具体降级类(也就是fallback),同时加入@Component注解,否则项目启动报错当前Sentinel兼容Hystrix的降级类。而现在大家代码里的fallback根本形同虚设,不管任何异常都不会走进去,当然造成这个现象也有一些历史原因的,本来要用Hystrix的,后面又决定不用了,原因是Hystrix已经闭源了,开源社区已经不活跃的组件,已经没有使用的必要。然后想着后面用Sentinel这个功能就会生效。

目前大家基本都是使用Feign调接口,使用方法跟大家现有Feign一样,几乎不需要改动,害怕大家看不懂,直接贴个例子:

接口端

@FeignClient(value= ServiceNameConstants.DS_USER_PLATFORM, fallbackFactory = DsUserFeignClientFallback.class)
public interface DsUserFeignClient {
    @GetMapping("/api/users/mail/{mail}")
    Result<LoginAppUser> findUserByMail(@RequestParam("mail") @PathVariable("mail") String mail);
}

Fallback类

必须加@Component注解,否则项目不能启动, 这点Hystrix跟Sentinel是一样的

@Component    //注意:必须加@Component注解,否则项目不能启动
public class DsUserFeignClientFallback implements FallbackFactory<DsUserFeignClient> {
    @Override
    public DsUserFeignClient create(Throwable throwable) {
        return new DsUserFeignClient() {
            @Override
            public Result findUserByMail(String mail) {
                log.error("通过邮箱查询用户异常:{}", mail, throwable);
                throw new BusinessException(BusinessExceptionEnum.BUSINESS_CALLBACK_SERVICE_ERROR.getCode()
                , BusinessExceptionEnum.BUSINESS_CALLBACK_SERVICE_ERROR.getMsg() + ServiceNameConstants.DS_USER_PLATFORM + ", 接口方法名:findUserByMail");
            }
        };
    }
}

这地方有个坑,原因是我们现在把服务调用Feign去拆出一个API项目出来(加注解项目会报错,当然本人有解决方案),但是本人不建议拆个服务出来,具体原因看下面的 其他 标题, 会详细分析。

Feign的fallback与fallbackFactory区别

fallback
fallbackFactory

使用Sentinel处理熔断、降级

本着对大家代码改动最小考虑,以前接口代码怎么样,现在还是一样,以前Feign调用fallback怎么处理(就是上面那个方案),现在还是一样。本人已经对Sentinel相关异常统一进行封装返回,一旦遇到Sentinel相关异常,则会默认抛出我的包装返回。
当然,在实际业务开发中,业务场景千奇百怪,虽然我封装的Sentinel默认返回处理适应大部分场景,但是还是有些业务场景需要指定自己的Sentinel异常。可以在需要特定Sentinel异常的接口加入@SentinelResource 注解,可以参照以下两个例子:

例子1:

  • value 指定resource名称,这个必须加,而且尽量不要跟,其他资源名重复
  • blockHandler 指定Sentinel异常返回的方法,参数必须跟接口一致+BlockException参数 ,需要注意的是,方法必须是跟接口在同一个类中
  • defaultFallback 指定接口提供方的异常 或 BlockException 异常(这个可以替代Hystrix的fallback的功能),需要注意的是,方法必须是跟接口在同一个类中
    @SentinelResource(value = "/uaa/hello", blockHandler = "exceptionHandler2", defaultFallback = "fallback2")
    @GetMapping(value = "/hello")
    public String abcd(@RequestParam(value = "name") String name, int age){
        log.info("name={}",name);
        log.info("age={}",age);
        String content = nacosProviderClient.abc(name, age);
        return "我是feign: " + content;
    }
    public String exceptionHandler2(String name, int age, BlockException ex) {.
        ex.printStackTrace();
        return "Oops, error occurred at "+ name + ","+ age;
    }
    public String fallback2(Throwable e){
        log.info("进入sentinelResource注解测试,进入fallback2,参数b={}", e.getMessage());
        return "defaultFallback";
    }

例子2:

  • value 同上
  • fallbackClass 指定异常返回的类(跟例子1不同的是,上面的异常方法必须在本类,而fallbackClass 可以指定其它类)
  • blockHandler & defaultFallback 跟例子1解释基本一样,不同的是,异常方法必须写在 fallbackClass 指定的类中
    @GetMapping(value = "/abcd")
    @SentinelResource(value = "abcd", fallbackClass = DefaultBlockFallbackHandler.class, defaultFallback = "defaultFallback")
    public String abcd(String name){
        if(name.equals("abc")){
            throw new IllegalArgumentException("adffdgdfg");
        }
        if(name.equals("bbb")){
            try {
                Thread.sleep(900);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return "aaaaaaasdjkl" + name;
    }

以上两个例子大家可以根据自己业务实际情况,定制自己的异常,也可以不做任何处理,会默认走我封装的返回。此外,我还提供了一个通用异常类(例子2上面那个DefaultBlockFallbackHandler就是我对大家封装的一个通用异常),大家可以按需引用(不引用不会生效),通用异常类在sentinel-spring-boot-starter基础包中:

public class DefaultBlockFallbackHandler {
    public static String defaultFallback(Throwable e){
        if(e instanceof FlowException){
            FlowException ex = (FlowException) e;
            log.error("defaultFallback FlowException,资源信息={}", JSON.toJSONString(ex.getRule()));
            throw new SeataTrBusinessException(BusinessExceptionEnum.BUSINESS_FLOWEXCEPTION);
        } else if(e instanceof DegradeException){
            DegradeException ex = (DegradeException) e;
            log.error("defaultFallback DegradeException,资源信息={}", JSON.toJSONString(ex.getRule()));
            throw new SeataTrBusinessException(BusinessExceptionEnum.BUSINESS_DEGRADEEXCEPTION);
        } else if(e instanceof ParamFlowException){
            ParamFlowException ex = (ParamFlowException) e;
            log.error("defaultFallback ParamFlowException,资源名={},参数={},资源信息={}", ex.getResourceName(), ex.getLimitParam(), JSON.toJSONString(ex.getRule()));
            throw new SeataTrBusinessException(BusinessExceptionEnum.BUSINESS_PARAMFLOWEXCEPTION.getCode(), BusinessExceptionEnum.BUSINESS_PARAMFLOWEXCEPTION.getMsg() + e.getMessage());
        } else if(e instanceof AuthorityException){
            AuthorityException ex = (AuthorityException) e;
            log.error("defaultFallback AuthorityException,资源信息={}", JSON.toJSONString(ex.getRule()));
            throw new SeataTrBusinessException(BusinessExceptionEnum.BUSINESS_AUTHORITYEXCEPTION);
        }
        throw new SeataTrBusinessException(999, "defaultFallback, error = " + e.getClass().getName() + ": " + e.getMessage());
    }
}

无论是Feign+Hystrix方案还是Sentinel方案都需要大家根据自身业务需要去选型,也可以组合用。基本做到大家最少改动,就算大家现在代码什么都不改(除了Feign的fallback要加@Component注解外),也会走我封装的默认处理(默认会抛出异常)。但是还是那句话,结合自身业务场景去用,而不是一味的懒而不做处理(参照我上面Fallback类那块例子按自身业务去返回什么),默认走我抛出的异常。这时调用方就需要跟接口提供方协调代码异常是抛出BusinessException 还是 SeataTrBusinessException(这两种异常下面会进行说明),而不是什么都不处理或简单处理,线上接口出错,接口提供方和接口调用方互相指责

BusinessException 和 SeataTrBusinessException

说明:BusinessException 和 SeataTrBusinessException内部代码几乎一样,只是一个request state返回200,一个返回500

接口提供方代码异常无非就两种场景:

  • 第一种场景是接口提供方抛出异常,调用方不捕获跟着抛出(SeataTrBusinessException)
  • 第二种场景是接口提供方不抛异常(返回的是一个Result.fail对象),接口调用方拿到Result对象,去判断success是否是false,如果是false,进行相应业务错误处理,否则是true拿着接口提供方返回的数据继续执行后续业务代码(BusinessException )

SeataTrBusinessException:接口提供方异常会被抛出,这时调用方不处理(没有走feign fallback或者try cache捕获),也会跟着往上抛而出错。这种适合上面第一种场景。

BusinessException :接口提供方异常不会抛出,调用方拿到的是Result.fail(success=false)的对象,这时调用方可以根据success去判断接下来怎么处理。这种适合上面第二种场景

注意:SeataTrBusinessException如果在分布式事务场景下,只要一方服务出现任何异常(包括调用服务超时),其他服务事务跟着会回滚。而BusinessException只会回滚那个报错的服务事务。

其他

另外我想说的是,不建议大家把服务拆分出一个API项目出来,就比如拿上面的Feign fallback为例,如果服务拆分出来,会导致你不加@Component注解,服务可以调用,但是熔断、降级失效;加了这个注解,你连你自己的服务因为报错都启动不起来。(当然这个我也有解决方案)

Dubbo服务间调用默认是一种二进制TCP协议,它本身就需要握手来传输数据,所以导致他是有状态的,这就是为什么dubbo服务调用是调用对方的service,协议设计上没有足够的前瞻性,不适合做 service-mesh。但是熟悉dubbo的人肯定会想用dubbo这种调用方式,但是这种方式本身就不适合服务间调用(具体可以看dubbo协议介绍)。

而Spring Cloud的走的是HTTP协议,HTTP协议本身就是无状态的(对同一个url请求没有上下文关系),就算服务提供方的服务本身有问题,我调用方最多包一层处理就好了,也总比我依赖你的API POM包,导致我调用方服务都运行不起来好。

所以我的建议是Feign调用就在自身服务里,不要单独拆个API项目给别人用。

链路监控

Sentinel提供链路监控功能,能监控每个请求,或者某个微服务的QPS或响应时间(该功能需要添加网关),所以能充当一部分监控功能。

针对接口的监控
针对微服务监控
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343