Sentinel熔断与限流

一、服务雪崩与解决方案

1.1、服务雪崩问题

一句话:微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。

微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务。

image-20240827204943349

如图,如果服务提供者I发生了故障,当前的应用的部分业务因为依赖于服务I,因此也会被阻塞。此时,其它不依赖于服务I的业务似乎不受影响。

image-20240827205002590

但是,依赖服务I的业务请求被阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:

image-20240827205244584

服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,那么当前服务也就不可用了。

那么,依赖于当前服务的其它服务随着时间的推移,最终也都会变的不可用,形成级联失败,雪崩就发生了:

image-20240827205256638

解决雪崩问题的常见方式有四种:

1.2、超时处理

超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待

image-20240827205650618

1.3、仓壁模式

仓壁模式来源于船舱的设计:

船舱都会被隔板分离为多个独立空间,当船体破损时,只会导致部分空间进入,将故障控制在一定范围内,避免整个船体都被淹没。

于此类似,我们可以限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。

image-20240827205714458

1.4、断路器

断路器模式:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。

断路器会统计访问某个服务的请求数量,异常比例:

image-20240827205519805

当发现访问服务D的请求异常比例过高时,认为服务D有导致雪崩的风险,会拦截访问服务D的一切请求,形成熔断:

image-20240827205540092

1.5、限流

流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。

image-20240827205625160

1.6、总结

什么是雪崩问题?

  • 微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。

可以认为:

限流是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。是一种预防措施。

超时处理、线程隔离、降级熔断是在部分服务故障时,将故障控制在一定范围,避免雪崩。是一种补救措施。

解决雪崩问题的常见方式有四种:

  • 超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待

  • 舱壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。

  • 熔断降级:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。

  • 流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。

二、初识Sentinel

在SpringCloud当中支持多种服务保护技术:

早期比较流行的是Hystrix框架,但目前国内实用最广泛的还是阿里巴巴的Sentinel框架,这里我们做下对比:

SentinelHystrix
隔离策略信号量隔离线程池隔离/信号量隔离
熔断降级策略基于慢调用比例或异常比例基于失败比率
实时指标实现滑动窗口滑动窗口(基于 RxJava)
规则配置支持多种数据源支持多种数据源
扩展性多个扩展点插件的形式
基于注解的支持支持支持
限流基于 QPS,支持基于调用关系的限流有限的支持
流量整形支持慢启动、匀速排队模式不支持
系统自适应保护支持不支持
控制台开箱即用,可配置规则、查看秒级监控、机器发现等不完善
常见框架的适配Servlet、Spring Cloud、Dubbo、gRPC 等Servlet、Spring Cloud Netflix
https://sentinelguard.io/zh-cn/index.html
https://github.com/alibaba/Sentinel

Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址:https://sentinelguard.io/zh-cn/index.html

Sentinel 具有以下特征:

丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。

完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。

广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。

完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

image-20240827210841757

三、微服务整合Sentinel

3.1、改POM

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

image-20240827214826617

3.2、写YAML

server:
  port: 9001

spring:
  application:
    name: sentinelservice
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.200.129:8848 #配置Nacos地址
    
    sentinel:
      transport:
        dashboard: 192.168.200.129:8858 #配置Sentinel dashboard控制台服务地址
        port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口

image-20240827215202688

3.3、主启动

package com.sentinel.service;

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

/**
 * @Author: 史小创
 * @Time: 2024/8/27 下午9:43
 * @Description:
 */

@SpringBootApplication
@EnableDiscoveryClient
public class SentinelServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(SentinelServiceApplication.class, args);
    }
}

image-20240827215302783

3.4、测试

image-20240827215421736

image-20240827215459305

原因:Sentinel采用的为懒加载的模式。想使用Sentinel对某个接口进行限流和降级等操作,一定要先访问下接口,使Sentinel检测出相应的接口

http://localhost:9001/testA
http://localhost:9001/testB

image-20240827215846359

四、实战:流控规则

4.1、概述

image-20240827220426995

Sentinel能够对流量进行控制,主要是监控应用的QPS流量或者并发线程数等指标,如果达到指定的阈值时,就会被流量进行控制,以避免服务被瞬时的高并发流量击垮,保证服务的高可靠性。参数见最下方:

序号名称含义
1资源名资源的唯一名称,默认就是请求的接口路径,可以自行修改,但是要保证唯一。
2针对来源具体针对某个微服务进行限流,默认值为default,表示不区分来源,全部限流。
3阈值类型QPS表示通过QPS进行限流,并发线程数表示通过并发线程数限流。
4单机阈值与阈值类型组合使用。如果阈值类型选择的是QPS,表示当调用接口的QPS达到阈值时,进行限流操作。如果阈值类型选择的是并发线程数,则表示当调用接口的并发线程数达到阈值时,进行限流操作。
5是否集群选中则表示集群环境,不选中则表示非集群环境。

4.2、流控模式

image-20240827220951390

在添加限流规则时,点击高级选项,可以选择三种流控模式

  • 直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
  • 关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
  • 链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流

4.2.1、直连

默认的流控模式,当接口达到限流条件时,直接开启限流功能。

image-20240827221313582

image-20240827221330218

表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误

http://localhost:9001/testA

1

Blocked by Sentinel (flow limiting) 这样的方式貌似太丑,能否有美观一点呢

4.2.2、关联

关联模式:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流

使用场景:比如用户支付时需要修改订单状态,同时用户要查询订单。查询和修改操作会争抢数据库锁,产生竞争。业务需求是优先支付和更新订单的业务,因此当修改订单业务触发阈值时,需要对查询订单业务限流。

简单讲:当关联的资源达到阈值时,就限流自己。当与A关联的资源B达到阀值后,就限流A自己B惹事,A挂了。

image-20240827222328595

image-20240827222442555

当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名,B惹事,A挂了

http://localhost:9001/testB
http://localhost:9001/testA

2

小结:

满足下面条件可以使用关联模式:

  • 两个有竞争关系的资源
  • 一个优先级较高,一个优先级较低

4.2.3、链路

链路模式:只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。

来自不同链路的请求对同一个目标访问时,实施针对性的不同限流措施,比如C请求来访问就限流,D请求来访问就是OK

web-context-unify: false # controller层的方法对service层调用不认为是同一个根链路

image-20240827223822595

package com.sentinel.service.service;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.stereotype.Service;

/**
 * @Author: 史小创
 * @Time: 2024/8/27 下午10:43
 * @Description:
 */

@Service
public class FlowLimitService {

    @SentinelResource(value = "common")
    public void common() {
        System.out.println("------FlowLimitService come in");
    }
}

image-20240827224459534

 /**
     * 流控-链路演示demo
     * C和D两个请求都访问flowLimitService.common()方法,阈值到达后对C限流,对D不管
     */
    @Resource
    private FlowLimitService flowLimitService;

    @GetMapping("/testC")
    public String testC() {
        flowLimitService.common();
        return "------testC";
    }

    @GetMapping("/testD")
    public String testD() {
        flowLimitService.common();
        return "------testD";
    }

image-20240827224607692

image-20240827224717762

image-20240827224837210

http://localhost:9001/testC
http://localhost:9001/testD

5

4.3、流控效果

在流控的高级选项中,还有一个流控效果选项:

image-20240827225952576

流控效果是指请求达到流控阈值时应该采取的措施,包括三种:

  • 快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。

  • warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。

  • 排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长

4.3.1、快速失败

4.3.2、预热Warm up

https://github.com/alibaba/Sentinel/wiki/Flow-Control:-Warm-Up
https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8

image-20240828080826894

阈值一般是一个微服务能承担的最大QPS,但是一个服务刚刚启动时,一切资源尚未初始化(冷启动),如果直接将QPS跑到最大值,可能导致服务瞬间宕机。

warm up也叫预热模式,是应对服务冷启动的一种方案。请求阈值初始值是 maxThreshold / coldFactor,持续指定时长后,逐渐提高到maxThreshold值。而coldFactor的默认值是3.

例如,我设置QPS的maxThreshold为10,预热时间为5秒,那么初始阈值就是 10 / 3 ,也就是3,然后在5秒后逐渐增长到10.

image-20240828080911618

如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。

默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。

image-20240828081044514

https://github.com/alibaba/Sentinel/blob/1.8/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java

image-20240828081219370

默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。
案例,单机阈值为10,预热时长设置5秒。
系统初始化的阈值为10 / 3 约等于3,即单机阈值刚开始为3(我们人工设定单机阈值是10,sentinel计算后QPS判定为3开始);
然后过了5秒后阀值才慢慢升高恢复到设置的单机阈值10,也就是说5秒钟内QPS为3,过了保护期5秒后QPS为10

image-20240828081428677

image-20240828081601147

http://localhost:9001/testB

3

image-20240828082156779

4.3.3、排队等待

当请求超过QPS阈值时,快速失败和warm up 会拒绝新的请求并抛出异常。

而排队等待则是让所有请求进入一个队列中,然后按照阈值允许的时间间隔依次执行。后来的请求必须等待前面执行完成,如果请求预期的等待时间超出最大时长,则会被拒绝。

工作原理

例如:QPS = 5,意味着每200ms处理一个队列中的请求;timeout = 2000,意味着预期等待时长超过2000ms的请求会被拒绝并抛出异常。

那什么叫做预期等待时长呢?

比如现在一下子来了12 个请求,因为每200ms执行一个请求,那么:

  • 第6个请求的预期等待时长 = 200 * (6 - 1) = 1000ms
  • 第12个请求的预期等待时长 = 200 * (12-1) = 2200ms

现在,第1秒同时接收到10个请求,但第2秒只有1个请求,此时QPS的曲线这样的:

image-20240828082324502

如果使用队列模式做流控,所有进入的请求都要排队,以固定的200ms的间隔执行,QPS会变的很平滑:

image-20240828082358536

平滑的QPS曲线,对于服务器来说是更友好的。

@GetMapping("/testE")
    public String testE() {
        System.out.println(System.currentTimeMillis() + "      testE,排队等待");
        return "------testE";
    }

image-20240828082554438

http://localhost:9001/testE

image-20240828083318096

4

image-20240828083731619

4.4.4、总结

流控效果有哪些?

  • 快速失败:QPS超过阈值时,拒绝新的请求

  • warm up: QPS超过阈值时,拒绝新的请求;QPS阈值是逐渐提升的,可以避免冷启动时高并发导致服务宕机。

  • 排队等待:请求会进入队列,按照阈值允许的时间间隔依次执行请求;如果请求预期等待时长大于超时时间,直接拒绝

4.4、线程隔离(舱壁模式)

线程隔离有两种方式实现:

  • 线程池隔离

  • 信号量隔离(Sentinel默认采用)

如图:

image-20240828232857421

线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果

信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求。

两者的优缺点:

image-20240828232923471

用法说明

在添加限流规则时,可以选择两种阈值类型:

image-20240828232953989

  • QPS:就是每秒的请求数,在快速入门中已经演示过

  • 线程数:是该资源能使用用的tomcat线程数的最大值。也就是通过限制线程数量,实现线程隔离(舱壁模式)。

实操

image-20240828233245078

image-20240828233952370

Jmeter给它打满了,大部分我们自己访问都不好使,偶尔Jmeter线程切换系统判定没访问,我们自己的点击才有点机会

五、实战:熔断规则

熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。

断路器控制熔断和放行是通过状态机来完成的:

image-20240828090457975

状态机包括三个状态:

  • closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
  • open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后会进入half-open状态
  • half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
    • 请求成功:则切换到closed状态
    • 请求失败:则切换到open状态

断路器熔断策略有三种:慢调用、异常比例、异常数

https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7

image-20240828090248426

5.1、慢调用

慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。

慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。

/**
     * 新增熔断规则-慢调用比例
     *
     * @return
     */
    @GetMapping("/testF")
    public String testF() {
        // 暂停几秒钟线程
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("----测试:新增熔断规则-慢调用比例 ");
        return "------testF 新增熔断规则-慢调用比例";
    }

image-20240828091326594

image-20240828091640919

名词解释:

进入熔断状态判断依据:在统计时长内,实际请求数目>设定的最小请求数 且 实际慢调用比例>比例阈值 ,进入熔断状态。

1.调用:一个请求发送到服务器,服务器给与响应,一个响应就是一个调用。

2.最大RT:即最大的响应时间,指系统对请求作出响应的业务处理时间。

3.慢调用:处理业务逻辑的实际时间>设置的最大RT时间,这个调用叫做慢调用。

4.慢调用比例:在所以调用中,慢调用占有实际的比例=慢调用次数➗总调用次数

5.比例阈值:自己设定的 , 比例阈值=慢调用次数➗调用次数

6.统计时长:时间的判断依据

7.最小请求数:设置的调用最小请求数,上图比如1秒钟打进来10个线程(大于我们配置的5个了)调用被触发

触发条件+熔断状态:

1熔断状态(保险丝跳闸断电,不可访问):在接下来的熔断时长内请求会自动被熔断

2探测恢复状态(探路先锋):熔断时长结束后进入探测恢复状态

3结束熔断(保险丝闭合恢复,可以访问):在探测恢复状态,如果接下来的一个请求响应时间小于设置的慢调用 RT,则结束熔断,否则继续熔断。

5.2、异常比例

异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

 /**
     * 新增熔断规则-异常比例
     *
     * @return
     */
    @GetMapping("/testG")
    public String testG() {
        System.out.println("----测试:新增熔断规则-异常比例 ");
        int age = 10 / 0;
        return "------testG,新增熔断规则-异常比例 ";
    }

image-20240828230336091

不配置Sentinel,对于int age=10/0,调一次错一次报错error,页面报【Whitelabel Error Page】或全局异常

配置Sentinel,对于int age=10/0,如符合如下异常比例启动熔断,页面报【Blocked by Sentinel (flow limiting)】

image-20240828230656770

image-20240828234558108

5.3、异常数

  /**
     * 新增熔断规则-异常数
     *
     * @return
     */
    @GetMapping("/testH")
    public String testH() {
        System.out.println("----测试:新增熔断规则-异常数 ");
        int age = 10 / 0;
        return "------testH,新增熔断规则-异常数 ";
    }

image-20240828232402712

image-20240828232525919

image-20240828234817139

六、SentinelResource注解

6.1、概述

SentinelResource是一个流量防卫防护组件注解,用于指定防护资源,对配置的资源进行流量控制、熔断降级等功能。

https://github.com/alibaba/Sentinel/blob/1.8/sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java
package com.alibaba.csp.sentinel.annotation;

import com.alibaba.csp.sentinel.EntryType;

import java.lang.annotation.*;

/**
 * 该注解用于定义 Sentinel 资源。
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {

    /**
     * @return Sentinel 资源的名称
     */
    String value() default "";

    /**
     * @return 进入类型(入站或出站),默认为出站
     */
    EntryType entryType() default EntryType.OUT;

    /**
     * @return 资源的分类(类型)
     * @自 1.7.0
     */
    int resourceType() default 0;

    /**
     * @return 阻塞异常处理函数的名称,默认为空
     */
    String blockHandler() default "";

    /**
     * 默认情况下,{@code blockHandler} 位于与原方法相同的类中。
     * 但是,如果某些方法具有相同的签名并且打算设置相同的阻塞处理程序,
     * 则用户可以设置阻塞处理程序所在的类。注意,阻塞处理程序方法必须是静态的。
     *
     * @return 阻塞处理程序所在的类,不应提供多个类
     */
    Class<?>[] blockHandlerClass() default {};

    /**
     * @return 回退函数的名称,默认为空
     */
    String fallback() default "";

    /**
     * {@code defaultFallback} 用作默认的通用回退方法。
     * 它不应接受任何参数,并且返回类型应与原方法兼容。
     *
     * @return 默认回退方法的名称,默认为空
     * @自 1.6.0
     */
    String defaultFallback() default "";

    /**
     * 默认情况下,{@code fallback} 位于与原方法相同的类中。
     * 但是,如果某些方法具有相同的签名并且打算设置相同的回退,
     * 则用户可以设置回退函数所在的类。注意,共享的回退方法必须是静态的。
     *
     * @return 回退方法所在的类(仅单个类)
     * @自 1.6.0
     */
    Class<?>[] fallbackClass() default {};

    /**
     * @return 要跟踪的异常类列表,默认是 {@link Throwable}
     * @自 1.5.1
     */
    Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};
    
    /**
     * 指示要忽略的异常。注意,{@code exceptionsToTrace} 和 {@code exceptionsToIgnore}
     * 不应同时出现,否则 {@code exceptionsToIgnore} 将具有更高优先级。
     *
     * @return 要忽略的异常类列表,默认为空
     * @自 1.6.0
     */
    Class<? extends Throwable>[] exceptionsToIgnore() default {};
}

当请求进入微服务时,首先会访问DispatcherServlet,然后进入Controller、Service、Mapper,这样的一个调用链就叫做簇点链路。簇点链路中被监控的每一个接口就是一个资源

默认情况下sentinel会监控SpringMVC的每一个端点(Endpoint,也就是controller中的方法),因此SpringMVC的每一个端点(Endpoint)就是调用链路中的一个资源。

image-20240828141700276

流控、熔断等都是针对簇点链路中的资源来设置的,因此我们可以点击对应资源后面的按钮来设置规则:

  • 流控:流量控制
  • 降级:降级熔断
  • 热点:热点参数限流,是限流的一种
  • 授权:请求的权限控制

6.2、按照rest地址限流+默认限流返回

通过访问res地址来限流,会返回Sentinel自带默认的限流处理信息。

package com.sentinel.service.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: 史小创
 * @Time: 2024/8/28 下午2:20
 * @Description:
 */

@RestController
public class RateLimitController {
    @GetMapping("/rateLimit/byUrl")
    public String byUrl() {
        return "按rest地址限流测试OK";
    }
}

image-20240828142422182

先访问一下:

http://localhost:9001/rateLimit/byUrl

image-20240828142614726

image-20240828142921795

image-20240828142752072

http://localhost:9001/rateLimit/byUrl

6

6.3、按SentinelResource资源名称限流+自定义限流返回

不想用默认的限流提示(Blocked by Sentinel (flow limiting)),想返回自定义限流的提示

@GetMapping("/rateLimit/byResource")
    @SentinelResource(value = "byResourceSentinelResource", blockHandler = "handleException")
    public String byResource() {
        return "按资源名称SentinelResource限流测试OK";
    }

    public String handleException(BlockException exception) {
        return "服务不可用@SentinelResource启动" + "\t" + "o(╥﹏╥)o";
    }

image-20240828143408207

http://localhost:9001/rateLimit/byResource

image-20240828143837999

image-20240828143943693

http://localhost:9001/rateLimit/byResource

7

6.4、按SentinelResource资源名称限流+自定义限流返回+服务降级处理

按SentinelResource配置,点击超过限流配置返回自定义限流提示+程序异常返回fallback服务降级

 @GetMapping("/rateLimit/doAction/{p1}")
    @SentinelResource(value = "doActionSentinelResource",
            blockHandler = "doActionBlockHandler", fallback = "doActionFallback")
    public String doAction(@PathVariable("p1") Integer p1) {
        if (p1 == 0) {
            throw new RuntimeException("p1等于零直接异常");
        }
        return "doAction";
    }

    public String doActionBlockHandler(@PathVariable("p1") Integer p1, BlockException e) {
        System.err.printf("sentinel配置自定义限流了:{}", e);
        return "sentinel配置自定义限流了";
    }

    public String doActionFallback(@PathVariable("p1") Integer p1, Throwable e) {
        System.err.printf("程序逻辑异常了:{}", e);
        return "程序逻辑异常了" + "\t" + e.getMessage();
    }

image-20240828144947872

http://localhost:9001/rateLimit/doAction/99

image-20240828145241809

image-20240828145424387

image-20240828145538861

image-20240828145554556

http://localhost:9001/rateLimit/doAction/99
http://localhost:9001/rateLimit/doAction/0

8

七、实战:热点规则

7.1、概述

热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作

https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81

image-20240828151135470

7.2、全局参数限流

方法testHotKey里面第一个参数P1只要QPS超过每秒1次,马上降级处理

 @GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
    public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                             @RequestParam(value = "p2", required = false) String p2) {
        return "------testHotKey";
    }

    public String dealHandler_testHotKey(String p1, String p2, BlockException exception) {
        return "-----dealHandler_testHotKey";
    }

image-20240828152207986

http://localhost:9001/testHotkey

image-20240828152622745

image-20240828152657339

image-20240828152741465

限流模式只支持QPS模式,固定写死了。(这才叫热点)

@SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推

单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。

上面的抓图就是第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用dealHandler_testHotKey支持方法。

http://localhost:9001/testHotkey?p1=abc
http://localhost:9001/testHotkey?p1=abc
http://localhost:9001/testHotKey?p2=99

9

7.3、测试例外项(热点参数限流)

上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流

我们期望p1参数当它是某个特殊值时,到达某个约定值后【普通正常限流】规则突然例外、失效了,它的限流值和平时不一样,假如当p1的值等于5时,它的阈值可以达到200或其它值

image-20240828173838436

image-20240828173941007

http://localhost:9001/testHotKey?p1=abc
http://localhost:9001/testHotKey?p1=5

10

注意:热点参数的注意点,参数必须是基本类型或者String

image-20240828174347473

八、实战:授权规则

8.1、概述

https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6%E8%A7%84%E5%88%99-authorityrule

image-20240828181035196

在Sentinel的授权规则中,提供了 白名单与黑名单 两种授权类型。白放行、黑禁止

授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。

  • 白名单:来源(origin)在白名单内的调用者允许访问

  • 黑名单:来源(origin)在黑名单内的调用者不允许访问

点击左侧菜单的授权,可以看到授权规则:

image-20240828181207484

  • 资源名:就是受保护的资源,例如/order/{orderId}

  • 流控应用:是来源者的名单,

    • 如果是勾选白名单,则名单中的来源被许可访问。
    • 如果是勾选黑名单,则名单中的来源被禁止访问。

比如:

image-20240828181229237

我们允许请求从gateway到order-service,不允许浏览器访问order-service,那么白名单中就要填写网关的来源名称(origin)

8.2、代码

package com.sentinel.service.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: 史小创
 * @Time: 2024/8/28 下午6:13
 * @Description: Empower授权规则,用来处理请求的来源
 */

@RestController
public class EmpowerController {
    @GetMapping(value = "/empower")
    public String requestSentinel4() {
        System.err.println("测试Sentinel授权规则empower");
        return "Sentinel授权规则";
    }
}
package com.sentinel.service.handler;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;

/**
 * @Author: 史小创
 * @Time: 2024/8/28 下午6:15
 * @Description:
 */

@Component
public class MyRequestOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        return request.getParameter("serverName");
    }
}

image-20240828181928270

8.3、测试

http://localhost:9001/empower

image-20240828182750123

http://localhost:9001/empower?serverName=abc
http://localhost:9001/empower?serverName=test2
http://localhost:9001/empower?serverName=test

11

九、规则持久化

image-20240828192641430

现在,sentinel的所有规则都是内存存储,重启后所有规则都会丢失。在生产环境下,我们必须确保这些规则的持久化,避免丢失。

9.1、规则管理模式

规则是否能持久化,取决于规则管理模式,sentinel支持三种规则管理模式:

  • 原始模式:Sentinel的默认模式,将规则保存在内存,重启服务会丢失。

  • pull模式

  • push模式

9.2、pull模式

pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则。

image-20240828183231650

9.3、push模式

push模式:控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。

image-20240828183306153

9.4、改POM

 <!--SpringCloud ailibaba sentinel-datasource-nacos -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>

image-20240828185546463

9.5、写Yaml

      datasource:
        ds1:
          nacos:
            server-addr: 192.168.200.129:8848
            dataId: ${spring.application.name}
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow # com.alibaba.cloud.sentinel.datasource.RuleType

image-20240828190858764

跟踪rule-type: flow

image-20240828193458457

image-20240828193516164

image-20240828193953165

public enum RuleType {
    FLOW("flow", FlowRule.class),
    DEGRADE("degrade", DegradeRule.class),
    PARAM_FLOW("param-flow", ParamFlowRule.class),
    SYSTEM("system", SystemRule.class),
    AUTHORITY("authority", AuthorityRule.class),
    GW_FLOW("gw-flow", "com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule"),
    GW_API_GROUP("gw-api-group", "com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition");
}
枚举常量含义作用
FLOW限流规则控制应用的QPS(每秒查询率),限制系统流量以避免过载。
DEGRADE熔断降级规则当响应时间过长或错误率过高时,触发降级,临时阻断流量以保护系统。
PARAM_FLOW热点参数限流规则针对特定参数值进行限流,例如限制某个用户或IP的访问频率。
SYSTEM系统规则根据CPU使用率、内存等指标调整流控策略,保护系统整体稳定性。
AUTHORITY授权规则基于黑白名单控制访问权限,限制特定来源的流量。
GW_FLOW网关流控规则针对API网关的流量控制规则,细粒度控制通过网关的流量。
GW_API_GROUP网关API分组规则将API进行分组管理,定义一组API的流量控制策略。

image-20240828194134734

我想多数据源配置呢??

spring:
  application:
    name: sentinel-project

  cloud:
    nacos:
      discovery:
        # Nacos服务注册中心地址
        server-addr: 10.20.30.227:9999

sentinel:
  transport:
    # 配置Sentinel dashboard地址
    dashboard: 10.20.30.94:8080
    # 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
    port: 8719

  datasource:
    # sentinel持久化配置
    ds1:
      # 你是自定义key, 也可以做限流类型等
      nacos:
        server-addr: 10.20.30.227:9999  # nacos地址
        dataId: flow # 你是nacos配置里面的
        groupId: DEFAULT_GROUP # 就是nacos配置里面的
        data-type: json # 你是nacos配置里面的
        rule-type: flow # 也以为流量规则, 具体类型见com.alibaba.cloud.sentinel.datasource.RuleType

    ds2:
      # 你是自定义key, 也可以做限流类型等
      nacos:
        server-addr: 10.20.30.227:9999  # nacos地址
        dataId: degrade # 你是nacos配置里面的
        groupId: DEFAULT_GROUP # 就是nacos配置里面的
        data-type: json # 你是nacos配置里面的
        rule-type: degrade # 也以为熔断降级规则, 具体类型见com.alibaba.cloud.sentinel.datasource.RuleType

9.6、Nacos配置

[
  {
    "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是否集群

image-20240828191100628

image-20240828191349365

9.7、测试

重启服务后一定要多刷几次就出来了哈:

http://localhost:9001/rateLimit/byUrl

12

十、OpenFeign和Sentinel集成实现fallback服务降级

SpringCloud中,微服务调用都是通过Feign来实现的,因此做客户端保护必须整合Feign和Sentinel。

先降低版本:

image-20240828215517583

 <spring.boot.version>3.0.9</spring.boot.version>
        <spring.cloud.version>2022.0.2</spring.cloud.version>

image-20240828215605331

10.1、服务的提供者

image-20240828220055880

101.1、YAML

server:
  port: 9001

spring:
  application:
    name: sentinelprovider
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.200.129:8848 #配置Nacos地址

    sentinel:
      transport:
        dashboard: 192.168.200.129:8858 #配置Sentinel dashboard控制台服务地址
        port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口

10.1.2、主启动

package com.sentinel.provider;

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

/**
 * @Author: 史小创
 * @Time: 2024/8/28 下午7:56
 * @Description:
 */

@SpringBootApplication
@EnableDiscoveryClient
public class SentinelProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(SentinelProviderApplication.class, args);
    }
}

10.1.3、业务类

package com.sentinel.provider.web;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: 史小创
 * @Time: 2024/8/28 下午8:12
 * @Description:
 */

@RestController
public class SentinelProviderController {

    @GetMapping("/rateLimit/doAction/{p1}")
    @SentinelResource(value = "doActionSentinelResource", blockHandler = "doActionBlockHandler")
    public String doAction(@PathVariable("p1") Integer p1) {
        if (p1 == 0) {
            throw new RuntimeException("p1等于零直接异常");
        }
        return "doAction";
    }

    public String doActionBlockHandler(@PathVariable("p1") Integer p1, BlockException e) {
        System.err.printf("sentinel配置自定义限流了:{}", e);
        return "sentinel配置自定义限流了";
    }
}

10.1.4、测试

http://localhost:9001/rateLimit/doAction/1

image-20240828215929074

10.2、OpenFeign

image-20240828220328978

 <dependencies>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>
package com.sentinel.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "sentinelprovider", fallback = FeignSentinelApiFallBack.class)
public interface SentinelApi {
    @GetMapping("/rateLimit/doAction/{p1}")
    public String doAction(@PathVariable("p1") Integer p1);

}
package com.sentinel.feign;

import org.springframework.stereotype.Component;

/**
 * @Author: 史小创
 * @Time: 2024/8/28 下午8:26
 * @Description: 统一服务降级类
 */

@Component
public class FeignSentinelApiFallBack implements SentinelApi {
    @Override
    public String doAction(Integer p1) {
        return "对方服务宕机或不可用,FallBack服务降级o(╥﹏╥)o";
    }
}

10.3、服务消费者

10.3.1、POM

<dependencies>
        <!-- 引入自己定义的api通用包 -->
        <dependency>
            <groupId>com.sentinel</groupId>
            <artifactId>sentinel-feign</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

10.3.2、YAML

server:
  port: 9002

spring:
  application:
    name: sentinelconsumer
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.200.129:8848 #配置Nacos地址

# 激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true

10.3.3、主启动

package com.sentinel.consumer;

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

/**
 * @Author: 史小创
 * @Time: 2024/8/28 下午7:56
 * @Description:
 */

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.sentinel.feign")
@ComponentScan("com.sentinel")
public class SentinelConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SentinelConsumerApplication.class, args);
    }
}

10.3.4、业务类

package com.sentinel.consumer.web;

import com.sentinel.feign.SentinelApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: 史小创
 * @Time: 2024/8/28 下午8:33
 * @Description:
 */

@RestController
public class SentinelConsumerController {
    @Autowired
    private SentinelApi sentinelApi;

    @GetMapping("/rateLimit/doAction/{p1}")
    public String doAction(@PathVariable("p1") Integer p1) {
        return sentinelApi.doAction(p1);
    }
}

10.3.5、测试

http://localhost:9002/rateLimit/doAction/1
http://localhost:9002/rateLimit/doAction/0

image-20240828220727389

image-20240828220738700

10.4、整合Sentinel

image-20240828220913600

image-20240828220931509

image-20240828220940379

http://localhost:9002/rateLimit/doAction/1
http://localhost:9002/rateLimit/doAction/0

13

10.5、版本复原

image-20240828224525489

十一、GateWay和Sentinel集成实现服务限流

image-20240828224653700

11.1、POM

Spring Cloud Gateway确实是基于Spring WebFlux的反应式框架构建的。因此,通常情况下,不需要也不应该同时包含spring-boot-starter-web依赖,因为spring-boot-starter-web是基于Servlet API的,而Spring WebFlux是基于反应式堆栈的。这两者使用不同的底层模型,不适合一起使用。

  <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

image-20240828224734361

<dependencies>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-simple-http</artifactId>
            <version>1.8.6</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
            <version>1.8.6</version>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
    </dependencies>

image-20240828224845723

11.2、YAML

server:
  port: 8888

spring:
  application:
    name: sentinelgateway     # sentinel+gataway整合Case
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.200.129:8848
    gateway:
      routes:
        - id: pay_routh1 #pay_routh1                #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
          uri: lb://sentinelprovider            #匹配后提供服务的路由地址
          predicates:
            - Path=/rateLimit/doAction/**                      # 断言,路径相匹配的进行路由

11.3、启动

package com.sentinel.gateway;

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

/**
 * @Author: 史小创
 * @Time: 2024/8/28 下午10:19
 * @Description:
 */

@SpringBootApplication
@EnableDiscoveryClient
public class SentinelGatewayApplication {
    public static void main(String[] args) {
        org.springframework.boot.SpringApplication.run(SentinelGatewayApplication.class, args);
    }
}

11.4、配置类

https://sentinelguard.io/zh-cn/docs/api-gateway-flow-control.html

image-20240828225407856

package com.sentinel.gateway.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * @Author: 史小创
 * @Time: 2024/8/28 下午10:24
 * @Description:
 */

@Configuration
public class GatewayConfiguration {
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        // Register the block exception handler for Spring Cloud Gateway.
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    @Bean
    @Order(-1)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    @PostConstruct
    public void doInit() {
        // 自己动手,丰衣足食
        // initGatewayRules();
        initBlockHandler();
    }

    // 处理+自定义返回的例外信息内容,类似我们的调用触发了流控规则保护
    private void initBlockHandler() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(new GatewayFlowRule("pay_routh1").setCount(2).setIntervalSec(1));

        GatewayRuleManager.loadRules(rules);

        BlockRequestHandler handler = new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
                Map<String, String> map = new HashMap<>();

                map.put("errorCode", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
                map.put("errorMessage", "请求太过频繁,系统忙不过来,触发限流(sentinel+gataway整合Case)");

                return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(BodyInserters.fromValue(map));
            }
        };

        GatewayCallbackManager.setBlockHandler(handler);
    }
}

11.5、测试

http://localhost:8888/rateLimit/doAction/1

14

十二、环境搭建

SpringBoot+SpringCloud

   <spring.boot.version>3.2.0</spring.boot.version>
        <spring.cloud.version>2023.0.0</spring.cloud.version>
        <spring.cloud.alibaba.version>2022.0.0.0</spring.cloud.alibaba.version>

docker

image-20240827194204811

Nacos

docker pull nacos/nacos-server:v2.0.3
docker run --env MODE=standalone --name nacos --restart=always -d -p 8848:8848 -p 9848:9848 -p 9849:9849 -e JVM_XMS=512m -e JVM_XMX=512m -v /opt/nacos:/home/nacos/nacos-server-2.0.3/nacos/standalone/data nacos/nacos-server:v2.0.3
http://192.168.200.129:8848/nacos/#/login

sentinel

docker pull bladex/sentinel-dashboard:1.8.6
docker run --name sentinel-dashboard --restart=always -p 8858:8858 -v /opt/sentinel:/opt/sentinel -d bladex/sentinel-dashboard:1.8.6
http://192.168.200.129:8858/#/login

jdk:

image-20240824171950942

Maven

image-20240823181810973

IDEA

image-20240823181906302

代码汇总:

https://github.com/shixiaochuangjob/markdownfile/tree/main/20240828

image-20240829000412781

https://mp.weixin.qq.com/s?__biz=MzkwOTczNzUxMQ==&mid=2247485129&idx=1&sn=6ff255ba38842473c025ada5ccd72182&chksm=c1376d81f640e49770514d2af99b21e9893c606c31fa9eefbd9483a9af3053901635d43a8c0e#rd

在这里插入图片描述

  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值