Sentinel限流熔断应用

Sentinel是阿里开源的流量防护工具,提供流量控制、熔断降级和系统保护等功能。本文介绍了Sentinel的安装、服务注册、限流快速入门及流控规则分析,包括QPS限流、线程数限流和不同流控模式的设置。同时,文章还讨论了Sentinel的异常处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在idea中配置启动  Sentinel  的脚本

 

 

script options:   -Dserver.port=8180 -Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar D:/sentinel/sentinel-dashboard-1.8.0.jar

配置的 Sentinel 在哪一个目录下

Sentinel概述

Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点, 从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。
Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景, 例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。

Sentinel核心分为两个部分:

核心库(Java 客户端):能够运行于所有 Java 运行时环境,同时对Dubbo /Spring Cloud 等框架也有较好的支持。
控制台(Dashboard):基于 Spring Boot 开发,打包后可以直接运行。


安装Sentinel服务

Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能,其控制台安装步骤如下:
第一步:打开sentinel下载网址

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

第二步:下载Jar包(可以存储到一个sentinel目录),如图所示:

第三步:在sentinel对应目录,打开命令行(cmd),启动运行sentinel

java -Dserver.port=8180 -Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar

检测启动过程,如图所示:

访问Sentinal服务

 第一步:假如Sentinal启动ok,通过浏览器进行访问测试,如图所示:

第二步:登陆sentinel,默认用户和密码都是sentinel,登陆成功以后的界面如图所示:

 

Sentinel限流入门
概述
我们系统中的数据库连接池,线程池,nginx的瞬时并发,MQ消息等在使用时都会跟定一个限定的值,这本身就是一种限流的设计。限流的目的防止恶意请求流量、恶意攻击,或者防止流量超过系统峰值。

Sentinel集成
第一步:Sentinel 应用于服务消费方(Consumer),在消费方添加依赖如下:
 

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>01-sca</artifactId>
        <groupId>com.jt</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>sca-consumer</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--Web服务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--服务的注册和发现(我们要讲服务注册到nacos)-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!--当基于feign方式进行服务调用时就需要此依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!--限流依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--打开springboot的监控功能-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

    </dependencies>

</project>

第二步:打开服务消费方配置文件application.yml,添加sentinel配置,代码如下:

spring:
  cloud:
    sentinel:
      transport:
         port: 8099 #跟sentinel控制台交流的端口,随意指定一个未使用的端口即可
         dashboard: localhost:8180 # 指定sentinel控制台地址。
server:
  port: 8090
spring:
  application:
    name: sca-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #从哪里去查找服务
    sentinel:
      transport:
        port: 8719     #跟sentinel控制台交流的端口,随意指定一个未使用的端口即可  默认情况下是8719,如果该端口被占用会自动递增8720,8721 ...
        dashboard: localhost:8180    #指定sentinel控制台地址。
     # web-context-unify: false  #取消链路聚合,否则sentinel 1.7.2以后版本会默认将所有所有链路聚合到一个入口上
  # feign方式访问超时配置
#ribbon:
  #ConnectTimeout: 50000 #连接超时(主要是针对网络阻塞)
  #ReadTimeout: 50000 #请求处理超时

#feign方式的服务熔断处理    启动feign方式调用时的服务中断处理机制
feign:
  hystrix: #hystrix 含义是熔断(就相当于服务停止了)或降级
    enabled: true #默认值为false

#打开springboot工程中的监控端点
management:
  endpoints:
    web:
      exposure:
        include: '*'

bootstrap.yml

 

server:
  port: 8083
  #tomcat:
    #threads:
    # max: 300   #最大线程数
  #    min-spare: 8  #核心线程数

spring:
  application:
    name: sca-provider
  cloud:
    nacos:
      config:   #配置中心的配置
        server-addr: localhost:8848
        file-extension: yml
        # 命名空间
        namespace: d8f592cf-503d-4123-b725-94d94f2bdc2d
        # 分组名
        group: DEV_GROUP_51
        # 共享配置
        shared-configs[0]:
          data-id: app-common-dev.yml
          group: DEFAULT_GROUP
          refresh: true #默认false


      discovery: #注册中心
        server-addr: localhost:8848 #服务注册和发现(默认是注册到这个地步)
# 配置日志 (经常被修改的配置,需要配置到配置中心中)
#logging:
#  level:
#    com.jt: debug

第三步:启动服务提供者,服务消费者,然后在浏览器访问消费者url,如图所示:

第四步:刷新sentinel 控制台,检测服务列表,如图所示:

Sentinel的控制台其实就是一个SpringBoot编写的程序,我们需要将我们的服务注册到控制台上,即在微服务中指定控制台的地址,并且还要在消费端开启一个与sentinel控制台传递数据端的端口,控制台可以通过此端口调用微服务中的监控程序来获取各种信息。

Sentinel限流快速入门

我们设置一下指定接口的流控(流量控制),QPS(每秒请求次数)单机阈值为1,代表每秒请求不能超出1次,要不然就做限流处理,处理方式直接调用失败。

第一步:选择要限流的链路,如图所示:

第二步:设置限流策略,如图所示:

 

第三步:反复刷新访问消费端端服务,检测是否有限流信息输出,如图所示:

 

 

Sentinel流控规则分析


阈值类型分析
QPS(Queries Per Second):当调用相关url对应的资源时,QPS达到单机阈值时,就会限流。

线程数:当调用相关url对应的资源时,线程数达到单机阈值时,就会限流。

设置限流模式
Sentinel的流控模式代表的流控的方式,默认【直接】,还有关联,链路。

直接模式

 Sentinel默认的流控处理就是【直接->快速失败】。

关联模式 

当关联的资源达到阈值,就限流自己。例如设置了关联资源为/ur2时,假如关联资源/url2的qps阀值超过1时,就限流/url1接口(是不是感觉很霸道,关联资源达到阀值,是本资源接口被限流了)。这种关联模式有什么应用场景呢?我们举个例子,订单服务中会有2个重要的接口,一个是读取订单信息接口,一个是写入订单信息接口。在高并发业务场景中,两个接口都会占用资源,如果读取接口访问过大,就会影响写入接口的性能。业务中如果我们希望写入订单比较重要,要优先考虑写入订单接口。那就可以利用关联模式;在关联资源上面设置写入接口,资源名设置读取接口就行了;这样就起到了优先写入,一旦写入请求多,就限制读的请求。例如:

 

链路模式

链路模式只记录指定链路入口的流量。也就是当多个服务对指定资源调用时,假如流量超出了指定阈值,则进行限流。被调用的方法用@SentinelResource进行注解,然后分别用不同业务方法对此业务进行调用,假如A业务设置了链路模式的限流,在B业务中是不受影响的。例如现在设计一个业务对象,代码如下(为了简单,可以直接写在启动类内部):
 

@Service
public class ConsumerService{
    @SentinelResource("doGetResource")
    public String doGetResource(){
        return "doGetResource";
    }
}

接下来我们在/consumer/doRestEcho1对应的方法中对ConsumerService中的doGetResource方法进行调用(应用consumerService对象之前,要先在doRestEcho01方法所在的类中进行consumerService值的注入)。例如:
 

   @GetMapping("/consumer/doRestEcho1")
    public String doRestEcho01() throws InterruptedException {
        consumerService.doGetResource();
        //Thread.sleep(200);
        String url="http://localhost:8081/provider/echo/"+server;
        //远程过程调用-RPC
        return restTemplate.getForObject(url,String.class);//String.class调用服务响应数据类型
    }

其路由规则配置如下:

 

说明,流控模式为链路模式时,假如是sentinel 1.7.2以后版本,Sentinel Web过滤器默认会聚合所有URL的入口为sentinel_spring_web_context,因此单独对指定链路限流会不生效,需要在application.yml添加如下语句来关闭URL PATH聚合,例如:

sentinel:
     web-context-unify: false

修改配置以后,重新sentinel,并设置链路流控规则,然后再频繁对链路/consumer/doRestEcho1进行访问,检测是否会出现500异常。

Sentinel 异常处理

系统提供了默认的异常处理机制,假如默认处理机制不满足我们需求,我们可以自己进行定义。定义方式上可以直接或间接实现BlockExceptionHandler接口,并将对象交给spring管理。

@Component
public class ServiceBlockExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,BlockException e) throws Exception {
         //response.setStatus(601);
         //设置响应数据的编码
         response.setCharacterEncoding("utf-8");
         //告诉客户端要响应的数据类型以及客户端以什么编码呈现数据
         response.setContentType("text/html;charset=utf-8");
         PrintWriter pw=response.getWriter();
         Map<String,Object> map=new HashMap<>();
         if(e instanceof DegradeException){//降级、熔断
             map.put("status",601);
             map.put("message", "服务被熔断了!");
         }else if(e instanceof FlowException){
             map.put("status",602);
             map.put("message", "服务被限流了!");
         }else{
             map.put("status",603);
             map.put("message", "Blocked by Sentinel (flow limiting)");
         }
         //将map对象转换为json格式字符串
         String jsonStr=new ObjectMapper().writeValueAsString(map);
         pw.println(jsonStr);
         pw.flush();
    }
}
package com.jt.excetion;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

@Component
public class ServiceBlockExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest,
                       HttpServletResponse httpServletResponse,
                       BlockException e) throws Exception {
        //设置响应数据的编码
        httpServletResponse.setCharacterEncoding("utf-8");
        //告诉客户端要响应的数据类型以及客户端以什么编码呈现数据
        httpServletResponse.setContentType("text/html;charset=utf-8");
        PrintWriter pw= httpServletResponse.getWriter();
        pw.println("服务维护中");
        pw.flush();
    }
}

ConsumerApplication

package com.jt;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.jt.service.ConsumerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.atomic.AtomicLong;

//@EnableFeignClients注解描述启动类时,用于告诉springboot在启动时,
//扫描启动类所在包或子包中的类,假如接口上有@FeignClient注解描述,则对
//这样的接口创建其实现类,在实现类内部帮我们进行远程服务调用

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

    /**创建RestTemplate对象,然后基于此对象进行远程服务调用
     * @Bean 注解用于描述方法,用于告诉spring框架这个方法的返回值交给spring管理,
     * spring默认会这个方法返回对象起一个bean的名字(key),默认为方法名.当然也可以
     * 对一个bean的名字进行自定义,例如@Bean("自己定义的名字").
     * 思考:spring框架中为什么会给出这样的注解,用于描述方法,在方法中构建对象?
     * 第一:Spring是一个资源整合框架.
     * 第二:何为资源? (内存中对象)
     * 第三:所有对象是否都有类型?   是
     * 第四:所有对象的类型都是你自己定义的吗?不一定(有自己,有第三方)
     * 第五:第三方的类,我们能在类上直接添加注解描述吗(例如@Component)?不可以
     */

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    @Bean
    @LoadBalanced  //这个注解描述RestTemplate对象时,系统底层会对RestTemplate对象的请求进行拦截  性能较为差一些
    public RestTemplate loadBlanceRestTemplate(){
    return new RestTemplate();
    }




    @RestController
    public class ConsumerController{

        @Autowired
        private ConsumerService consumerService;
        //http://localhost:8090/consumer/findById?id=10
        @GetMapping("/consumer/findById")
        @SentinelResource("res")
        public String doFindById(@RequestParam Integer id){
            return "resource id is "+id;
        }






        @Autowired
        private RestTemplate restTemplate;

        @Value("${spring.application.name}")
        private String appName;

        //负载均衡客户端对象(基于此对象可以从nacos中获取服务列表,并且可以基于一定的算法
        //从列表中获取一个服务实例)
        @Autowired
        private  RestTemplate loadBlanceRestTemplate;
        /**
         * @Autowired  注解描述属性时,会告诉spring框架,要优先属性类型进行对象的查找和注入,假如此类型的对象存在多个
         * 此时还会按照属性名进行查找和比对,有相同的则直接注入(DI),没有相同的则出错,
         * 当然也可以在属性上添加@Qualifier("bean的名字")注解,指定要注入的具体对象
         */

        @Autowired
        private LoadBalancerClient loadBalancerClient;
        //J.U.C (原子操作对象,提供了线程安全的操作)
            private AtomicLong atomicLong= new AtomicLong(1);
        //直接调用(非负载均衡方式)
        //http://localhost:8090/consumer/doRestEcho1
        @GetMapping("/consumer/doRestEcho1")
        public String doRestEcho1() throws InterruptedException {
            long num=atomicLong.getAndIncrement();
            if(num%2==0){//模拟50%的慢调用比例
                Thread.sleep(200);
            }
            String url="http://localhost:8083/provider/echo/"+appName;
            return restTemplate.getForObject(url,String.class);
        }

        //负载均衡应用方式1
        @GetMapping("/consumer/doRestEcho2")
        public String doRestEcho2(){
            consumerService.doGetResource();



            //手动自己写负载均衡算法(随机调用服务列表中的服务对象)
            //调用谁?sca-provider中的一个url
//            String url1="http://localhost:8080/provider/echo/"+appName;
//            String url2="http://localhost:8081/provider/echo/"+appName;
//            String url3="http://localhost:8082/provider/echo/"+appName;
//            String urls[]=new String[]{url1,url2,url3};
//            //随机获取一个小于urls数组长度的整数
//            int n=new Random().nextInt(urls.length);
//            System.out.println("n="+n);
//            //如何调用?
//            return restTemplate.getForObject(urls[n],String.class);

            //基于loadBalancerClient方式获取服务实例
            String serviceId="sca-provider";//这个名字要在nacos的服务列表中
            ServiceInstance choose = loadBalancerClient.choose(serviceId);
            String ip = choose.getHost();
            int port = choose.getPort();
            //String url="http://"+ip+":"+port+"/provider/echo/"+appName;
            String  url=String.format("http://%s:%s/provider/echo/%s",ip,port,appName);
            return restTemplate.getForObject(url, String.class);

        }

        //负载均衡应用方式2:@LoadBalanced  //此方法性能没有第二种好一点
        @GetMapping("/consumer/doRestEcho3")
        public String doRestEcho3(){
            consumerService.doGetResource();
            //基于loadBalancerClient方式获取服务实例
            String serviceId="sca-provider";//这个名字要在nacos的服务列表中
            String  url=String.format("http://%s/provider/echo/%s",serviceId,appName);
            return loadBlanceRestTemplate.getForObject(url, String.class);
        }




    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值