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

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中

        <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>

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

 

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

在order的yml中配置如下:

spring:cloud:openfeign:client:config:default:logger-level: fullconnect-timeout: 3000read-timeout: 5000service-product:logger-level: fullconnect-timeout: 3000read-timeout: 5000sentinel:transport:dashboard: localhost:8080eager: true
feign:sentinel:enabled: true

 

在product的properties中配置如下:

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=falsespring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.eager=true

 

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

 

  3.基础场景

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

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

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {@AutowiredDiscoveryClient discoveryClient;@AutowiredRestTemplate restTemplate;@AutowiredLoadBalancerClient loadBalancerClient;@AutowiredProductFeignClient productFeignClient;@SentinelResource(value="createOrder")@Overridepublic Order createOrder(Long productId, Long userId) {//Product product = getProductFromRemoteWithLoadBalanceAnnotation(productId);//使用openfeignProduct 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/4String 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/4String 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 类。代码如下:

@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  回车

代码如下:

 

@Component
public class MyblockExceptionHandler implements BlockExceptionHandler {private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic 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

得到如下报错。

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

 添加注解:

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

 

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

 

    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硬编码

        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方法。


@RestController
public class OrderController {
/*    @Value("${order.timeout}")private String timeOut;@Value("${order.auto-confirm}")private String aotuConfirm;*/@AutowiredOrderService orderService;@AutowiredOrderProperties 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文件配置下上下文不统一:

spring:cloud:openfeign:client:config:default:logger-level: fullconnect-timeout: 3000read-timeout: 5000service-product:logger-level: fullconnect-timeout: 3000read-timeout: 5000sentinel:transport:dashboard: localhost:8080eager: trueweb-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.流控模式-关联

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

先删除所有规则

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

@RestController
public class OrderController {
/*    @Value("${order.timeout}")private String timeOut;@Value("${order.auto-confirm}")private String aotuConfirm;*/@AutowiredOrderService orderService;@AutowiredOrderProperties 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.流控效果-直接

先删除所有流控规则

编写代码打印日志:

@Slf4j
@RestController
public class OrderController {
/*    @Value("${order.timeout}")private String timeOut;@Value("${order.auto-confirm}")private String aotuConfirm;*/@AutowiredOrderService orderService;@AutowiredOrderProperties 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:

@Component
public class MyblockExceptionHandler implements BlockExceptionHandler {private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic 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,达到每次请求都会是慢调用的条件

@RestController
public class ProductController {@AutowiredProductService 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给关闭下:

@RestController
public class ProductController {@AutowiredProductService 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;

@RestController
public class ProductController {@AutowiredProductService 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;
}

代码如下:

  

@Slf4j
@RestController
public class OrderController {
/*    @Value("${order.timeout}")private String timeOut;@Value("${order.auto-confirm}")private String aotuConfirm;*/@AutowiredOrderService orderService;@AutowiredOrderProperties 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;
@RestController
public class ProductController {@AutowiredProductService 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 

@Slf4j
@RestController
public class OrderController {
/*    @Value("${order.timeout}")private String timeOut;@Value("${order.auto-confirm}")private String aotuConfirm;*/@AutowiredOrderService orderService;@AutowiredOrderProperties 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配置规则后,如果重启服务,配置的规则会消失。如何持久化,这个需要再找资料完善下。 

相关文章:

  • Linux驱动学习day3
  • 动手学深度学习pytorch(第一版)学习笔记汇总
  • 6.8 note
  • el-input,金额千分符自动转换
  • window下配置ssh免密登录服务器
  • RushDB开源程序 是现代应用程序和 AI 的即时数据库。建立在 Neo4j 之上
  • 【网站建设】不同类型网站如何选择服务器?建站项目实战总结
  • 【MySQL系列】MySQL 执行 SQL 文件
  • GeoBoundaries下载行政区划边界数据(提供中国资源shapefile)
  • Linux:守护进程(进程组、会话和守护进程)
  • Ubuntu系统多网卡多相机IP设置方法
  • Prompt工程学习之思维树(TOT)
  • Prompt Tuning(提示调优)到底训练优化的什么部位
  • 在React 中安装和配置 shadcn/ui
  • Windmill:开源开发者基础设施的革命者
  • Prompt工程学习之自我一致性
  • 双指针详解
  • 《第五人格》暑期活动前瞻爆料:39赛季精华、限定时装返场、新玩法攻略
  • JavaScript 数组学习总结
  • 获取wordpress某个栏目的内容数量
  • 广州安全教育平台登录账号/优化营商环境
  • 电子商务网站规划/江门seo
  • 农业网站建设/线上销售渠道有哪些
  • 椒江网站建设578做网站/seow
  • 成品网站管理系统源码/照片查询百度图片搜索
  • 做商城网站在哪里注册营业执照/免费软文推广平台