三、spring cloud alibaba学习(Sentinel)

筅森魡賤 提交于 2020-02-08 01:15:21

 

雪崩效应

也叫级联故障,基础服务故障导致上层服务故障,并不断放大的过程。

例如C-->B-->A,A挂了,B调用A超时,B的线程有限,满了以后导致B也挂了,逐渐导致C及后面的也挂了,就像滚雪球一样,最终可能导致整个服务都挂了。

常见容错方案

超时

设置比较短的超时时间,比如给B服务设置1秒的超时时间,1秒以后不管调用成不成功,线程都会被释放。

限流

比如服务B最大限流的值是1000,那给B设置800,达到阈值后,后面的请求服务直接拒绝。

仓壁模式

每个controller都有自己的线程池,这个controller线程满了,拒绝服务了,并不会影响其他controller的服务。

断路器模式

监控每个api的状态,例如5秒钟错误次数或错误率到达一个值,那就认定这个服务出了问题,断路器打开。断路器有一个半开状态,隔一段时间断路器会切位半开状态,调用一下挂掉的服务,如果服务可以被调用,则说明服务恢复,断路器关闭。如果不能被调用,说明服务没有恢复,断路器仍处于打开状态。

Sentinel

轻量级的流量控制、熔断级java库。说白了就是个容错的库

加依赖

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

没有注解没有配置

加配置可访问  localhost:端口/actuator/sentinel  端点,查看sentinel

management:
  endpoints:
    web:
      exposure:
        include: '*'

搭建sentinel控制台

sentinel控制台是官方提供的可视化界面

下载地址:https://github.com/alibaba/Sentinel/releases

版本注意:应该跟应用的sentinel的版本相同

java -jar安装即可

给应用整合控制台

写配置

spring:
  cloud:
    sentinel:
      transport:
        #指定sentinel控制台的地址
        dashboard: localhost:8080

注意:sentinel是懒加载的,所以需访问请求才可以在控制台上看到

Sentinel流控规则

在控制台簇点链路即可添加设置

sentient的流控规则包含:

直接、关联、链路

直接:直接流控,根据设置的qps或线程数直接限制访问

关联:当关联的资源达到阈值的时候,限流自己。使用场景:有两个接口,一个读,一个改,哪个过快都会影响另一个,所以设置合适的阈值,才可以使两个接口达到平衡状态。

链路:指定资源从入口资源进来的流量达到阈值就限流。比如有ab两个接口,都调用common服务,在簇点链路a下的common设置流控规则,那只会限制a的流量,而对b服务调用common没有影响。

流控效果

快速失败:就是直接抛异常

Warm up:根据codeFactor(默认3)的值,从阈值除以codeFactor,所以刚开始的阈值是设置阈值的三分之一,经过预热时长,才打到设置的qps阈值。说白了就是阈值从一个较小的数慢慢增加。

排队等待:匀速排队,让请求以均匀的速度通过,阈值类型必须设成qps,否则无效。qps的阈值即为每秒钟允许的访问数,超过的就排队,直到超时时间到了,就过期。适用于应对突发大量请求的场景。

Sentinel降级规则

所谓降级,即断路器模式

RT:平均响应时间。例如设置成1,时间窗口为5,即每秒钟请求超过1毫秒,且5秒内请求大于等于5次,断路器打开,5秒结束,断路器关闭。注意:RT最大是4900毫秒

 

 

中间浏览器卡了保存失败,后面再补吧,难受

 

 

RestTemplate整合Sentinel

在启动类下的初始化RestTemplate类上加注解@SentinelRestTemplate即可

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

可以用配置关闭注解

resttemplate:
  sentinel:
    enabled: false

注意:有空看一下SentinelRestTemplate类

feign整合sentinel

添加配置即可

feign:
  sentinel:
    enabled: true

如果feign限流、降级发生时,想自己定制自己的逻辑:

新增一个类实现feignclient接口,并写想实现的代码

@Component
public class UserCenterFeignClientFallback implements UserCenterFeignClient{
    @Override
    public UserDTO findById(Integer id){
        UserDTO userDTO = new UserDTO();
        userDTO.setWxNickname("一个默认用户");
        return userDTO;
    }
}

在接口注解上加fallback

@FeignClient(name = "user-center", fallback = UserCenterFeignClientFallback.class)
public interface UserCenterFeignClient{
    //当findById被调用的时候,feign就会构建出http://user-center/users/{id}并请求
    @GetMapping("/users/{id}")
    UserDTO findById(@PathVariable Integer id);
}

这时,当被限流或者降级时,就会自动跳入UserCenterFeignClientFallback这个类。

但如果想打印异常,不能用Fallback了,要用FallbackFactory

@Component
public class UserCenterFeignClientFallbackFactory implements FallbackFactory<UserCenterFeignClient> {
    @Override
    public UserCenterFeignClient create(Throwable cause){
        return new UserCenterFeignClient(){
            @Override
            public UserDTO findById(Integer id){
                log.warn("远程调用被限流/降级了", cause);
                UserDTO userDTO = new UserDTO();
                userDTO.setWxNickname("一个默认用户");
                return userDTO;
            }
        }
    }
}

UserCenterFeignClient接口的注解要改为fallbackFactory

@FeignClient(name = "user-center", 
    fallbackFactory = UserCenterFeignClientFallbackFactory.class)
public interface UserCenterFeignClient{
    //当findById被调用的时候,feign就会构建出http://user-center/users/{id}并请求
    @GetMapping("/users/{id}")
    UserDTO findById(@PathVariable Integer id);
}

sentinel的使用方式

规则持久化

目前为止,每次服务重启,规则都要重新配置,如何让规则不丢失?

拉模式

控制台生成规则后,推给微服务,微服务将规则更新至文件,sentinel会有一个定时任务,定时读取规则文件并更新到规则缓存中。

加依赖

<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-datasource-extension</artifactId>
</dependency>

写代码

/**
 * 拉模式规则持久化
 *
 * @author itmuch.com
 */
public class FileDataSourceInit implements InitFunc {
    @Override
    public void init() throws Exception {
        //每个规则写到一个json文件中
        // TIPS: 如果你对这个路径不喜欢,可修改为你喜欢的路径
        String ruleDir = System.getProperty("user.home") + "/sentinel/rules";
        String flowRulePath = ruleDir + "/flow-rule.json";
        String degradeRulePath = ruleDir + "/degrade-rule.json";
        String systemRulePath = ruleDir + "/system-rule.json";
        String authorityRulePath = ruleDir + "/authority-rule.json";
        String paramFlowRulePath = ruleDir + "/param-flow-rule.json";

        this.mkdirIfNotExits(ruleDir);
        this.createFileIfNotExits(flowRulePath);
        this.createFileIfNotExits(degradeRulePath);
        this.createFileIfNotExits(systemRulePath);
        this.createFileIfNotExits(authorityRulePath);
        this.createFileIfNotExits(paramFlowRulePath);

        // 流控规则 ReadableDataSource为可读数据源,定时读取json文件并更新到缓存中
        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
            flowRulePath,
            flowRuleListParser
        );

        // 将可读数据源注册至FlowRuleManager
        // 这样当规则文件发生变化时,就会更新规则到内存
        FlowRuleManager.register2Property(flowRuleRDS.getProperty());
        WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
            flowRulePath,
            this::encodeJson
        );

        // 将可写数据源注册至transport模块的WritableDataSourceRegistry中
        // 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写入到文件中
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);

        // 降级规则
        ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
            degradeRulePath,
            degradeRuleListParser
        );
        DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
        WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
            degradeRulePath,
            this::encodeJson
        );
        WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);

        // 系统规则
        ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
            systemRulePath,
            systemRuleListParser
        );
        SystemRuleManager.register2Property(systemRuleRDS.getProperty());
        WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
            systemRulePath,
            this::encodeJson
        );
        WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);

        // 授权规则
        ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>(
            authorityRulePath,
            authorityRuleListParser
        );
        AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
        WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>(
            authorityRulePath,
            this::encodeJson
        );
        WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);

        // 热点参数规则
        ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
            paramFlowRulePath,
            paramFlowRuleListParser
        );
        ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
        WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
            paramFlowRulePath,
            this::encodeJson
        );
        ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
    }

    private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(
        source,
        new TypeReference<List<FlowRule>>() {
        }
    );
    private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(
        source,
        new TypeReference<List<DegradeRule>>() {
        }
    );
    private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(
        source,
        new TypeReference<List<SystemRule>>() {
        }
    );

    private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(
        source,
        new TypeReference<List<AuthorityRule>>() {
        }
    );

    private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(
        source,
        new TypeReference<List<ParamFlowRule>>() {
        }
    );

    private void mkdirIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

    private void createFileIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.createNewFile();
        }
    }

    private <T> String encodeJson(T t) {
        return JSON.toJSONString(t);
    }
}

在项目的resources/META-INF/services目录下创建文件,名为com.alibaba.csp.sentinel.init.InitFunc的类,内容为

# 改成上面FileDataSourceInit的包名类名全路径即可。
com.haozi.contentcenter.FileDataSourceInit

优点:简单易懂、没有多余依赖(配置、缓存等)

缺点:因为是定时加载,所以更新会有延迟,且因为存在本地文件,所以服务器迁移,相关文件也要迁移走

推模式

控制台发送规则到nacos中,微服务监听nacos上的服务

加依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

加配置

spring:
  cloud:
    sentinel:
      datasource:
        # 名称随意
        flow:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-flow-rules
            groupId: SENTINEL_GROUP
            # 规则类型,取值见:
            # org.springframework.cloud.alibaba.sentinel.datasource.RuleType
            rule-type: flow
        degrade:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-degrade-rules
            groupId: SENTINEL_GROUP
            rule-type: degrade
        system:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-system-rules
            groupId: SENTINEL_GROUP
            rule-type: system
        authority:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-authority-rules
            groupId: SENTINEL_GROUP
            rule-type: authority
        param-flow:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-param-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: param-flow

控制台改造

1.修改pom.xml,找到

<!-- for Nacos rule publisher sample -->
  <dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <scope>test</scope>
  </dependency>

将<scope>test</scope>注释掉

2.找到 sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos目录,将整个目录拷贝到 sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/nacos

3.修改 com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2 ,找到

@Autowired
@Qualifier("flowRuleDefaultProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleDefaultPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

修改为

@Autowired
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

4.修改 sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html,找到:
 

<!--<li ui-sref-active="active">-->
  <!--<a ui-sref="dashboard.flow({app: entry.app})">-->
    <!--<i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则 V1</a>-->
<!--</li>-->

把注释都解开

编译、启动

执行 mvn clean package -DskipTests
在项目的 target 目录找到sentinel-dashboard.jar ,执行 java -jar sentinel-dashboard.jar 启动控制台。

  • 测试1:用Sentinel控制台【菜单栏的 流控规则 V1 】推送流控规则,规则会存储到Nacos;
  • 测试2:直接在Nacos上修改流控规则,然后刷新Sentinel控制台,控制台上的显示也会被修改;
  • 测试3:重启Sentinel控制台,并重启微服务;刷新控制台,可以发现规则依然存在。

以上,其实只实现了流控规则的持久化。Sentinel有若干种规则,例如降级规则、系统规则、授权规则、热点规则等,都需要使用类似的方式,修改 com.alibaba.csp.sentinel.dashboard.controller 包中对应的Controller,才能实现持久化。

优点:一致性好、性能优秀

缺点:改动多且麻烦、引入额外的依赖

以上完全不要脸的在copy http://www.imooc.com/article/289464

集群流控

每个微服务需要集成Token Client,并与Token Server通信,Token Server得到当前qps,根据规则判断是否限流。

然而当前无法用到生产环境,所以待定。

sentinel的错误页优化

如果关闭sentinel对spring mvc端点的保护,限流或降级会爆同样的错误页面,无法区分具体是哪种,关闭方法:

spring:
  cloud:
    sentinel:
      filter:
        enabled: true

sentinel提供了一个处理接口URLBlockHandler,在这个接口中可以对异常做处理

写代码

@Component
public class MyUrlBlockHandler implements UrlBlockHandler{
    @Override
    public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException{

        ErrorMsg msg = null;
        
        //不同的异常可以做不同的事情
        //如果是限流异常
        if(ex instanceof FlowException){
            msg = ErrorMsg.builder()
                .status(100)
                .msg("限流了")
                .build();
        }
        //降级异常
        else if(ex instanceof DegrandeException){
             msg = ErrorMsg.builder()
                .status(100)
                .msg("降级了")
                .build();
        }
        //参数热点异常
        else if(ex instanceof ParamFlowException){
             msg = ErrorMsg.builder()
                .status(100)
                .msg("热点参数限流")
                .build();
        }
        //系统异常
        else if(ex instanceof SystemBlockException){
             msg = ErrorMsg.builder()
                .status(100)
                .msg("系统规则(负载、...)不满足规则")
                .build();
        }
        //授权异常
        else if(ex instanceof AuthorityException){
             msg = ErrorMsg.builder()
                .status(100)
                .msg("授权规则不通过")
                .build();
        }
        response.setStatus(500);
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        response.setContentType("application/json;charset=utf-8");
        new ObjectMapper()
            .writeValue(response.getWriter(),msg);
    }
}

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class ErrorMsg{
    private Ingeger status;
    private String msg;
}

sentinel区分来源

在控制台的流控中有针对来源,授权规则的流控应用也是需要区分来源才支持。sentinel提供了处理来源的RequestOriginParser接口

写代码

@Component
public class MyRequestOriginParser implements RequestOriginParser{
    @Override
    public String parseOrigin(HttpServletRequest request){
        //在生产环境中,可以把origin放在header中,url更加好看
        //比如想实现origin参数必传,不传就报错
        //从请求参数中获取名为origin的参数并返回
        //如果获取不到,就抛异常
        String origin = request.getParameter("origin");
        if(StringUtils.isBlank(origin)){
            throw new IllegalArgumentException("origin参数必须被指定");
        }
        return origin;
    }
}

sentinel支持RESTful的url

假如有/shares/1,/shares/2...多个url,想让他们实现同一种流控规则,一个一个配太麻烦。name需要让他们的资源名称相同,sentinel提供了UrlCleaner接口

写代码

@Component
public class MyUrlCleaner implements UrlCleaner{
    @Override
    public String clean(String originUrl){
        //让/shares/1 /shares/2的返回值相同,返回/shares/{number}
        String[] split = originUrl.split("/");
        return Arrays.stream(split)
            .map(string -> {
                if(NumberUtils.isNumber(string)){
                    return "{number}";
                }
                return string;
            })
            .reduce((a , b) -> a + "/" + b)
            .orElse("");
    }
}

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!