当前位置: 首页 > news >正文

SpringCloud【Sentinel】

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")@Overridepublic 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();@Overridepublic 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());//写出jsonString 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")@Overridepublic 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 {@Overridepublic 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);}

http://www.lryc.cn/news/598224.html

相关文章:

  • 【C++】类和对象(1)
  • CDH yarn 重启后RM两个备
  • Compose 适配 - 键鼠模式
  • 图像认知与OpenCV——图像预处理2
  • 到底可不可以用jion?jion如何优化?
  • 【学习】数字化车间与智能工厂如何推进制造业转型
  • MIT线性代数02_矩阵消元
  • 云祺容灾备份系统AWS S3对象存储备份与恢复实操手册
  • 电商项目_秒杀_架构升级
  • 4G手机控车模块的核心功能与应用价值
  • 告别束缚:这款“隐形心电监测仪”让心脏健康管理更自由
  • Oracle 时间处理函数和操作符笔记
  • Python-初学openCV——图像预处理(二)
  • 服务器带宽具体是指什么意思?
  • 硅基计划3.0 学习总结 贰 顺序表与链表
  • 图论:搜索问题
  • 深度分析Android多线程编程
  • Leetcode力扣解题记录--第2题(加法模拟)
  • ESP32S3 Ubuntu vscode如何使用USB-JTAG调试
  • 【开源】WPF的数据可视化大屏解决方案——WpfMap
  • C++课设实践项目:C++构建的学籍管理系统
  • Cisco 主模式配置
  • SGLang + 分布式推理部署DeepSeek671B满血版
  • JavaSE:开发环境的搭建(Eclipse)
  • Java与NLP实战:文本处理到情感分析全解析
  • 【ECharts✨】解决Vue 中 v-show 导致组件 ECharts 样式异常问题
  • [AI 生成] Flink 面试题
  • 【论文阅读】REVISITING DEEP AUDIO-TEXT RETRIEVAL THROUGH THE LENS OF TRANSPORTATION
  • 基于SpringBoot+Uniapp的健身饮食小程序(协同过滤算法、地图组件)
  • 人形机器人加快先进AI机器人开发