Skip to content

sentinel介绍和使用

一、介绍

阿里巴巴开源的分布式系统流控工具,以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性,提供丰富的应用场景:消息削峰填谷、集群流量控制、实时熔断下游不可用应用等, 以及完备的实时监控:Sentinel 同时提供实时的监控功能。提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合

官网地址:https://github.com/alibaba/Sentinel/wiki/

sentinel的核心概念:

  • 资源:是 Sentinel 中的核心概念之一,可以是java程序中任何内容,可以是服务或者方法甚至代码,总结起来就是我们要保护的东西

  • 规则:定义怎样的方式保护资源,主要包括流控规则、熔断降级规则等

Sentinel 分为两个部分

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo、Spring Cloud 等框架也有较好的支持。

  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

控制台包含如下功能:

  • 查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。

  • 监控 (单机和集群聚合)通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。

  • 规则管理和推送:统一管理推送规则。

  • 鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制

二、限流控制

原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

流控规则会下发到微服务,微服务如果重启,则流控规则会消失可以持久化配置

两种规则

  • 基于统计并发线程数的流量控制

并发数控制用于保护业务线程池不被慢调用耗尽,Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目)。如果超出阈值,新的请求会被立即拒绝, 效果类似于信号量隔离。

  • 基于统计QPS的流量控制

当 QPS 超过某个阈值的时候,则采取措施进行流量控制

流量控制的效果包括以下几种:

  • 直接拒绝:默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝

  • Warm Up:冷启动/预热,如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值

  • 匀速排队:严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法,主要用于处理间隔性突发的流量,如消息队列,想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求

注意:

  • 匀速排队等待策略是 Leaky Bucket 算法结合虚拟队列等待机制实现的。

  • 匀速排队模式暂时不支持 QPS > 1000 的场景

三、熔断降级

熔断降级(虽然是两个概念,基本都是互相配合)

  • 对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一

  • 对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩

  • 熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置

四、Sentinel 熔断

服务熔断一般有三种状态

  • 熔断关闭(Closed):服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制

  • 熔断开启(Open):后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法

  • 半熔断(Half-Open):所谓半熔断就是尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率

熔断恢复:

  • 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态)尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。

  • 如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断状态

熔断策略

  • 慢调用比例(响应时间): 选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用

    • 比例阈值:修改后不生效-目前已经反馈给官方那边的bug

    • 熔断时长:超过时间后会尝试恢复

    • 最小请求数:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断

  • 异常比例:当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断

    • 比例阈值

    • 熔断时长:超过时间后会尝试恢复

    • 最小请求数:熔断触发的最小请求数,请求数小于该值时,即使异常比率超出阈值也不会熔断

  • 异常数:当单位统计时长内的异常数目超过阈值之后会自动进行熔断

    • 异常数:

    • 熔断时长:超过时间后会尝试恢复

    • 最小请求数:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断

五、依赖

基于cloud-alibaba的依赖

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

六、配置

spring:
  cloud:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080
        port: 9999
#dashboard: 8080 控制台端口
#port: 9999 本地启的端口,随机选个不能被占用的,与dashboard进行数据交互,会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互, 若被占用,则开始+1一次扫描

#开启feign对sentinel的支持
feign:
  sentinel:
    enabled: true

七、降级异常统一处理

  • 【旧版】实现UrlBlockHandler并且重写blocked方法
@Component
public class XdclassUrlBlockHandler implements UrlBlockHandler {
    @Override
    public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
       //降级业务处理
    }
}
  • 【新版】实现BlockExceptionHandler并且重写handle方法
public class XdclassUrlBlockHandler implements BlockExceptionHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
    //降级业务处理
    }
}

例子:

package com.lcy.cloud.common.handler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.alibaba.fastjson.JSON;
import com.lcy.base.common.response.CommonCode;
import com.lcy.base.common.response.Result;
import com.lcy.base.common.util.ResultUtil;

/**
 * @Description sentinel异常
 * @Author lcy
 * @Date 2021/7/30 18:19
 */
@Component
public class SentinelHandler implements BlockExceptionHandler {

    @Override public void handle(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,BlockException e) throws Exception{
        Result<Object> result;
        if (e instanceof FlowException) {
            result = ResultUtil.create("701","限流异常");
        } else if (e instanceof DegradeException) {
            result = ResultUtil.create("702","降级异常");
        } else if (e instanceof ParamFlowException) {
            result = ResultUtil.create("703","热点参数异常");
        } else if (e instanceof SystemBlockException) {
            result = ResultUtil.create("704","系统异常异常");
        } else if (e instanceof AuthorityException) {
            result = ResultUtil.create("705","认证异常");
        } else {
            result = ResultUtil.create(CommonCode.FAIL);
        }

        // 设置返回json数据
        httpServletResponse.setStatus(200);
        httpServletResponse.setHeader("content-Type","application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(result));

    }
}

八、feign降级回调

回调类,这里需要实现feign接口,必须与参数名一致

package com.lcy.cloud.common.fallback;

import com.lcy.cloud.common.api.UserApi;
import com.lcy.cloud.common.model.domain.User;

/**
 * @Description 用户接口回调类
 * @Author lcy
 * @Date 2021/7/30 18:35
 */
public class UserServiceFallback implements UserApi {

    @Override public User getUserById(Long id){
        return new User();
    }
}

feign

package com.lcy.cloud.common.api;

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

import com.lcy.cloud.common.fallback.UserServiceFallback;
import com.lcy.cloud.common.model.domain.User;

/**
 * @Description video feign
 * @Author lcy
 * @Date 2021/7/30 11:46
 */
@FeignClient(value = "cloud-user", fallback = UserServiceFallback.class)
public interface UserApi {

    /**
     * 根据用户id获取用户
     * @param id 用户id
     * @return com.lcy.cloud.common.model.domain.User
     * @author lcy
     * @date 2021/7/30 18:37
     **/
    @GetMapping("getUserById")
    User getUserById(@RequestParam Long id);

}