限流、熔断、降级框架Sentinel使用

标题Sentinel 是什么

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
官网地址
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

简单处理

try{
	//限流
	//http请求消耗线程
	map(URI,线程数) //隔离
	线程池(线程数)
	if (当前线程满了){
    	throw exception
	}
	
	//熔断
	//计数 连续失败次数 达到阈值
	失败 count++;
	if(count == 10){
	  //试试
	  随机请求/定时发请求(半请求)
	  throw exception;
	}

}catch(Exception e){

	1.	避免返回不友好的错误信息
		-> 好看点儿的页面 重试按钮 联系邮箱

	2.	
	return "客官稍后再来";
	
	return 操作写到MQ里,返回成功 发个邮件...
}

配置降级规则

@SysControllerLog(isSaveDB = "true")
@ApiOperation(value = "获取用户基本信息")
@RequestMapping(value = "/basicInfo.do", method = RequestMethod.GET)
@PreAuthorize("hasAuthority('b:PersonalInformationController:basicInfo')")
@SentinelResource(value = "basicInfo")
public ServerResponse<Admin> basicInfo() {
    Long adminId = RequestHolder.getCurrentAdmin().getAdmin().getId();
    return ServerResponse.createBySuccess("查询成功!", adminService.getById(adminId));
}

限流、熔断、降级框架Sentinel使用
出现慢查询后服务被降级
限流、熔断、降级框架Sentinel使用
控制台抛出了异常
限流、熔断、降级框架Sentinel使用

流量控制

Controller层处理异常

@SysControllerLog(isSaveDB = "true")
@ApiOperation(value = "获取用户部门岗位信息")
@RequestMapping(value = "/listDeptJobs.do", method = RequestMethod.GET)
@PreAuthorize("hasAuthority('m:PersonalInformationController:listDeptJobs')")
public ServerResponse<List<DeptVO>> listDeptJobs() {
    Admin admin = RequestHolder.getCurrentAdmin().getAdmin();
    try {
        List<DeptVO> deptVOS = adminJobService.listDeptJobs(admin.getId(), admin.getDefaultEnterpriseId());
        return ServerResponse.createBySuccess("查询成功!", deptVOS);
    }catch (BlockException e){
        return ServerResponse.createByErrorMessage("服务暂时不可用");
    }
}

Service层抛出异常

@Override
@SentinelResource(value = "listDeptJobs")
public List<DeptVO> listDeptJobs(Long adminId, Long enterpriseId) throws BlockException {
    return adminJobMapper.listDeptJobs(adminId, enterpriseId);
}

为了方便测试,限制QPS为5
限流、熔断、降级框架Sentinel使用
限流、熔断、降级框架Sentinel使用
限流、熔断、降级框架Sentinel使用

全局配置

由于控制台的配置会在应用重启后重置,应使用持久化的配置

import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

/**
 * Sentinel切面类配置:
 */
@Configuration
public class SentinelAspectConfig {

    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {

//熔断降级
//        List<DegradeRule> rules = new ArrayList<>();
//        DegradeRule rule = new DegradeRule();
//        //资源名
//        rule.setResource("index");
//        //阈值类型:异常比例
//        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
//        //阈值
//        rule.setCount(200);
//        //比例阀值
//        rule.setSlowRatioThreshold(0.5);
//        //时间窗口为5秒
//        rule.setTimeWindow(5);
//        //最小
//        rule.setMinRequestAmount(1);
//        rules.add(rule);
//        DegradeRuleManager.loadRules(rules);

        //限流
        List<FlowRule> flow = new ArrayList<>();
        FlowRule rule1 = new FlowRule();
        //可以不使用资源名,直接使用路径
        rule1.setResource("/sys/enterpriseAdmin/index.do");
        // QPS控制
        rule1.setCount(100);
        // QPS限流
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule1.setLimitApp("default");
        flow.add(rule1);
        FlowRuleManager.loadRules(flow);

        return new SentinelResourceAspect();
    }
}

配置自定义的异常返回
sentinel自带的返回异常会显得不友好,因此需要进行处理

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.fasterxml.jackson.databind.ObjectMapper;
import com.training.examination.core.common.ServerResponse;
import lombok.Data;
import org.springframework.stereotype.Component;

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

@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws Exception {
        String msg = null;
        if (ex instanceof FlowException) {//限流
            msg = "服务忙,请稍后再试";
        } else if (ex instanceof DegradeException) {//降级
            msg = "服务暂不可用";
        } else if (ex instanceof ParamFlowException) {
            msg = "热点参数限流";
        } else if (ex instanceof SystemBlockException) {
            msg = "系统规则(负载/...不满足要求)";
        } else if (ex instanceof AuthorityException) {
            msg = "授权规则不通过";
        }
        // http状态码
        response.setStatus(500);
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        response.setContentType("application/json;charset=utf-8");
        // spring mvc自带的json操作工具,叫jackson
        new ObjectMapper()
                .writeValue(response.getWriter(),
                        ServerResponse.createByErrorCodeMessage(10000, msg));
    }

    @PostConstruct
    public void init() {
        new CustomBlockExceptionHandler();
    }

    @Data
    static class SentinelErrorMsg {
        private Integer status;
        private String msg;
    }

}

Spring Cloud Gateway网关限流

  1. 参考地址
    https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81
  2. 参考github上官方DEMO,对代码做如下调整
    限流、熔断、降级框架Sentinel使用

配置依赖

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

代码配置

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.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.online.examination.gateway.handler.CustomBlockRequestHandler;
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.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;

import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Sentinel配置
 */
@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(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    @PostConstruct
    public void doInit() {
        initGatewayRules();
    }

    private void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(new GatewayFlowRule("system_route")
                .setCount(200)
                .setIntervalSec(1)
                //流量整形的控制效果,同限流规则的 controlBehavior 字段,目前支持快速失败和匀速排队两种模式,默认是快速失败。
                .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
                //匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。
                .setMaxQueueingTimeoutMs(6000)
        );
        rules.add(new GatewayFlowRule("work_route")
                .setCount(200)
                .setIntervalSec(1)
                //流量整形的控制效果,同限流规则的 controlBehavior 字段,目前支持快速失败和匀速排队两种模式,默认是快速失败。
                .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
                //匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。
                .setMaxQueueingTimeoutMs(6000)
        );
        rules.add(new GatewayFlowRule("external_route")
                .setCount(200)
                .setIntervalSec(1)
                //流量整形的控制效果,同限流规则的 controlBehavior 字段,目前支持快速失败和匀速排队两种模式,默认是快速失败。
                .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
                //匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。
                .setMaxQueueingTimeoutMs(6000)
        );
        GatewayRuleManager.loadRules(rules);
        GatewayCallbackManager.setBlockHandler(new CustomBlockRequestHandler());
    }
}

网关限流规则 GatewayFlowRule 的字段解释如下:
resource:资源名称,可以是网关中的 route 名称或者用户自定义的 API 分组名称。
resourceMode:规则是针对 API Gateway 的 route(RESOURCE_MODE_ROUTE_ID)还是用户在 Sentinel 中定义的 API 分组(RESOURCE_MODE_CUSTOM_API_NAME),默认是 route。
grade:限流指标维度,同限流规则的 grade 字段。
count:限流阈值
intervalSec:统计时间窗口,单位是秒,默认是 1 秒。
controlBehavior:流量整形的控制效果,同限流规则的 controlBehavior 字段,目前支持快速失败和匀速排队两种模式,默认是快速失败。
burst:应对突发请求时额外允许的请求数目。
maxQueueingTimeoutMs:匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。
paramItem:参数限流配置。若不提供,则代表不针对参数进行限流,该网关规则将会被转换成普通流控规则;否则会转换成热点规则。其中的字段:
parseStrategy:从请求中提取参数的策略,目前支持提取来源 IP(PARAM_PARSE_STRATEGY_CLIENT_IP)、Host(PARAM_PARSE_STRATEGY_HOST)、任意 Header(PARAM_PARSE_STRATEGY_HEADER)和任意 URL 参数(PARAM_PARSE_STRATEGY_URL_PARAM)四种模式。
fieldName:若提取策略选择 Header 模式或 URL 参数模式,则需要指定对应的 header 名称或 URL 参数名称。
pattern:参数值的匹配模式,只有匹配该模式的请求属性值会纳入统计和流控;若为空则统计该请求属性的所有值。(1.6.2 版本开始支持)
matchStrategy:参数值的匹配策略,目前支持精确匹配(PARAM_MATCH_STRATEGY_EXACT)、子串匹配(PARAM_MATCH_STRATEGY_CONTAINS)和正则匹配(PARAM_MATCH_STRATEGY_REGEX)。(1.6.2 版本开始支持)
用户可以通过 GatewayRuleManager.loadRules(rules) 手动加载网关规则,或通过 GatewayRuleManager.register2Property(property) 注册动态规则源动态推送(推荐方式)。
自定义返回异常
package com.online.examination.gateway.handler;

import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import static org.springframework.web.reactive.function.BodyInserters.fromObject;

/**
 * 返回自定义限流异常
 */
public class CustomBlockRequestHandler implements BlockRequestHandler {

    private static final String DEFAULT_BLOCK_MSG_PREFIX = "服务忙,请稍后再试";

    @Override
    public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable ex) {

        // 返回http状态码为200
        return ServerResponse.status(10000).contentType(MediaType.APPLICATION_JSON_UTF8)
                .body(fromObject(buildErrorResult(ex)));
    }


    private ErrorResult buildErrorResult(Throwable ex) {
        return new ErrorResult(10000,
                DEFAULT_BLOCK_MSG_PREFIX);
    }


    private static class ErrorResult {
        private final int code;
        private final String message;

        ErrorResult(int code, String message) {
            this.code = code;
            this.message = message;
        }

        public int getCode() {
            return code;
        }

        public String getMessage() {
            return message;
        }
    }

}

并发超过时的结果
限流、熔断、降级框架Sentinel使用

上一篇:CCF-CSP考试使用python编写时的注意点


下一篇:YOLOv4