SpringCloud学习笔记-4

声明:笔记来源于网络,如有侵权联系删除

Sentinel 服务保护(限流、熔断降级)

Gateway网关

Seata

1.Sentinel

Spring Cloud Alibaba Sentinel,有多种保护规则,这些规则可以动态配置在Nacos、ZooKeeper、Apollo等系统上,同时这个功能支持SpringCloud,Dubbo,gRPC,ServiceMesh等框架

如上图,每一个应用中都有很多资源(比如接口就是资源),当App1引入Sentinel Client客户端时,就可以在Sentinel Dashboard中配置这个资源对应的规则,后面这个规则就会对这个资源起作用。规则可以在Nacos等地方配置。

资源:

1.默认所有Web接口都是资源。

2.使用SphU API

3.使用@SentinelResource

规则:

1.流量控制(FlowRule) 控制访问流量的大小等(限流)

2.熔断降级(DegradeRule) 防止服务雪崩,比如前面的配置兜底数据返回

3.系统保护(SystemRule)根据系统的cpu负载,内存使用率进行保护

4.来源访问控制(AuthorityRule)控制访问权限

5.热点参数(ParamFlowRule)定义常用数据

工作原理:

当用户访问资源时,如果配置了规则,就会引发Sentinel检查,如果没有违反,就放行。如果违反了,就抛出异常,如果有兜底,就走兜底的fallback逻辑,没有的话就是默认错误。

1)sentinel整合基础场景

1.下载并启动sentinel控制台

说明:没找到jar包直接下载的地址,以下方法供参考

下载地址:GitCode - 全球开发者的开源社区,开源代码托管平台

下载后,解压缩到本地

然后用idea打开项目

在控制台(Terminal),输入命令打包: mvn clean package '-Dmaven.test.skip=true'

打包成功后,进入项目的target文件夹,如下图可以看到已生成jar包

在当前路径,输入cmd,回车,打开

输入: java -jar sentinel-dashboard.jar

启动成功,在8080端口

浏览器输入:http://localhost:8080/

打开登陆页面,用户名和密码都输入sentinel

登录成功如下:

2.配置依赖

上一次我们把sentinel依赖引入了order服务,由于每个服务都需要这个依赖,因此我们把这个依赖删除,添加到order和product的父项目services中

java 复制代码
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

粘贴到services的pom中,右上角刷新

引入依赖成功后,我们在微服务yml中配置下:

在order的yml中配置如下:

java 复制代码
spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            logger-level: full
            connect-timeout: 3000
            read-timeout: 5000
          service-product:
            logger-level: full
            connect-timeout: 3000
            read-timeout: 5000
    sentinel:
      transport:
        dashboard: localhost:8080
      eager: true
feign:
  sentinel:
    enabled: true

在product的properties中配置如下:

java 复制代码
spring.application.name=service-product
server.port=9000
spring.cloud.nacos.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.import-check.enabled=false

spring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.eager=true

重启两个微服务,然后在页面上f5刷新下,可以看到控制台已经有了2个微服务的信息

3.基础场景

经过前面的步骤已经把控制台和微服务关联了。

我们定义一个资源:order微服务的创建订单方法:

java 复制代码
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    DiscoveryClient discoveryClient;
    @Autowired
    RestTemplate restTemplate;
    @Autowired
    LoadBalancerClient loadBalancerClient;
    @Autowired
    ProductFeignClient productFeignClient;
    @SentinelResource(value="createOrder")
    @Override
    public Order createOrder(Long productId, Long userId) {
        //Product product = getProductFromRemoteWithLoadBalanceAnnotation(productId);
        //使用openfeign
        Product product = productFeignClient.getProductById(productId);
        Order order = new Order();
        order.setId(1L);
        order.setAddress("京海");
        //金额
        order.setTotalAmount(product.getPrice().multiply(BigDecimal.valueOf(product.getNum())));
        order.setUserId(userId);
        order.setNickName("张三");
        order.setProductList(Arrays.asList(product));
        return order;
    }
    //普通的调用
    private Product getProductFromRemote(Long productId){
        //获取所有商品微服务实例所在的ip和端口号
        List<ServiceInstance> instanceList = discoveryClient.getInstances("service-product");
        //获取第一个实例
        ServiceInstance instance = instanceList.get(0);
        //拼接远程调用地址 http://localhost:9000/product/4
        String url = "http://"+instance.getHost()+":"+instance.getPort()+"/product/"+productId;
        //打印日志
        log.info("请求地址{}",url);
        //给远程发送请求
        Product pro = restTemplate.getForObject(url, Product.class);
        return pro;


    }
    //LoadBalancerClient实现负载均衡
    private Product getProductFromRemoteWithLoadBalancer(Long productId){

        ServiceInstance instance = loadBalancerClient.choose("service-product");
        //拼接远程调用地址 http://localhost:9000/product/4
        String url = "http://"+instance.getHost()+":"+instance.getPort()+"/product/"+productId;
        //打印日志
        log.info("请求地址{}",url);
        //给远程发送请求
        Product pro = restTemplate.getForObject(url, Product.class);
        return pro;


    }
    //注解实现负载均衡
    private Product getProductFromRemoteWithLoadBalanceAnnotation(Long productId){


        String url = "http://service-product/product/"+productId;

        //给远程发送请求 此时 restTemplate会动态替换url的值
        Product pro = restTemplate.getForObject(url, Product.class);
        return pro;


    }

}

添加注解@SentinelResource(value="createOrder")后,重启服务,然后浏览器输入:

http://localhost:8000/create?userId=5&productId=88 回车。

可以看到浏览器正常返回数据,同时sentinel控制台刷新后,可以看到刚才的调用方法已经被记录到簇点链路里面了。

上面可以看到,sentinel已经分析出这次调用,经过了/create接口,createOrder方法,还探测到了方法里面调用了product微服务http://service-product/product/{id}

下面我们配置下/create 这个接口的流控:

点击流控后打开页面如下:

把QPS的值设置为1。代表每秒的请求数量最多为1,多了就报错。点击新增按钮。

看到已经配置成功。

接下来验证:

打开页面输入http://localhost:8000/create?userId=5&productId=88

回车后,然后按f5刷新

如果是间隔1s或者更长,那么每次都返回正确的值。

如果1S内连续刷新页面,就报错了 被sentinel阻断,如下图

2)sentinel异常处理

上图中,我们看到,返回了错误信息:Blocked by Sentinel(flow limiting)。正常的话,这种信息是不标准的,我们一般是希望返回如下这种:

{

"code": 500,

"msg": "流量超限",

"data": ""

}

sentinel抛出的异常是BlockException,如上图。

打开项目,双击shift,打开搜索,输入BlockException,打开第一个

然后ctrl+h,打开blockexception的实现,如下图右侧

可以看到有5种方式。其实对应的就是下面的 5个功能

抛出异常后,会根据资源的类型,来处理异常。如上图,如果是web接口, 是用的sentinelwebinterceptor

1.web接口

在项目中双击shift,输入sentinelwebinterceptor,打开第一个

上面这个拦截器中的preHandle表示在调用接口之前执行这个方法,如果返回true,表示放行,如果返回false,就是被拦截了。打开handleBlockEception后,

如上看到这个错误信息是在这里打印的。 BlockExceptionHandler被DefaultBlockExceptionHandler继承后处理了这个业务逻辑。

下面我们写下新的处理逻辑

先定义一个返回标准json格式的类

在model模块创建一个com.atguigu.common.R 类。代码如下:

java 复制代码
@Data
public class R {
    private Integer code;
    private String msg;
    private Object data;
    public static R ok(){
        R r = new R();
        r.setCode(200);
        return r;
    }
    public static R ok(String msg,Object data){
        R r = new R();
        r.setCode(200);
        r.setMsg(msg);
        r.setData(data);
        return r;
    }
    public static R error(){
        R r = new R();
        r.setCode(500);
        return r;
    }
    public static R error(Integer code,String msg){
        R r = new R();
        r.setCode(code);
        r.setMsg(msg);
        return r;
    }
}

然后我们建一个MyblockExceptionHandler类处理逻辑

输入 exception.MyblockExceptionHandler 回车

代码如下:

java 复制代码
@Component
public class MyblockExceptionHandler implements BlockExceptionHandler {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String s, BlockException e) throws Exception {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter writer = httpServletResponse.getWriter();
        R error =  R.error(500,s+"被sentinel限制了,原因是:"+e.getClass());
        String result = objectMapper.writeValueAsString(error);

        writer.write(result);
        writer.flush();
        writer.close();
    }
}

重启微服务,这时候规则会失效,因为是保存在内存中的,需要重新配置下熔断规则

这时候,打开浏览器,输入 http://localhost:8000/create?userId=5&productId=88 回车

当频繁刷新机会触发流控规则,返回如下图:

2.@SentinelResource

上面我们配置的接口的规则,为了方便这个@SentinelResource 我们先把规则删除

上面的代码可知这是对应的方法,那么我们在控制台配置下这个方法的流控规则如下:

配置好后,我们频繁刷新http://localhost:8000/create?userId=5&productId=88

得到如下报错。

接下来我们把返回结果优化。

添加注解:

java 复制代码
 @SentinelResource(value="createOrder",blockHandler = "createOrderFallback")

创建一个跟blockHandler的值一样的方法:

java 复制代码
    public Order createOrderFallback(Long productId, Long userId, BlockException e){
        Order order  = new Order();
        order.setId(0L);
        order.setAddress("异常信息:"+e.getClass());
        order.setTotalAmount(new BigDecimal("0"));
        order.setUserId(userId);
        order.setNickName("未知用户");
        return order;
    }

重启服务,然后重新配置规则:

配置后频繁刷新页面,得到如下返回:

3.OpenFeign

openfeign调用被限流后,如果有fallback的配置,就会走这个兜底回调,如下图:

如果没有,就会抛出异常。因为这个兜底我们之前写过了,下面我们直接验证:

先删掉原来的流控规则

接下来配置feign的流控规则:

浏览器输入http://localhost:8000/create?userId=5&productId=88

频繁刷新后,得到:

可以看到这个返回跟fallback的逻辑代码是一致的:

4.SphU硬编码

java 复制代码
        try {
            SphU.entry("newRule");
            //代码逻辑A
        } catch (BlockException e) {
            //处理违背规则后的业务逻辑
        }

这个代码中国,newRule是资源名字,当代码逻辑A 的调用触发了资源对应的规则时,会抛出异常,捕获后再处理后续的业务逻辑,这是第四种方式处理异常。

3)sentinel流控规则

1.阈值类型

资源名:sentinel探测到的这个资源名

阈值类型: QPS: 每秒处理的请求数,一般都是设置QPS

并发线程数:线程的总数,涉及到需要限制线程池的线程的数量时使用

是否集群:

如上图,如果是单机均摊,设置为5,那么就是所有的实例都可以承受5,比如有3个实例,那就是一共3*5=15。

如上图,如果是 总体阈值,设置为5,那就是所有实例一共可以承受5,比如有3个实例,那就是5。

2.流控模式-直接

直接控制就是字面意思,最普通的控制

3.流控模式-链路

看上图,链路策略里面,资源B是最下端的,如果想对它配置一个规则,但是它的上游有2个地方都用到了它这个方法,通过资源A调用的资源B的话,这个规则不生效,通过资源C秒杀调用资源B的话,这个规则生效。如何配置规则呢?

如下配置:

先在sentinel控制台删除所有规则。

在order服务中配置一个秒杀的controller入口,跟创建订单入口,使用同一个后续的createOrder方法。

java 复制代码
@RestController
public class OrderController {
/*    @Value("${order.timeout}")
    private String timeOut;
    @Value("${order.auto-confirm}")
    private String aotuConfirm;*/
    @Autowired
    OrderService orderService;
    @Autowired
    OrderProperties orderProperties;
    //创建订单
    @GetMapping("/create")
    public Order createOrder(@RequestParam("userId")Long userId,@RequestParam("productId")Long productId){
        Order order = orderService.createOrder(productId,userId);
        return order;
    }
    //获取配置
    @GetMapping("/getConfig")
    public String getConfig(){
        //return "timeout: "+timeOut + "; auto-confirm" +aotuConfirm;
        return "timeout: "+orderProperties.getTimeOut() + "; auto-confirm" +orderProperties.getAutoConfirm()
                +"; dburl=" +orderProperties.getDbUrl();

    }
    //创建订单(秒杀)
    @GetMapping("/seckill")
    public Order seckill(@RequestParam("userId")Long userId,@RequestParam("productId")Long productId){

        Order order = orderService.createOrder(productId,userId);
        order.setId(Long.MAX_VALUE);
        return order;
    }

}

yml文件配置下上下文不统一:

java 复制代码
spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            logger-level: full
            connect-timeout: 3000
            read-timeout: 5000
          service-product:
            logger-level: full
            connect-timeout: 3000
            read-timeout: 5000
    sentinel:
      transport:
        dashboard: localhost:8080
      eager: true
      web-context-unify: false
feign:
  sentinel:
    enabled: true

重启服务,页面系统调用http://localhost:8000/seckill?userId=5&productId=88

刷新后,在调用http://localhost:8000/create?userId=5&productId=88

这时候在sentinel打开看到如下:

我们把第一个的流控打开,配置->高级选项->链路->入口资源填入/seckill,如下图

新增配置后,我们在页面输入 http://localhost:8000/create?userId=5&productId=88

频繁快速连续刷新后,发现每次都能返回值

但是当我们在页面输入 http://localhost:8000/seckill?userId=5&productId=88

然后频繁快速连续刷新后,发现会返回兜底值。

这就是配置了链路规则,触发时不对/create 进入的拦截,而是只对/seckill 进入的拦截

4.流控模式-关联

当读和写两个资源都有请求时,如果要保证优先写,当写的流量过大时,限制下读的请求,这就是关联策略。

先删除所有规则

然后编写读和写的请求代码如下:

java 复制代码
@RestController
public class OrderController {
/*    @Value("${order.timeout}")
    private String timeOut;
    @Value("${order.auto-confirm}")
    private String aotuConfirm;*/
    @Autowired
    OrderService orderService;
    @Autowired
    OrderProperties orderProperties;
    //创建订单
    @GetMapping("/create")
    public Order createOrder(@RequestParam("userId")Long userId,@RequestParam("productId")Long productId){
        Order order = orderService.createOrder(productId,userId);
        return order;
    }
    //获取配置
    @GetMapping("/getConfig")
    public String getConfig(){
        //return "timeout: "+timeOut + "; auto-confirm" +aotuConfirm;
        return "timeout: "+orderProperties.getTimeOut() + "; auto-confirm" +orderProperties.getAutoConfirm()
                +"; dburl=" +orderProperties.getDbUrl();

    }
    //创建订单(秒杀)
    @GetMapping("/seckill")
    public Order seckill(@RequestParam("userId")Long userId,@RequestParam("productId")Long productId){

        Order order = orderService.createOrder(productId,userId);
        order.setId(Long.MAX_VALUE);
        return order;
    }
    //读取请求
    @GetMapping("/readDb")
    public String readDb(){
        return "readDb";
    }
    //写入请求
    @GetMapping("/writeDb")
    public String writeDb(){
        return "writeDb";
    }

}

重启服务,浏览器输入http://localhost:8000/readDb

得到如下结果:

浏览器开启无痕模式再打开一个窗口,输入 http://localhost:8000/writeDb

得到如下:

两个接口都返回数据了。

下面配置关联规则

如上图,配置阈值为1,关联资源是/writeDb,这个意思就是当关联资源/writeDb的阈值超过1时,对 /readDb进行限流。

接下来我们验证下:

如上图,频繁刷新writeDb页面,让其达到QPS为1,然后迅速刷新readDb

可以看到read被限流了。

普通的规则是直接对当前资源如果超过阈值,限流。

关联规则,也是对当前资源限流,不过条件变成了关联资源超过阈值。

5.流控效果-直接

先删除所有流控规则

编写代码打印日志:

java 复制代码
@Slf4j
@RestController
public class OrderController {
/*    @Value("${order.timeout}")
    private String timeOut;
    @Value("${order.auto-confirm}")
    private String aotuConfirm;*/
    @Autowired
    OrderService orderService;
    @Autowired
    OrderProperties orderProperties;
    //创建订单
    @GetMapping("/create")
    public Order createOrder(@RequestParam("userId")Long userId,@RequestParam("productId")Long productId){
        Order order = orderService.createOrder(productId,userId);
        return order;
    }
    //获取配置
    @GetMapping("/getConfig")
    public String getConfig(){
        //return "timeout: "+timeOut + "; auto-confirm" +aotuConfirm;
        return "timeout: "+orderProperties.getTimeOut() + "; auto-confirm" +orderProperties.getAutoConfirm()
                +"; dburl=" +orderProperties.getDbUrl();

    }
    //创建订单(秒杀)
    @GetMapping("/seckill")
    public Order seckill(@RequestParam("userId")Long userId,@RequestParam("productId")Long productId){

        Order order = orderService.createOrder(productId,userId);
        order.setId(Long.MAX_VALUE);
        return order;
    }
    //读取请求
    @GetMapping("/readDb")
    public String readDb(){
        log.info("readDb...");
        
        return "readDb";
    }
    //写入请求
    @GetMapping("/writeDb")
    public String writeDb(){
        return "writeDb";
    }

}

修改下处理拦截代码返回code429:

java 复制代码
@Component
public class MyblockExceptionHandler implements BlockExceptionHandler {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String s, BlockException e) throws Exception {
        httpServletResponse.setStatus(429);
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter writer = httpServletResponse.getWriter();
        R error =  R.error(500,s+"被sentinel限制了,原因是:"+e.getClass());
        String result = objectMapper.writeValueAsString(error);

        writer.write(result);
        writer.flush();
        writer.close();
    }
}

页面输入http://localhost:8000/readDb

这时候在后台配置下

这时选的流控效果是快速失败。当频繁刷新时就会返回错误数据。先打开f5控制台,然后刷新如下

看到错误码为429.

下面我们下载安装一个apipost工具,可以网络搜索下载,然后注册登录

点击新建接口:

选择http请求

输入localhost:8000/readDb

点击发送

看到返回了数据。

点击一键压测 并发数10,按压测时长5S。表示10个用户一直请求,持续5S

选择调试->后执行操作->断言

断言内容:响应码 等于 200 表示如果响应码是200表示成功。其他是失败

点击压测按钮:

看到访问请求17415,成功数7 。统计有少许误差,每秒请求3000+的情况,基本上绝大部分失败,每秒1次的成功。

6.流控效果-Warm Up

我们修改下设置如下

warm up指的是当配置好阈值后,比如说是QPS为10,预热时长3秒,但是一开始不会允许10,先允许2,然后过1秒允许4,3秒后增加到10。这就是起到一个缓冲的作用

点击压测:

看到5s成功了39次。看控制台:

可以看到第一秒4次请求,第二秒4次,第三秒5次,第四秒7次,第五秒10次。这就做到了冷启动。

7.流控效果-匀速排队

匀速排队指的是,当超过QPS的值2时,多出来的请求,不会立即失败,而是进入一个队列进行排队,然后按照顺序进行请求。但是当队列中的请求过多,如果轮到这个请求的时候超过1000毫秒秒这个设置时,那么这个请求就会被丢弃。

压测:

可以看到每秒2次的稳定调用

说明:只有快速失败的时候 支持流控模式中的关联和链路,Warm Up和排队不支持。

4)sentinel熔断规则

熔断降级(DegradeRule)

微服务之间调用的时候,如果服务G调用服务D的时候,发现服务D不稳定,那么如果不采取措施的话,会导致请求积压,从而造成雪崩效应。为了保证客户端G自身的系统功能,它需要主动切断不稳定调用,快速返回不积压。这就需要用到断路器。当断路器是闭合时,A可以调用B服务。当A调用B发现B服务不行时,那么会打开断路器一段时间。这段时间内,A是不会调用B的,而是返回默认数据。当这个时间过去后,A会把断路器半开,像B发送一次请求,如果B通了,那么就闭合断路器,后面继续访问,如果B还是没通,那就继续打开断路器一段时间。如此往复。

1.断路器工作原理

如上图,sentinel会统计一段时间内的请求情况,拿慢调用比例来说,就是返回的结果等待时间很长的请求,占到所有请求的比例到阈值,那么就会触发断路器打开,断路器打开的状态会持续一个固定时间(熔断时长),当在熔断时长之内时,所有的请求都会拒绝访问,返回默认值,只有过了熔断时间之后,断路器才会变成半开状态,这时候会主动请求一次,如果这次访问不是慢调用,那么就会把断路器关闭,后续的请求都可以访问,但是如果这次请求还是慢调用,那么就会再次触发断路器打开,并持续固定的熔断时长,如此循环。

2.熔断策略-慢调用比例

打开页面,输入http://localhost:8000/create?userId=5&productId=88

回车后,会返回内容。

在sentinel后台配置规则:

上图中,熔断策略选择慢调用比例。

最大RT:最大的响应时间,1000ms,超过这个时间就算作慢调用

比例阈值:0.5,即慢调用占所有调用的比例,50%

熔断时长:即断路器保持打开的时间,这个时间内不可调用

最小请求数:指这个规则触发的前提是必须大于5次请求,样本数量要多才会触发

统计时长:指的是慢调用比例统计的时间跨度。即统计5S内的请求

下面我们修改下product里面的获取商品的方法,将其睡眠2S,达到每次请求都会是慢调用的条件

java 复制代码
@RestController
public class ProductController {
    @Autowired
    ProductService productService;
    //查询商品
    @GetMapping("/product/{id}")
    public Product getProduct(@PathVariable("id")Long productId, HttpServletRequest httpServletRequest){

        String header = httpServletRequest.getHeader("X-Token");
        System.out.println("hello token=【"+header+"】");
        Product product = productService.getById(productId);
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        return product;
    }
}

重启product微服务。注意这里不用重启order微服务。

打开页面输入http://localhost:8000/create?userId=5&productId=88

5秒内多次频繁刷新,可以看到一开始是正常返回的,但是后续触发了熔断,返回兜底数据

这就实现了慢调用比例熔断控制。

下面我们把product服务的睡眠2S给关闭下:

java 复制代码
@RestController
public class ProductController {
    @Autowired
    ProductService productService;
    //查询商品
    @GetMapping("/product/{id}")
    public Product getProduct(@PathVariable("id")Long productId, HttpServletRequest httpServletRequest){

        String header = httpServletRequest.getHeader("X-Token");
        System.out.println("hello token=【"+header+"】");
        Product product = productService.getById(productId);
 /*       try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }*/

        return product;
    }
}

重启product服务。然后页面输入http://localhost:8000/create?userId=5&productId=88

一直不停刷新,可以看到每次都返回正常数据,且不卡顿。

3.熔断策略-异常比例

前面我们配置慢调用比例的时候,每次调用,其实都是会返回正常数据的,只是每次返回的速度慢,触发了规则,导致进行兜底回调。也就是说即使不配置规则,流程也会通。但是为了快速响应,我们配置了规则。

如果调用feign的时候,product服务处理逻辑发生了异常,流程也会通,走feign配置的兜底回调。(稍后我们验证下这点)。但是熔断的作用就是为了快速响应,如果每次都去调product服务,它发生异常后,走兜底,这还是慢了,因此我们配置下熔断规则,符合规则时,触发熔断,在熔断时间内,不去调用product服务,而是直接走兜底,减少资源的浪费,加快响应速度。

验证product异常时的返回:

我们故意写一个运行错误 int i = 10/0;

java 复制代码
@RestController
public class ProductController {
    @Autowired
    ProductService productService;
    //查询商品
    @GetMapping("/product/{id}")
    public Product getProduct(@PathVariable("id")Long productId, HttpServletRequest httpServletRequest){

        String header = httpServletRequest.getHeader("X-Token");
        System.out.println("hello token=【"+header+"】");
        int i = 10/0;
        Product product = productService.getById(productId);
 /*       try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }*/

        return product;
    }
}

然后重启product服务

浏览器输入http://localhost:8000/create?userId=5&productId=88

回车后,发现页面返回了兜底数据,order控制台走了兜底打印逻辑,product抛出了异常。

上面验证的其实就是上图中无熔断规则的情况,product服务报错了,触发了order服务的兜底回调逻辑。但是如果每次都调用product,抛异常,兜底回调,这样耗费时间和资源,所以我们配置个熔断规则,触发后,在熔断时间内,就不用调用product服务而是直接返回兜底回调,这样系统反应更快,雪崩几率大大降低。

下面我们配置下熔断规则:

熔断策略:异常比例

比例阈值:0.8 即80%的请求抛出了异常

熔断时长:30s。即触发规则后,断路器打开的时间,此时间内访问都不通,直接走兜底

最小请求数:5 指触发规则前提至少是5次以上的调用请求

统计时长:5000ms 即5s。即5S内至少调用了5次请求且异常比例大于80%,才会触发规则。

打开页面输入http://localhost:8000/create?userId=5&productId=88

回车后,看到页面返回了兜底数据,product抛出异常,order打印了兜底逻辑

当我们5s内不停刷新大于5次时,

可以看到order服务每次都触发了兜底回调,但是由于熔断规则触发,导致order并没有大量调用product服务,而是直接自行返回兜底逻辑的。(product控制台只有最初的少量异常抛出,触发熔断后,不调用product了,所以后续product就不打印异常了)

4.熔断策略-异常数

异常数配置规则跟异常比例是差不多的,唯一区别就是统计的是次数。配置如上。

浏览器输入:http://localhost:8000/create?userId=5&productId=88

回车后,频繁刷新10次以上,触发熔断。然后清空product和order的控制台的打印日志,便于后续观察。

清空日志后,由于触发了熔断,再次刷新页面,可以看到order服务有兜底日志,而product服务没有,说明熔断生效。

5)sentinel热点规则

热点就是资源当中的参数。比如配置axb为热点,QPS为5,那么如果axb的QPS超过了5,就触发限流。其他情况,非热点,或者热点但QPS值没有超过设置,则不触发限流。

问题1:每个用户秒杀QPS不得大于1(对userId限流)

问题2:6号用户是vvip,不限制QPS

问题3:666号商品已下架,不允许访问(对productId限流)

修改order服务controller中秒杀代码,添加注解和兜底逻辑:

复制代码
@SentinelResource(value = "seckill-order",fallback = "seckillFallback")
复制代码
public Order seckillFallback(Long userId, Long productId, BlockException e){
    log.info("seckillFallback.....");
    Order order = new Order();
    order.setId(productId);
    order.setUserId(userId);
    order.setAddress("异常信息:"+e.getClass());
    return order;
}

代码如下:

java 复制代码
@Slf4j
@RestController
public class OrderController {
/*    @Value("${order.timeout}")
    private String timeOut;
    @Value("${order.auto-confirm}")
    private String aotuConfirm;*/
    @Autowired
    OrderService orderService;
    @Autowired
    OrderProperties orderProperties;
    //创建订单
    @GetMapping("/create")
    public Order createOrder(@RequestParam("userId")Long userId,@RequestParam("productId")Long productId){
        Order order = orderService.createOrder(productId,userId);
        return order;
    }
    //获取配置
    @GetMapping("/getConfig")
    public String getConfig(){
        //return "timeout: "+timeOut + "; auto-confirm" +aotuConfirm;
        return "timeout: "+orderProperties.getTimeOut() + "; auto-confirm" +orderProperties.getAutoConfirm()
                +"; dburl=" +orderProperties.getDbUrl();

    }
    //创建订单(秒杀)
    @GetMapping("/seckill")
    @SentinelResource(value = "seckill-order",fallback = "seckillFallback")
    public Order seckill(@RequestParam("userId")Long userId,@RequestParam("productId")Long productId){

        Order order = orderService.createOrder(productId,userId);
        order.setId(Long.MAX_VALUE);
        return order;
    }
    public Order seckillFallback(Long userId, Long productId, BlockException e){
        log.info("seckillFallback.....");
        Order order = new Order();
        order.setId(productId);
        order.setUserId(userId);
        order.setAddress("异常信息:"+e.getClass());
        return order;
    }
    //读取请求
    @GetMapping("/readDb")
    public String readDb(){
        log.info("readDb...");

        return "readDb";
    }
    //写入请求
    @GetMapping("/writeDb")
    public String writeDb(){
        return "writeDb";
    }

}

product微服务注释掉报错的代码:

复制代码
// int i = 10/0;
java 复制代码
@RestController
public class ProductController {
    @Autowired
    ProductService productService;
    //查询商品
    @GetMapping("/product/{id}")
    public Product getProduct(@PathVariable("id")Long productId, HttpServletRequest httpServletRequest){

        String header = httpServletRequest.getHeader("X-Token");
        System.out.println("hello token=【"+header+"】");
       // int i = 10/0;
        Product product = productService.getById(productId);
 /*       try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }*/

        return product;
    }
}

重启服务,页面输入​​​​​​​http://localhost:8000/seckill?userId=5&productId=88

正常返回数据:

下面我们添加热点规则配置:

如上图,参数索引,0代表第一个参数,1代表第二个参数,以此类推。

问题1:每个用户秒杀QPS不得大于1(对userId限流),因此,如果要对userId限流,那么看下上面的代码,userId是在接口的第一个参数,因此参数索引填0。单击阈值1,统计窗口时长1秒,这样就符合QPS不得大于1这个条件。

现在验证:

打开页面,输入http://localhost:8000/seckill?userId=5&productId=88

频繁刷新,可以看到

说明配置生效了

注意:如果接口改为userId不是必传的,那么如果传的话,会拦截,如果不传,不拦截。可以尝试下。

验证如下:传userId:拦截:

不传userId,不拦截:

问题2:6号用户是vvip,不限制QPS

编辑规则:

如上图,userId是long类型,因此参数类型选long,参数值,因为是要对6号用户放开,那么就填6,限流阈值,因为是不限制,因此把阈值填大,1000000,就表示不限制了。点击添加,确定。

把浏览器页面上userId值改为6,频繁刷新,但是不会报错,说明6用户是vvip设置成功了。

如果把userId改为不是6,比如88,那么频繁刷新,会报错。如下图:

问题3:666号商品已下架,不允许访问(对productId限流)

这个是对productId限流,是方法中第二个参数

如上图,我们配置的是userId的规则,索引是0。productId因为是第二个参数,索引应该是1。但是一个规则只能配置一个参数,因此我们单独配置productId。

如上图,因为商品是第二个参数,所以索引填1,因为商品是需要被并发很大的量购买,因此我们把阈值设置很高,1000000,统计窗口时长设置为1秒,表示支持1000000的QPS。点击新增。

新增后,点击编辑,

如上图,高级选项,因为productId是long类型,所以选择long,因为是666号商品要下架,因此填666,因为666要下架,相当于这个商品不让购买了,因为阈值填0,表示如果有666的请求就拦截。配置好后点击添加,保存

把浏览器中商品id改为666,回车,发现被拦截,热点限制生效了。

6)fallback与blockhandler兜底回调

上图中,没有走兜底回调,返回了错误页面,不友好。

上图代码中,我们用的fallback,但是兜底函数用的是BlockException,这两个不对应。我们把fallback改为blockHandler

java 复制代码
@Slf4j
@RestController
public class OrderController {
/*    @Value("${order.timeout}")
    private String timeOut;
    @Value("${order.auto-confirm}")
    private String aotuConfirm;*/
    @Autowired
    OrderService orderService;
    @Autowired
    OrderProperties orderProperties;
    //创建订单
    @GetMapping("/create")
    public Order createOrder(@RequestParam("userId")Long userId,@RequestParam("productId")Long productId){
        Order order = orderService.createOrder(productId,userId);
        return order;
    }
    //获取配置
    @GetMapping("/getConfig")
    public String getConfig(){
        //return "timeout: "+timeOut + "; auto-confirm" +aotuConfirm;
        return "timeout: "+orderProperties.getTimeOut() + "; auto-confirm" +orderProperties.getAutoConfirm()
                +"; dburl=" +orderProperties.getDbUrl();

    }
    //创建订单(秒杀)
    @GetMapping("/seckill")
    @SentinelResource(value = "seckill-order",blockHandler = "seckillFallback")
    public Order seckill(@RequestParam(value = "userId",required = false)Long userId,@RequestParam("productId")Long productId){

        Order order = orderService.createOrder(productId,userId);
        order.setId(Long.MAX_VALUE);
        return order;
    }
    public Order seckillFallback(Long userId, Long productId, BlockException e){
        log.info("seckillFallback.....");
        Order order = new Order();
        order.setId(productId);
        order.setUserId(userId);
        order.setAddress("异常信息:"+e.getClass());
        return order;
    }
    //读取请求
    @GetMapping("/readDb")
    public String readDb(){
        log.info("readDb...");

        return "readDb";
    }
    //写入请求
    @GetMapping("/writeDb")
    public String writeDb(){
        return "writeDb";
    }

}

重启服务,然后配置规则:

浏览器输入http://localhost:8000/seckill?userId=62&productId=6668

如上图,走兜底回调了。

还可以如下修改:

把注解改为fallback,把兜底函数改为Throwable。

重启服务,重新配置规则。然后页面刷新:

可以看到也走了兜底回调。

个人总结就是注解里面是fallback,那么对应的兜底函数用Throwable接收。

如果注解里面是blockHandler ,那么对应的兜底函数用BlockException接收

7)sentinel总结

系统规则和授权规则,由于不常用,而且有相关的替代,这里不多做详细了解了。

授权规则替代方案:网关、权限框架、业务流程。

系统规则替代方案:K8S

sentinel配置规则后,如果重启服务,配置的规则会消失。如何持久化,这个需要再找资料完善下。

相关推荐
Chan16几秒前
【 SpringCloud | 微服务 MQ基础 】
java·spring·spring cloud·微服务·云原生·rabbitmq
恰薯条的屑海鸥几秒前
零基础在实践中学习网络安全-皮卡丘靶场(第十五期-URL重定向模块)
学习·安全·web安全·渗透测试·网络安全学习
自小吃多21 分钟前
STC8H系列 驱动步进电机
笔记·单片机
斯普信云原生组1 小时前
Docker构建自定义的镜像
java·spring cloud·docker
moxiaoran57532 小时前
uni-app学习笔记三十--request网络请求传参
笔记·学习·uni-app
嘉陵妹妹2 小时前
深度优先算法学习
学习·算法·深度优先
乖乖是干饭王3 小时前
Linux系统编程中的_GNU_SOURCE宏
linux·运维·c语言·学习·gnu
待什么青丝4 小时前
【TMS570LC4357】之相关驱动开发学习记录2
c语言·arm开发·驱动开发·单片机·学习
行云流水剑4 小时前
【学习记录】如何使用 Python 提取 PDF 文件中的内容
python·学习·pdf
明月醉窗台5 小时前
qt使用笔记二:main.cpp详解
数据库·笔记·qt