0
点赞
收藏
分享

微信扫一扫

SpringCloud Alibaba实战--第七篇:Sentinel Ⅲ服务熔断和持久化

系列文章目录

微服务新王SpringCloudAlibaba


文章目录


前言

Sentinel: 分布式系统的流量防卫兵

在这里插入图片描述

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
  • 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
  • 官方文档:https://github.com/alibaba/Sentinel
  • 中文文档:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
  • sentinel与hystrix功能类似,但是更优。

一、@SentinelResource注解

1. 按资源名称限流

添加RateLimitController接口,用于测试

package com.atguigu.springcloud.alibaba.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @auther zzyy
 * @create 2020-02-25 15:04
 */
@RestController
public class RateLimitController
{
    @GetMapping("/byResource")
    @SentinelResource(value = "byResource",blockHandler = "handleException")
    public CommonResult byResource()
    {
        return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
    }
    public CommonResult handleException(BlockException exception)
    {
        return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
    }
}

好,简单测试一下,这块不再讲,上篇讲过,给byResource配置一个QPS流控规则,观察流控状态。

额外的问题:
此时关闭问服务8401看看,Sentinel控制台,流控规则消失了?????

2. 按URL限流

通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息。
改造RateLimitController,添加测试接口,注意这时SentinelResource没有写兜底方法。

@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl()
{
    return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
}

配置规则,按照资源路径进行配置(GetMapping的值)
在这里插入图片描述
这时可以发现,不会走兜底方法,细心的小伙伴可能会说没写兜底方法,可以自己手动添加上试下,使用URL限流是不会走兜底方法的,只会使用Sentinel自带默认的限流处理信息。

3. 客户自定义限流

上面我们的操作存在的问题:

  • 系统默认的,没有体现我们自己的业务要求。
  • 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
  • 每个业务方法都添加一个兜底的,那代码膨胀加剧。
  • 全局统一的处理方法没有体现。

我们使用客户自定义限流处理逻辑可以解决以上说的这些问题。
编写controller接口

    @GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(value = "customerBlockHandler",
            blockHandlerClass = CustomerBlockHandler.class,
            blockHandler = "handlerException2")
    public CommonResult customerBlockHandler()
    {
        return new CommonResult(200,"按客戶自定义",new Payment(2020L,"serial003"));
    }

新建CustomerBlockHandler,注意:方法要是static的,sentinel会通过类名.方法名的方式调用。
在这里插入图片描述

package com.atguigu.springcloud.alibaba.myhandler;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.atguigu.springcloud.entities.CommonResult;

/**
 * @Author: Daisen.Z
 * @Date: 2022/1/21 11:47
 * @Version: 1.0
 * @Description:
 */
public class CustomerBlockHandler {

    public static CommonResult handlerException(BlockException exception)
    {
        return new CommonResult(4444,"按客戶自定义,global handlerException----1");
    }
    public static CommonResult handlerException2(BlockException exception)
    {
        return new CommonResult(4444,"按客戶自定义,global handlerException----2");
    }

}

我们这样编写完成后,就相当于编写的customerBlockHandler中,指定CustomerBlockHandler类的handlerException2方法作为兜底方法。
测试一下,先访问下接口

http://localhost:8401/rateLimit/customerBlockHandler

然后添加下规则
在这里插入图片描述
再次测试下访问接口,多刷新几次,发现已经会走我们的兜底方法。
这里千万注意:使用资源路径方式进行的配置,不会走兜底方法!!!!!!

二、Sentinel整合ribbon+fallback

1. 场景介绍

我们使用以下场景来学习Sentinel和Ribbon的整合,构建消费者84去伏鳟均衡的调用9003和9004。
在这里插入图片描述

2. 构建服务提供者9003

在这里插入图片描述
模块名

cloudalibaba-provider-payment9003

基础包

com.atguigu.springcloud.alibaba

pom.xml依赖

    <dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

application.yml

server:
  port: 9003

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址

management:
  endpoints:
    web:
      exposure:
        include: '*'

主启动类PaymentMain9003

package com.atguigu.springcloud.alibaba;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @auther zzyy
 * @create 2020-02-25 16:10
 */
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003
{
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain9003.class, args);
    }
}

业务类PaymentController

package com.atguigu.springcloud.alibaba.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

/**
 * @auther zzyy
 * @create 2020-02-25 16:11
 */
@RestController
public class PaymentController
{
    @Value("${server.port}")
    private String serverPort;
    public static HashMap<Long,Payment> hashMap = new HashMap<>();
    static
    {
        hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
        hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182"));
        hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
    }
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
    {
        // 这里只有三个记录,当id传成4或其他的不是123的时payment会是null,后续其他微服务直接调用会出现空指针
        Payment payment = hashMap.get(id);
        CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort:  "+serverPort,payment);
        return result;
    }
}

3. 构建服务提供者9004

除端口号外,其余和9003工程一样。

4. 构建服务消费者84

在这里插入图片描述
模块名

cloudalibaba-consumer-nacos-order84

pom.xml依赖

 <dependencies>
        <!--SpringCloud openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

基础包

com.atguigu.springcloud.alibaba

配置文件application.yml

server:
  port: 84


spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719

#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider

启动类OrderNacosMain84

package com.atguigu.springcloud.alibaba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @auther zzyy
 * @create 2020-02-25 16:04
 */
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderNacosMain84
{
    public static void main(String[] args) {
        SpringApplication.run(OrderNacosMain84.class, args);
    }
}

配置类ApplicationContextConfig

package com.atguigu.springcloud.alibaba.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @auther zzyy
 * @create 2020-02-25 16:06
 */
@Configuration
public class ApplicationContextConfig
{
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}

业务类CircleBreakerController

package com.atguigu.springcloud.alibaba.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

/**
 * @auther zzyy
 * @create 2020-02-25 16:05
 */
@RestController
@Slf4j
public class CircleBreakerController
{
    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback") //没有配置
    public CommonResult<Payment> fallback(@PathVariable Long id)
    {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);

        if (id == 4) {
            throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return result;
    }
}

5. 测试一下环境

启动Sentinel、Nacos、9003、9004及84服务模块
访问84接口,重复刷新,看是否能够轮询的去调用9003和9004模块

http://localhost:84/consumer/fallback/1

在这里插入图片描述
Sentinel也已经监控成功
在这里插入图片描述
当我们输入参数4会报非法参数异常

http://localhost:84/consumer/fallback/4

在这里插入图片描述
当我们输入非1234的其他参数会报空指针异常

http://localhost:84/consumer/fallback/5

在这里插入图片描述
注意,这时@SentinelResource没有配置任何兜底方法,错误会直接返回到前端,这样不太友好。。。

6. @SentinelResource的fallback

fallback属性配置的是出现异常时的兜底方法。
我们给84的接口添加上该注解
在这里插入图片描述

@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常,表示出现异常时会找handlerFallback方法

添加上handlerFallback方法

//本例是fallback
public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
    Payment payment = new Payment(id,"null");
    return new CommonResult<>(444,"兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
}

重启84服务,我们再次访问接口

http://localhost:84/consumer/fallback/5

这时页面友好多了,没有把错误直接抛给浏览器,而是走的我们的异常兜底方法。
在这里插入图片描述

7. @SentinelResource的blockHandler

接下来,我们再添加上blockHandler

@SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
//本例是blockHandler
public CommonResult blockHandler(@PathVariable  Long id,BlockException blockException) {
    Payment payment = new Payment(id,"null");
    return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);
}

重启84服务,访问接口并在Sentinel页面上配置上限流规则

http://localhost:84/consumer/fallback/1

在这里插入图片描述
好,加好之后我们再测试一下访问接口,狂刷一把让流控规则生效。

http://localhost:84/consumer/fallback/1

在这里插入图片描述
已经走了限流,并且使用的是我们刚才添加的兜底方法。
接下来我们再做一个测试,访问报错的接口,在fallback(异常兜底方法)和blockHandler(Sentinel规则兜底方法)同时存在的情况下,出现异常和命中限流时会怎样呢?

http://localhost:84/consumer/fallback/4

缓慢点击时会走fallback异常兜底方法
在这里插入图片描述
快速刷新,命中Sentinel限流规则后会走blockHandler降级兜底方法。
在这里插入图片描述
这就说明了,fallback(异常兜底方法)和blockHandler(Sentinel规则兜底方法)同时存在的情况下,各管各的,互不影响,且Sentinel规则的优先级高于异常解析。

8. @SentinelResource的忽略属性exceptionsToIgnore

给注解添加上属性,并重启84服务

@SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",
        exceptionsToIgnore = {IllegalArgumentException.class})

exceptionsToIgnore 意思就是当出现指定的错误类型(例外)时,就不走兜底方法,让错误该往下传往下传,到页面就到页面,通常我们在开发、测试时需要用的该注解,用于调试、处理异常情况。
我们测试下分别访问参数4(会报IllegalArgumentException异常和参数5),看下结果。

http://localhost:84/consumer/fallback/4

参数4时没有走兜底方法,直接报错了。。。
在这里插入图片描述

http://localhost:84/consumer/fallback/5

参数是5时,仍然走兜底方法。
在这里插入图片描述

三、Sentinel整合ribbon+openFeign+fallback

1. 添加OpenFeign调用

添加pom依赖
在这里插入图片描述

<!--SpringCloud openfeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

修改84工程application.yml增加对openFeign支持
在这里插入图片描述

feign:
  sentinel:
    enabled: true

给主启动类添加开启Feign的注解
在这里插入图片描述

@EnableFeignClients

好了,接下来编写Service,@FeignClient注解的意思是去注册中心找nacos-payment-provider服务,然后fallback 表示的是服务出错时调用哪个类来兜底。
在这里插入图片描述

package com.atguigu.springcloud.alibaba.service;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Author: Daisen.Z
 * @Date: 2022/1/21 16:56
 * @Version: 1.0
 * @Description:
 */
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {

// 表示找/paymentSQL/{id}接口
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);

}

PaymentFallbackService

package com.atguigu.springcloud.alibaba.service;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.stereotype.Component;

/**
 * @auther zzyy
 * @create 2020-02-25 18:30
 */
@Component
public class PaymentFallbackService implements PaymentService
{
    @Override
    public CommonResult<Payment> paymentSQL(Long id)
    {
        return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
    }
}

给Controller添加使用OpenFeign的方式调用

//==================OpenFeign
@Resource
private PaymentService paymentService;

@GetMapping(value = "/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
{
    return paymentService.paymentSQL(id);
}

2. 测试

接下来启动测试,我们只启动9003和84模块,一会方便测试出现调用失败的情况。
调用使用OpenFeign的接口。

http://localhost:84/consumer/paymentSQL/1

哎,完美~
在这里插入图片描述
接下来我们把9003手动停掉,测试下调用不通(报错)的情况。
也能够成功进行降级,完美!!
在这里插入图片描述

四、Sentinel规则持久化

1. 规则持久化介绍

在前面的测试中我们发现,一旦重启,sentinel配好的规则就会消失了,生产环境需要将配置规则进行持久化。
我们使用以下步骤进行演示:
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效。

2.开始玩耍

修改8401工程,让规则持久化进Nacos。
先给pom添加依赖。

<!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

在这里插入图片描述
给sentinel客户端添加nacos持久化数据源
在这里插入图片描述

server:
  port: 8401
spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
#       默认端口8719,假如被占用会自动+1直到找到可用端口
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

management:
  endpoints:
    web:
      exposure:
        include: '*'

在这里插入图片描述
登录Nacos,在配置管理里面,新建配置文件,dataid为配置文件中的dataId,group为配置中的groupId,配置格式配置文件中的data-type,配置内容下面给出详解。
在这里插入图片描述

[{
    "resource": "/rateLimit/byUrl",
    "limitApp": "default",
    "grade": 1,
    "count": 1,
    "strategy": 0,
    "controlBehavior": 0,
    "clusterMode": false
}]
 
 
#resource: 资源名称;
#limitApp:来源应用; 
#grade:阈值类型, 0表示线程数, 1表示QPS;
#count:单机阈值;
#strategy:流控模式,0表示直接,1表示关联, 2表示链路;
#controlBehavior:流控效果,0表示快速失败, 1表示Warm Up, 2表示排队等待;
#clusterMode:是否集群。

重新启动Sentinel和8401工程,狂刷接口,发现已经呗限制了(因为我们使用的是接口的方式,所以使用的是Sentinel默认的兜底方法),然后访问Sentinel页面

http://localhost:8401/rateLimit/byUrl

访问Sentinel页面,发现规则也自动加载了。

localhost:8080

在这里插入图片描述

棒棒的,以后这个功能可能还会继续完善,或者我们可以在网上找一些方法修改源码实现自动化的同步。


五、总结

通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息。
@SentinelResource注解的fallback(异常兜底方法)和blockHandler(Sentinel规则兜底方法)同时存在的情况下,各管各的,互不影响,且Sentinel规则的优先级高于异常解析。
@SentinelResource注解的exceptionsToIgnore可以指定某些类型的异常(多个)不走fallback兜底的异常降级方法。
Sentinel可以进行持久化配置,在nacos中通过json文件配置即可。

举报

相关推荐

0 条评论