转载

Spring Cloud Alibaba-Sentinel(十四)

A lightweight powerful flow control component enabling reliability and monitoring for microservices. (轻量级的流量控制、熔断降级 Java 库). 中文文档

雪崩效应

雪崩效应又称cascading failure(级联故障),指基础服务故障导致上层服务故障并且故障像雪球一样越滚越大。

常见容错方案

  • 超时
  • 限流
  • 舱壁模式:每个controller独立配置线程池,互不干扰。
  • 断路器模式:服务低于阈值(错误次数/错误率等构成),断路器打开,过了一段时间后允许一个服务调用,如果成功,断路器恢复。

整合Sentinel

  • 加依赖
<!--sentinel-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sentinel</artifactId>
        </dependency>
复制代码
  • 写注解
无
复制代码
  • 写配置
无
复制代码

actuator/sentinel节点

打开所有端点查看是否生效

# actuator
management:
  endpoints:
    web:
      exposure:
        # 生产不能全部放开且需配置安全措施
        include: '*'
复制代码

出现以下内容证明整合成功了

Spring Cloud Alibaba-Sentinel(十四)

Sentinel控制台

下载我们pom文件定义的相同版本的Sentinel。 下载地址

#启动
java -jar XXX.jar
复制代码
  • 访问登陆界面http://localhost:8080/#/login
Spring Cloud Alibaba-Sentinel(十四)
  • 默认账号密码sentinel/sentinel
  • 项目整合
spring:
  cloud:
    sentinel:
      transport:
        # 指定sentinel控制台地址
        dashboard: localhost:8080
复制代码
  • 懒加载 由于Sentinel默认是懒加载的,所以请求任意一个接口后才会又视图
Spring Cloud Alibaba-Sentinel(十四)

功能说明

  • 流控规则

    • 流控模式
      • 直接

      • 关联:关联资源达到阈值,限流自己

      • 链路:指定链路上的流量

        • 代码
        // 定义一个common服务
        package com.virgo.user.service;
        
        import com.virgo.entity.TblCar;
        
        /**
         * @author zhaozha
         * @date 2019/10/15 下午4:27
         */
        public interface CommonService {
            TblCar common();
        }
        
        package com.virgo.user.service;
        
        import com.alibaba.csp.sentinel.annotation.SentinelResource;
        import com.virgo.entity.TblCar;
        import org.springframework.stereotype.Service;
        
        /**
         * @author zhaozha
         * @date 2019/10/15 下午4:28
         */
        @Service
        public class CommonServiceImpl implements CommonService {
        
            @Override
            @SentinelResource("common")
            public TblCar common() {
                return TblCar.builder().id(1L).build();
            }
        }
        
        // controller层同时调用这个服务
            @GetMapping("/test/a")
        public String testA() {
            commonServiceImpl.common();
            return "testA";
        }
        
        @GetMapping("/test/b")
        public String testB() {
            commonServiceImpl.common();
            return "testB";
        }
        
        复制代码
        • 设置:只对/test/a限流,对/test/b无影响
        Spring Cloud Alibaba-Sentinel(十四)
    • 流控效果
      • 快速失败
        • 直接失败,抛出异常
        • 源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
      • Warm Up
        • 初始为阈值/codeFactor(默认为3),经过预热时长,才达到阈值
        • 官方文档
        • 源码:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
      • 排队等待
        • 匀速排队,阈值必须设置为QPS
        • 官方文档
        • 源码:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
  • 降级规则

    • 降级策略
      • RT(平均响应时间,秒级别)
        • 平均响应时间超出阈值且在时间窗口内通过的请求>=5
        • 窗口期过后关闭断路器
        • RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
      • 异常比列(秒级别)
      • 异常数(分钟级别)
  • 热点规则

    • 参数级别的流控规则
    • 可以对指定参数/指定参数的指定值限流
    • 需要带 @SentinelResource
  • 系统规则

    • LOAD
      • 当系统load1(1分钟的load)超过阀值,且并发线程数超过系统容量时触发,建议设置为cpu核心数*2(Linux/Unix-like机器生效)
      • 系统容量=maxQps(秒级最大QPS)*minRt(秒级最小响应时间)
      • uptime命令-> 1 5 15(系统平均负载/load)
    • RT
      • 所有入口流量的平均RT达到阈值
    • 线程数
      • 所有入口流量的并发线程数达到阈值
    • 入口QPS
      • 所有入口流量的QPS达到阈值
  • 授权规则

    • 对访问的微服务设置黑白名单

微服务和控制台通信原理

微服务注册/心跳到控制台,控制台通过api获取/推送消息

yml配置

spring:
  cloud:
    sentinel:
      transport:
        控制台地址
        dashboard:
        和控制台通信的ip
        client-ip:
        和控制通信端口(默认8719,如果已经占用,+1直到找到)
        port:
        心跳毫秒
        heartbeat-interval-ms:
复制代码

控制台启动参数

配置项 默认值 描述
server.port 8080 指定端口
csp.sentinel.dashboard.server localhost:8080 指定地址
project.name - -
sentinel.dashboard.auth.username[1.6] sentinel Dashboard登陆账号
sentinel.dashboard.auth.password[1.6] sentinel Dashboard登陆密码
server.servlet.session.timeout[1.6] 30分钟 登陆session过期时间(7200:7200秒/60m:60分钟)

API(可以保护任意资源)

@GetMapping("/test/sentinel/api")
    public String testSentinelApi(@RequestParam(required = false) String a) {
        String resourceName = "test-sentinel-api";
        ContextUtil.enter(resourceName, "test");
        Entry entry = null;
        try {
            entry = SphU.entry(resourceName);
            if (StringUtils.isBlank(a)) {
                throw new IllegalArgumentException("参数不可为空");
            }
            return a;
        } catch (BlockException e) {
            return "限流/降级了";
        } catch (IllegalArgumentException e) {
            Tracer.trace(e);
            return "参数非法";
        } finally {
            if (entry != null) {
                entry.exit();
            }
            ContextUtil.exit();
        }
    }
复制代码

@SentinelResource

通过@SentinelResource简化代码

package com.virgo.user.sentinel;

    import com.alibaba.csp.sentinel.slots.block.BlockException;
    
    /**
     * @author zhaozha
     * @date 2019/10/16 下午3:11
     */
    public class ControllerBlockHandlerClass {
        /**
         * 处理限流/降级
         *
         * @param a
         * @param e
         * @return
         */
        public static String block(String a, BlockException e) {
            return "降级/限流了";
        }
    }

    @GetMapping("/test/sentinel/api")
    @SentinelResource(
            value = "test-sentinel-api",
            blockHandler = "block",
            blockHandlerClass = com.virgo.user.sentinel.ControllerBlockHandlerClass.class
    )
    public String testSentinelApi(@RequestParam(required = false) String a) {
        if (StringUtils.isBlank(a)) {
            throw new IllegalArgumentException("参数不可为空");
        }
        return a;
    }
复制代码

整合RestTemplate

  • 注解
@Bean
    @LoadBalanced
    @SentinelRestTemplate
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
复制代码
  • 开关
resttemplate:
  sentinel:
    enabled: 
复制代码

Feign整合Sentinel

feign:
  #feign整合sentinel
  sentinel
    enable: true
复制代码

自定义返回报文

  • fallback
package com.virgo.user.feignclient;

import com.virgo.entity.TblCar;
import com.virgo.user.configuration.GlobalFeignConfiguration;
import com.virgo.user.feignclient.fallback.LockCenterFeignClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @author zhaozha
 * @date 2019/10/11 下午12:52
 */
@FeignClient(name = "lock-center", configuration = GlobalFeignConfiguration.class,fallback = LockCenterFeignClientFallback.class)
public interface LockCenterFeignClient {

    @GetMapping("/lock/test/{id}")
    TblCar findById(@PathVariable(value="id") Long id);
}


package com.virgo.user.feignclient.fallback;

import com.virgo.entity.TblCar;
import com.virgo.user.feignclient.LockCenterFeignClient;
import org.springframework.stereotype.Component;

/**
 * @author zhaozha
 * @date 2019/10/16 下午3:36
 */
@Component
public class LockCenterFeignClientFallback implements LockCenterFeignClient {
    @Override
    public TblCar findById(Long id) {
        return TblCar.builder().level(1).build();
    }
}
复制代码
  • fallbackFactory(可以捕获异常)
package com.virgo.user.feignclient;

import com.virgo.entity.TblCar;
import com.virgo.user.configuration.GlobalFeignConfiguration;
import com.virgo.user.feignclient.fallbackFactory.LockCenterFeignClientFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @author zhaozha
 * @date 2019/10/11 下午12:52
 */
@FeignClient(name = "lock-center", configuration = GlobalFeignConfiguration.class,fallbackFactory = LockCenterFeignClientFallbackFactory.class)
public interface LockCenterFeignClient {

    @GetMapping("/lock/test/{id}")
    TblCar findById(@PathVariable(value="id") Long id);
}

package com.virgo.user.feignclient.fallbackFactory;

import com.virgo.entity.TblCar;
import com.virgo.user.feignclient.LockCenterFeignClient;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @author zhaozha
 * @date 2019/10/16 下午4:02
 */
@Component
@Slf4j
public class LockCenterFeignClientFallbackFactory implements FallbackFactory<LockCenterFeignClient> {
    @Override
    public LockCenterFeignClient create(Throwable throwable) {
        return new LockCenterFeignClient() {
            @Override
            public TblCar findById(Long id) {
                log.info("限流/降级",throwable);
                return TblCar.builder().level(1).build();
            }
        };
    }
}

复制代码

使用方式

使用方式 使用方式 使用方法
编码方式 API try...catch...finally
注解方式 @SentinelResource blockHandler/fallback
RestTemplate @SentinelRestTemplate blockHandler/fallback
Feign feign.sentinel.enabled fallback/fallbackFactory

配置持久化

  • 阿里云服务(AHAS)

    • 开通地址: ahas.console.aliyun.com
    • 开通说明: help.aliyun.com/document_de…
    Spring Cloud Alibaba-Sentinel(十四)

集群流控

官方文档

优化

  • 错误页优化(UrlBlockHandler)
package com.virgo.user.sentinel;

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
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.virgo.dto.CommonResult;
import org.springframework.stereotype.Component;

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

/**
 * @author zhaozha
 * @date 2019/10/17 上午9:49
 */
@Component
public class VirgoUrlBlockHandler implements UrlBlockHandler {
    @Override
    public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
        // 统一返回对象
        CommonResult commonResult = null;
        // 流控
        if(e instanceof FlowException){
            commonResult = CommonResult.builder().status(100).msg("流控异常").build();
        }
        // 降级
        else if(e instanceof DegradeException){
            commonResult = CommonResult.builder().status(101).msg("降级异常").build();
        }
        // 热点
        else if(e instanceof ParamFlowException){
            commonResult = CommonResult.builder().status(102).msg("热点异常").build();
        }
        // 系统
        else if(e instanceof SystemBlockException){
            commonResult = CommonResult.builder().status(102).msg("系统异常").build();
        }
        // 授权
        else if (e instanceof AuthorityException){
            commonResult = CommonResult.builder().status(102).msg("授权异常").build();
        }

        httpServletResponse.setStatus(500);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
        httpServletResponse.setContentType("application/json;charset=utf-8");
        // spring mvc自带的json操作工具,叫jackson
        new ObjectMapper()
                .writeValue(
                        httpServletResponse.getWriter(),
                        commonResult
                );

    }
}

复制代码
  • 区分来源(RequestOriginParser)
package com.virgo.user.sentinel;

import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * @author zhaozha
 * @date 2019/10/17 上午11:51
 */
@Component
public class MyRequestOriginParser implements RequestOriginParser {

    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        // 从请求参数中获取名为 origin 的参数并返回
        // 如果获取不到origin参数,那么就抛异常
        // todo 改成header
        String origin = httpServletRequest.getParameter("origin");
        if (StringUtils.isBlank(origin)) {
            throw new IllegalArgumentException("origin must be specified");
        }
        return origin;
    }
}

复制代码
  • Restful url支持(UrlCleaner)
package com.virgo.user.sentinel;

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * @author zhaozha
 * @date 2019/10/17 下午12:05
 */
@Component
public class VirgoUrlCleaner implements UrlCleaner {
    @Override
    public String clean(String originUrl) {
        // 让 /shares/1 与 /shares/2 的返回值相同
        // 返回/shares/{number}
        // todo 多参数
        String[] split = originUrl.split("/");

        return Arrays.stream(split)
                .map(string -> {
                    if (NumberUtils.isNumber(string)) {
                        return "{number}";
                    }
                    return string;
                })
                .reduce((a, b) -> a + "/" + b)
                .orElse("");
    }
}

复制代码
原文  https://juejin.im/post/5da5581c518825083d3bad1b
正文到此结束
Loading...