SpringCloud【Sentinel】

发布于:2025-07-29 ⋅ 阅读:(17) ⋅ 点赞:(0)

1,工作原理

2,常见规则

流量控制(FlowRule)

1,阈值类型设置如下

单机均摊:每个机器均摊,比如阈值填5,三个机器,就个机器都可以有5个

总体阈值:所有机器总阈值,比如阈值填5,三个机器,总共进5个请求

2,流控模式设置如下

只有流控效果是快速失败才支持调整流控模式 

直接:默认,限制请求直接对资源进行限制

关联:数据库读写,当写比较大时,限制读的限流,达到优先写

关联测试案例

先添加俩方法,一个读一个写,在orderControoler类中

  @GetMapping("/readDb")
    public String readDb(){
        return "readDb.....";
    }

    @GetMapping("/writeDb")
    public String writeDb(){
        return "writeDb.....";
    }

然后调用俩接口,在dashborard中查看俩资源,进行如下设置,即在读的资源下设置限流,流控模式为关联,关联资源为writeDb,则可实现写量过大时,访问读会被限流(设置好之后,测试先访问readDb,正常访问也没被限流,但再访问writeDb的时候,疯狂刷新,再回去访问readDb则readDb就会被限流)

链路: 根据不同的调用链,来控制不同调用链

链路测试案例

添加一个seckill方法,同createOrder,在orderController类中

   @GetMapping("/create")
    public Order createOrder(@RequestParam("userId") Long userId, @RequestParam("productId") Long productId){
        Order order = orderService.createOrder(userId,productId);
        return order;
    }

    @GetMapping("/seckill")
    public Order seckill(@RequestParam("userId") Long userId, @RequestParam("productId") Long productId){
        Order order = orderService.createOrder(userId,productId);
        order.setId(999l);
        return order;
    }

yml配置文件添加配置,取消统一上下文

spring:
  cloud:
    sentinel:
      web-context-unify: false      #是否统一web上下文(默认true)

配置链路规则:需要对createOrder限流,则如下设置,设置完之后测试,分别测试create接口和seckill接口,则会发现create接口随便刷新无误,seckill接口就有了一秒一个的限制

3,流控效果设置如下

快速失败:(默认)如果没有超阈值,就交给业务处理,如果超过了,多余的请求就会抛出blockHandler异常(比如每秒一个,哪么这一秒其他多的请求会被直接丢弃)

Warm Up:leng'q如果预见超高峰流量即将到达,设置参数参数QPS(每秒通过几个请求),period(冷启动周期),流程比如QPS=10,period=3,请求是一个稳步上升的趋势,而不是突然增加(每秒多余的请求会被丢弃)

排队等待:匀速排队,比如QPS=2,则每秒会通过2个请求,但其他请求不会被丢弃,而是在后面排队,等前面的请求,排队也有最大等待时间,timeout=100,超过最大等待时间也会被丢弃,如下每秒2个请求

熔断降级(DegradeRule)

思想:如下图的复杂调用关系,A->G->D,B->F->D,如果此时D不稳定,一直不返回结果,那么如果G一直去等D的结果,D也会卡住,同样A也会卡住,此时我们就需要及时切断这种不稳定的调用,当G感知到D调用很慢之后,后面就采取措施不调用或者快速返回,切断跟D的联系,快速返回错误,整个链路才会快速结束,请求不积压,则不会产生服务雪崩问题

此中间就有一个断路器的概念,当调用的B是稳定通畅的,则断路器是关闭的,一旦B出现了问题,则A就会打开,怎么知道什么时候该打开呢,此时就会有一个半开状态,就是稍微开一下,测一下当前状态如果调用不慢了,则闭合,如果调用仍然慢,则打开

断路器工作原理如下:

首先默认断路器是关闭的,调用是通的,此时我们设置慢调用比例(熔断策略:满调用比例/异常比例/异常数),比如设置0.7,就是请求70%都是慢请求(慢调用设置时长阈值)的话,就打开断路器,调用就会失败,但也不能一直打开,此时就会有一个熔断时长,比如30s,这30s以内的所有请求不调用,直接返回失败,但30s之后熔断窗口就结束,结束后断路器就会变成半开状态,A需要调用B就会放一个请求过去探测一下,调用成功,则B又可靠了,断路器闭合,如果还是调用失败或者慢,则断路器还是打开

熔断策略测试案例

慢调用比例

首先运行Order和Product服务,然后给CreateOrder设置熔断规则如下

修改一下商品服务,加个睡眠时长2秒,达到慢请求要求

在productController中的方法里加入睡眠时长

 @GetMapping("/getProductById/{productId}")
    public Product getProductById(@PathVariable("productId") Long productId, HttpServletRequest request) {
        String header = request.getHeader("X-Token");
        System.out.println("product==="+header);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        Product product = productService.getProductById(productId);
        return product;
    }
}

然后测试,调用createOrder接口, 先5秒内刷新超过5个请求吧,开始的请求会比较慢,5秒后就会很快,应为断路器打开了,就不再调用了,后台也不再打印,order服务就会返回兜底回调,30秒之后,在测试刷新接口,会有一个慢请求之后,又会变快,因为半开测试之后,断路器仍旧打开了,选择不调用。去掉getProductById里面的睡眠,在重启商品服务,熔断规则还在,然后再测试接口调用,则正常调用,因为不存在慢调用问题

异常比例

同上构造异常调用

修改一下商品服务,加一个异常

在productController中的方法里加入异常代码

 @GetMapping("/getProductById/{productId}")
    public Product getProductById(@PathVariable("productId") Long productId, HttpServletRequest request) {
        String header = request.getHeader("X-Token");
        System.out.println("product==="+header);
//        try {
//            Thread.sleep(2000);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }
        int a=10/0;
        Product product = productService.getProductById(productId);
        return product;
    }

然后同上测试方法进行测试看效果

当没有加熔断的时候通过后天打印可以看出,每次A调用B 之后,都会返回异常,但是每次还是会调用,加了熔断规则之后,达到规则之后,30s内就不会再调用,就直接执行兜底回调,不用再走一遍失败的路

异常数

测试也同上面一样

系统保护(SystemRule)

来源访问控制(AuthorityRule)

热点参数(ParamFlowRule)

类似流控规则,只是更细话到参数上面的限制

测试案例如下

场景1:每个用户秒杀QPS不得超过1(秒杀下单userId级别)

我们可以先写一个资源,在orderController中

   //添加上sentinel资源注解
    @GetMapping("/seckill")
    @SentinelResource(value = "seckill-order",blockHandler = "seckillFallback")
    public Order seckill(@RequestParam(value = "userId",required = false) Long userId, @RequestParam(value = "productId",required = false) Long productId){
        Order order = orderService.createOrder(userId,productId);
        order.setId(999l);
        return order;
    }

    public Order seckillFallback(Long userId, Long productId, BlockException e){
        Order order = new Order();
        order.setId(-1l);
        order.setTotalAmount(BigDecimal.ZERO);
        order.setAddress("异常信息-----");
        return order;
    }

启动订单服务,设置热点参数规则 

有参数userId,快速刷新效果,会限制1秒一个

去掉userId,快速刷新效果,不会限制

场景2:6号是vip用户,需要放行,不限制QPS

场景3:666号商品已经下架,不可访问

此时需要对另外一个参数进行限流,则需要再加一个限流规则

3,基础场景

1,下载sentinel-dashboard控制台

https://github.com/alibaba/Sentinel/releases/tag/1.8.8

Sentinel-Dashboard 是 阿里巴巴 开源的 流量控制组件 Sentinel的 可视化控制台 ,主要用于 微服务架构 的流量治理,支持实时监控、动态配置规则、熔断降级等功能

下载好之后,在文件夹中cmd,开启命令模式

然后输入java -jar sentinel-dashboard-1.8.8.jar启动

启动之后,浏览器输入http://localhost:8080/   即可访问,账密sentinel sentinel

2,添加配置

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080   #dashboard控制台
      eager: true                   #提前加载(本来是访问了请求才会加载,此时项目启动就会连接加载)

3,启动服务之后,查看控制台

4,在OrderServiceImpl得createOrder方法上添加注解@SentinelResource(value = "createOrder")

   @SentinelResource(value = "createOrder")
    @Override
    public Order createOrder(Long userId, Long productId) {
//        Product product = getProductByRemoteWithLoadBalancerByAnnotatin(productId);
        Product product = productFeignClient.getProductById(productId);
        Order order = new Order();
        order.setId(1l);
        //todo  远程调用
        order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
        order.setUserId(userId);
        order.setNickName("爱学习");
        order.setAddress("北京");
        //todo  远程调用
        order.setProductList(Arrays.asList(product));
        return order;
    }

5,重新启动订单服务,然后调用create接口,查看控制台 

6,测试规则---流控

7,然后调用createorder接口,一秒一刷则无妨,但一秒超过一次请求就会报错

4,异常处理

如上会默认返回一个流控错误字符串,我们需要返回json数据,返回错误消息和数据则就需要异常处理机制,常见得集中异常和处理方式如下:

1,Web接口

-----实现:AbstractSentinelInterceptor

异常处理,我们自己写个异常处理类,实现BlockExceptionHandler,添加到容器

新增类MyBlockExceptionHandler 

package org.example.order.exception;

import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.common.R;
import org.springframework.stereotype.Component;

import java.io.PrintWriter;

@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {

    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       String resourceName, BlockException e) throws Exception {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();

        R error = R.error(500, resourceName + "被Sentinel限制了" + e.getClass());

        //写出json
        String s = objectMapper.writeValueAsString(error);
        writer.write(s);
        writer.flush();
        writer.close();
    }
}

新增一个异常已处理对象R

在公共model模块添加对象

package org.example.common;

import lombok.Data;

@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(Object data, String msg){
        R r = new R();
        r.setCode(200);
        r.setData(data);
        r.setMsg(msg);
        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;
    }


}

然后重新启动,在dashboard中加入流控规则,调用接口,快速刷新即可看到以下报错

2,@SentinelResource

------实现:SentinelResourceAspect

@SentinelResource一般标注在非controller层,一旦违反规则,如果业务规定有回调数据,那就用blockHandle去指定兜底回调,如果没有指定回调,就让异常抛给全局(springboot全局异常处理器),此注解得处理(SentinelResourceAspect)

回调处理

1,在方法上加上注解

 @SentinelResource(value = "createOrder",blockHandler = "createOrderFallback")

2,添加回调方法

加上回调得方法,方法名通上面得注解里面blockHandler,其他内容需通上方注解方法,public、返回、参数

OrderServiceImpl

    //增加回调createOrderFallback
    @SentinelResource(value = "createOrder",blockHandler = "createOrderFallback")
    @Override
    public Order createOrder(Long userId, Long productId) {
//        Product product = getProductByRemoteWithLoadBalancerByAnnotatin(productId);
        Product product = productFeignClient.getProductById(productId);
        Order order = new Order();
        order.setId(1l);
        //todo  远程调用
        order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
        order.setUserId(userId);
        order.setNickName("爱学习");
        order.setAddress("北京");
        //todo  远程调用
        order.setProductList(Arrays.asList(product));
        return order;
    }


//同一个类中,加上回调得方法,方法名通上面得注解里面blockHandler,其他内容需通上方注解方法,public、返回、参数
   public Order createOrderFallback(Long userId, Long productId, BlockException e) {
        Order order = new Order();
        order.setId(0l);
        order.setTotalAmount(BigDecimal.ZERO);
        order.setUserId(userId);
        order.setNickName("未知用户");
        order.setAddress("异常信息:"+e.getClass());
        return order;
    }

添加流控规则

测试快速刷新的异常处理结果

3,OpenFeign调用

-----实现:SentinelFeignAutoConfiguration

在openFeign笔记中提到过兜底回调,此处即用

首先编写一个兜底返回org.example.order.feign.fallback.ProductFeignClientFallback 

实现ProductFeignClient 接口

package org.example.order.feign.fallback;
import java.math.BigDecimal;
 
import org.example.order.feign.ProductFeignClient;
import org.example.producet.domain.Product;
import org.springframework.stereotype.Component;
 
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
 
    @Override
    public Product getProductById(Long id) {
        Product product = new Product();
        product.setId(id);
        product.setPrice(new BigDecimal("0"));
        product.setProducetName("兜底回调的数据");
        product.setNum(0);
        return product;
    }
}

在ProductFeignClient 接口中添加fallback = ProductFeignClientFallback.class,这时,在远程调用成功则不管,如果调用失败,则会调用fallback,返回ProductFeignClientFallback里面编写的默认数据

package org.example.order.feign;
 
 
import org.example.order.feign.fallback.ProductFeignClientFallback;
import org.example.producet.domain.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
 
//需要调用的服务的名字
@FeignClient(value = "services-product",fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
 
    @GetMapping("/getProductById/{id}")
    Product getProductById(@PathVariable("id") Long id);
 
}

添加流控规则

测试异常回调处理结果

同样,如果此处没有些回调函数,就会抛给springboot全局异常处理

4,Sphu硬编码 

以上三种异常处理的源码都会有的编码

SphU.entry(resourceName);

我们也可自定义比如 

try {
            Entry hahah = SphU.entry("hahah");
            order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
            order.setUserId(userId);
            order.setNickName("爱学习");
            order.setAddress("北京");
        } catch (BlockException e) {
            throw new RuntimeException(e);
        }


网站公告

今日签到

点亮在社区的每一天
去签到