Sentinel

Sentinel 是由阿里巴巴开源的一个面向分布式、多语言异构化服务架构的流量治理组件,提供流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等功能

两大核心概念:

资源 (Resource) :可以是任何东西,比如一个 HTTP 接口、一段 Java 方法。只要通过Sentinel API或注解( @SentinelResource)包裹起来,它就是被保护的资源

规则 (Rule):定义如何保护资源。包含流控规则、熔断规则、热点规则、系统规则等

Sentinel使用

Sentinel由两部分组成,核心库(Java 客户端) : 不依赖任何框架/库, 能够运行于所有 Java 运行时环境, 同时对Spring Cloud 等框架也有较好的支持;控制台(Dashboard) 基于 Spring Boot 开发, 打包后可以直接运行

Sentinel Dashboard

  1. 下载

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

  2. 启动,将jar放在一个目录下,用cmd打开这个文件夹

    复制代码
    java -jar .\文件名
  3. 默认用户名和密码都是sentinel

    官方文档:https://sentinelguard.io/zh-cn/docs/dashboard.html

核心库

独立使用

  1. 引入依赖

    复制代码
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-core</artifactId>
        <version>1.8.6</version>
    </dependency>
  2. 定义资源

    java 复制代码
    //通过try-catch
    try(Entry entry= SphU.entry("资源名称")){
    	//被保护的逻辑
    }catch (BlockException ex){
    	//请求被拒绝的逻辑
    }
  3. 定义规则

    java 复制代码
    private static void initFlowRules(){
    		//新建规则集合
        List<FlowRule> rules = new ArrayList<>();
        //新建规则
        FlowRule rule = new FlowRule();
        //添加资源
        rule.setResource("资源名称");
        //设置限流维度为 QPS
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 设置每秒多少请求
        rule.setCount(20);
        rules.add(rule);
        添加规则
        FlowRuleManager.loadRules(rules);
    }

Spring项目

  1. 添加依赖

    复制代码
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
  2. 添加配置

    yaml 复制代码
    spring:
      cloud:
        sentinel:
          transport:
            dashboard: # sentinel控制台地址
            client-ip: # 连接本地Sentinel可以不配置,连接服务器Sentinel需要配置client-ip
  3. Sentinel starter默认对Spring MVC的所有HTTP服务进行限流,如果想要对非HTTP服务进行限流或者降级, 则需要在方法上添加@SentinelResource("资源名")

流量控制

流控规则

QPS:控制每秒请求数

并发线程数:表示最多只给 /order/{orderId} 分配的线程个数,防止因为接口响应过慢导致线程池耗尽

流控效果

  1. 快速失败:丢弃超过阈值的请求,对应RuleConstant.CONTROL_BEHAVIOR_DEFAULT ,适用于通过压测确定系统的抗压能力。底层主要依赖滑动窗口算法
  2. Warm Up:也叫预热模式,单机阈值一般设置为一个微服务能够承受的最大QPS。一个服务器刚启动时,资源还没有初始化,处理请求的速度远不及正常情况下的最大QPS。此时如果之间将QPS跑到最大值,会直接导致服务器宕机。预热模式则利用冷启动算法,系统起始放行的 QPS 只是 单机阈值 / 冷启动因子(默认3)。在随后的预热时长 内,逐步将QPS上限拉升到填写的标准阈值。对应RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
  3. 排队等待:同一时间的请求会放到一个队列中,按照阈值允许的时间间隔依次执行。比如当QPS=5时,系统会每隔200ms(1000ms/5)从队列中取出并处理一个请求。如果在队列里等待请求超过了 超时时间,就会被丢弃

流控模式

  1. 直接模式:根据自身收到的流量判断,不区分流量的来源

  2. 链路模式:针对同一个底层公共方法或者接口,只拦截特定上游来源的流量

    链路模式中,是对不同来源的两个链路做监,但是sentinel会默认给所有进入SpringMVC的请求设置同 一个root资源, 会导致链路模式失效. 所以需要关闭这种对SpringMVC的资源聚合

    yaml 复制代码
    spring:
      cloud:
        sentinel:
          web-context-unify: false
  3. 关联模式:

    关联资源:两个资源之间具有资源争抢或者依赖关系的时,比如读请求和写请求都依赖于数据库,则称这两个资源为关联资源

    关联模式:两个资源存在资源抢占时,关联资源超过单机阈值,就会对当前资源进行拦截(关联资源正常提供服务)。当关联资源低于阈值时,再对当前资源进行放行

热点参数限流

热点参数限流表示以资源参数值为维度进行限流,对于/product/{productId}接口,如果大家都在访问id为1的商品,这时可以对productId=1这个参数进行单独的流量控制,比如将单点阈值适当增加

对相同参数进行统一限流

  1. 定义资源,热点参数限流对默认的Spring MVC 资源无效, 需要在资源上加 @SentinelResource("资源名")注解
  2. 表示对于第一个参数的相同参数值每秒请求数不能超过5

对相同参数进行额外限流

表示对参数为1的请求限制阈值为10,其余参数值的阈值不变

限流算法

计数器算法

将时间划分为一个个固定大小的时间窗口,每个窗口单独维护一个计数器。当计数器超过阈值时,拒绝请求,时间进入下一个窗口时,计数器重置为0

缺点:假设QPS=100,在0.8s ~ 1.0s和0.8s ~ 1.2s两个时间段内各放行100个请求,那是在0.4s内服务器就会收到200个请求,大量请求会被拒绝

滑动窗口算法

将时间划分为更高的精细度,假设将1s划分为5块,每块为200ms。那么窗口每200ms会往前移一格,每次需要保证当前窗口内的请求数不超过QPS

Sentinel 底层就是通过滑动窗口算法实现的

漏桶限流算法

进水:用户的请求会放入一个队列中

漏水:服务器以固定速度从队列中取出数据并处理请求

溢出:队列满时,超过队列容量的请求会触发限流

适用于数据库操作,防止高并发情况造成的数据库服务器宕机

令牌桶限流算法

发放令牌:服务器以固定速率往桶内放入令牌,桶内令牌数达到上限,后续的令牌直接丢弃

消耗令牌:每个到达的请求都需要从桶中拿取固定数目的令牌,只有拿到足够令牌的请求才被允许通过,如果拿到的令牌数不够则触发限流。可以处理突发请求,Spring Cloud Gateway的RequestRateLimiter底层就是使用令牌桶算法实现限流功能

熔断降级

为了保证系统的高可用而做出的额外逻辑

熔断:当上游服务器A访问下游服务器B时,发现服务器B频繁超时或者报错,那么服务器A就不会真正调用服务器B,而是直接返回

降级:当下游服务器宕机,为了避免返回空数据,系统自动降级,返回默认数据

熔断策略

决定什么时候触发熔断

  1. 慢调用比例

    超过最大响应的时间(RT)的调用称为慢调用,当请求数量 > 设定的最小数量, 且慢调用比例 > 设定的阈值时触发熔断

  2. 异常比例

    统计时长内, 当请求数量 > 设置的最小数量, 且异常比例大于阈值时触发熔断

  3. 异常数

    统计时长内,当异常数目超过阈值时触发熔断

状态机

  1. 关闭状态:请求正常通过,Sentinel在后台收集慢响应和异常数据
  2. 熔断状态:触发熔断条件,在休眠时间窗口内,所有的新请求都会走降级逻辑
  3. 半开状态:休眠时间结束后,先放行少量请求,根据失败率来判断是否恢复正常

FallbackFactory

FallbackFactory是微服务架构中用于实现服务降级调用的接口,当远程调用失败或者超时时,FallbackFactory会根据异常信息创建一个降级处理实例,代替原服务调用。对于熔断和限流都有效

  1. 开启配置

    复制代码
    feign:
      sentinel:
        enabled: true
  2. 编写FallbackFactory实现类

    java 复制代码
    public class ProductFallBackFactory implements FallbackFactory<productAPI> {
        @Override
        public productAPI create(Throwable cause) {
            return new productAPI() {};
        }
    }
  3. 在接口上配置FallbackFactory实现类

    java 复制代码
    @FeignClient(value = "product-service",path = "/product",fallbackFactory = ProductFallBackFactory.class)
    public interface productAPI {
    }
  4. 定义DefaultFeignConfiguration类,并在类中添加ProductFallBackFactory的Bean,最后将DefaultFeignConfiguration交给Spring进行管理

    java 复制代码
    //Public-API
    public class DefaultFeignConfiguration {
        @Bean
        public ProductFallBackFactory productFallBackFactory(){
            return new ProductFallBackFactory();
        }
    }
    
    // 启动类
    @EnableFeignClients(clients ={productAPI.class} ,defaultConfiguration = DefaultFeignConfiguration.class)

授权规则

一种黑白名单机制,根据调用方的身份,决定是否允许其访问指定的受保护的资源

Sentinel提供了两种授权模式:白名单和黑名单

白名单模式:只有在白名单内才允许访问

黑名单模式:黑名单内不允许访问,并抛出AuthorityException异常,其余请求允许访问

配置

  1. 实现RequestOriginParser接口,从请求中提取出带有调用方身份的字符串(一般放到Header中),并交给Spring进行管理

    java 复制代码
    @Configuration
    public class HeaderOriginParser implements RequestOriginParser {
        @Override
        public String parseOrigin(HttpServletRequest request) {
            String origin=request.getHeader("origin");
            ...
            return origin;
        }
    }
  2. 配置请求头

    gateway:

    yaml 复制代码
    spring:
      cloud:
    	gateway:
    	  default-filters:
    		 - AddRequestHeader=origin,gateway #AddRequestHeader是gateway自带的请求头过滤器
  3. 配置授权规则

定义异常返回结果

实现BlockExceptionHandler接口

java 复制代码
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
       //设置编码
        response.setContentType("text/html;charset=utf-8");
        String msg="Blocked by Sentinel (flow limiting)";
        int status=429;
        if (e instanceof AuthorityException){
            status=401;
            msg="授权失败";
        }else if (e instanceof DegradeException){
            msg="触发降级";
        }else if (e instanceof FlowException){
            System.out.println("触发限流");
        }else if (e instanceof ParamFlowException){
            msg="触发热点限流";
        }
        response.setStatus(status);
        PrintWriter printWriter=response.getWriter();
        printWriter.print(msg);
        printWriter.flush();
        printWriter.close();
    }
}

规则管理及推送

特性 原始模式 (In-Memory) 拉模式 (Pull) 推模式 (Push)
规则持久化 不持久化 (重启即失) 持久化 (文件/数据库) 持久化 (Nacos/Apollo 等)
规则生效实时性 实时 有延迟 (取决于轮询周期) 毫秒级实时 (配置中心推送)
对配置中心依赖 无依赖 无或弱依赖 强依赖 (需要 Nacos/Apollo)
多实例同步/集群扩展 无法自动同步 自动同步 (轮询) 自动同步 (订阅通知)
适用场景 本地开发、测试验证 简单单机/小规模环境 生产环境标准方案

Pull模式

Sentinel控制台通过API将规则推送给客户端,由客户端负责在内存中更新规则并将规则写入本地文件

实现

java 复制代码
public class FileDataSourceInit implements InitFunc {
    @Override
    public void init() throws Exception {
        //定义文件路径
        String ruleDir = System.getProperty("user.home") +
                "/sentinel/rules/orderService";
        //定义文件名,不同规则要定义不同的文件,这里以流控规则为例
        String flowRulePath = ruleDir + "/flow-rule.json";
        //创建文件夹
        mkdirIfNotExist(ruleDir);
        //创建文件
        createFileIfNotExist(flowRulePath);
// 注册一个可读数据源,用来定时读取本地的json文件,更新到规则缓存中
// 流控规则
        ReadableDataSource<String, List<FlowRule>> ds = new
                FileRefreshableDataSource<>(
                flowRulePath, source -> JSON.parseObject(source, new
                TypeReference<List<FlowRule>>() {})
        );
// 将可读数据源注册至FlowRuleManager
// 这样当规则文件发生变化时,就会更新规则到内存
        FlowRuleManager.register2Property(ds.getProperty());
        WritableDataSource<List<FlowRule>> flowRuleWDS = new
                FileWritableDataSource<>(
                flowRulePath,
                this::encodeJson
        );
// 将可写数据源注册至transport模块的WritableDataSourceRegistry中
// 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写入到文件中
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
    }
    private <T> String encodeJson(T t) {
        return JSON.toJSONString(t);
    }
    private void mkdirIfNotExist(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdirs();
        }
    }
    private void createFileIfNotExist(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.createNewFile();
        }
    }
}

在resources下创建META-INF/services目录,并创建文件: com.alibaba.csp.sentinel.init.InitFunc, 文件内填写本地数据源的路径

com.object.FileDataSourceInit

Push模式

由控制台负责将配置推送到远程配置中心,数据源负责获取远程配置中心的配置并更新到本地

不修改源码

控制台对规则的修改无法同步到配置中心,只能接收配置中心的数据

  1. 添加依赖

    复制代码
    <dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
  2. 注册数据源

    java 复制代码
    public class NacosDataSourceInit implements InitFunc {
        // nacos server ip
        private static final String remoteAddress = "";
        // nacos group
        private static final String groupId = "";
        // nacos dataId
        private static final String dataId = "";
        @Override
        public void init() throws Exception {
            ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new
                    NacosDataSource<>(remoteAddress, groupId, dataId,
                    source -> JSON.parseObject(source, new
                            TypeReference<List<FlowRule>>() {
                            }));
            FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
        }
    }
  3. 配置数据源路径,在resources下创建META-INF/services目录,并创建文件: com.alibaba.csp.sentinel.init.InitFunc, 文件内填写本地数据源的路径

    com.object.NacosDataSourceInit

  4. 配置nacos

    json 复制代码
    [
    {
    "clusterMode": false,
    "controlBehavior": 0,
    "count": 88.0,
    "grade": 1,
    "limitApp": "default",
    "maxQueueingTimeMs": 500,
    "resource": "/order/{orderId}",
    "strategy": 0,
    "warmUpPeriodSec": 10
    }
    ]

源码改造

可以和配置中心双向通信

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

以下修改全部基于sentinel-dashboard源码

  1. 修改pom文件

  2. 将test下的com.alibaba.csp.sentinel.dashboard.rule.nacos复制到src/main/java/com.alibaba.csp.sentinel.dashboard.rule

  3. 修改com.alibaba.csp.sentinel.dashboard.rule.nacos.NacosConfig,将默认地址修改为自己的Nacos地址

  4. 修改com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2为

  5. 修改前端页面

    1. 修改src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html,将以下注释取消

    2. 修改src/main/webapp/resources/app/scripts/controllers/identity.js,将第4行左右的FlowServiceV1 修改为 FlowServiceV2

    3. 修改第98行左右的/dashboard/flow/ -> /dashboard/v2/flow/

  6. 将sentinel-dashboard重新打包,然后启动,需要清理浏览器缓存

  7. 配置nacos

    1. 引入依赖

      复制代码
      <dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>sentinel-datasource-nacos</artifactId>
      </dependency>
    2. 配置nacos地址

      yaml 复制代码
      spring:
        cloud:
          sentinel:
            datasource:
              flow-rules: #流控规则
                nacos:
                  server-addr: 192.168.109.130:8848
                  dataId: ${spring.application.name}-flow-rules
                  groupId: SENTINEL_GROUP
                  rule-type: flow
相关推荐
阿pin2 小时前
Java随笔-JDK7 HashMap头插法为何能导致死循环?
java·开发语言·hashmap
名字还没想好☜2 小时前
Go 的 sync.Cond 实战:用条件变量做等待/通知,比忙轮询省 CPU
开发语言·数据库·后端·golang·go
zzzll11112 小时前
Spring Boot 入门指南:从零开始构建微服务
spring boot·后端·微服务
李可以量化3 小时前
Tornado 从了解到精通(一)下:异步与非阻塞 IO 核心原理
java·数据库·tornado
yurenpai(27届找实习中)3 小时前
从销售订单推送 MES 看懂微服务调用(二):DTO 转换与 OpenFeign 调用
java·微服务·架构·服务调用
运维行者_3 小时前
企业带宽监控工具实战:网络流量分析与异常排查的5个关键能力
运维·服务器·开发语言·网络·分布式·后端·php
Patrick在香港3 小时前
Python爬虫框架实战:优雅抓取香港政府公开API数据的通用方案
java·开发语言·爬虫·python·ai编程·数据可视化·可用性测试
长栎3 小时前
你用了五年的消息队列,不知道它背后站着中介者模式
后端
Gopher_HBo3 小时前
路由注册:RouterGroup(routergroup.go)
后端