sentinel的使用

本文详细介绍了服务雪崩的原因和解决方案,包括超时机制、服务限流、隔离(线程隔离和信号隔离)、服务熔断与降级。Sentinel作为一款面向分布式服务架构的流量控制组件,提供了丰富的容错机制,如流量控制、熔断降级、系统负载保护等,以保障服务的稳定性。此外,文章还展示了如何在Spring Boot应用中集成Sentinel,并通过Nacos实现规则的持久化推送。
摘要由CSDN通过智能技术生成

前言

在一个高度服务化的系统中,我们实现的一个业务逻辑通常会依赖多个服务, 如图所示:

 如果其中的下单服务不可用, 就会出现线程池里所有线程都因等待响应而被阻塞, 从而造成整个服务链路不可用,  进而导致整个系统的服务雪崩. 如图所示:

服务雪崩效应:因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程,就叫服务雪崩效应

导致服务不可用的原因:

 在服务提供者不可用的时候,会出现大量重试的情况:用户重试、代码逻辑重试,这些重试最终导致:进一步加大请求   流量。所以归根结底导致雪崩效应的最根本原因是:大量请求线程同步等待造成的资源耗尽。当服务调用者使用同步调   用时, 会产生大量的等待线程占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了

解决方案

稳定性、恢复性

常见的容错机制:

        1.超时机制

在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽。加入超时机制,  一旦超时,就释放资源。由于释放资源速度较快,一定程度上可以抑制资源耗尽的问题。

        2.服务限流

         3. 隔离

原理:用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级  处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回友好的提示信息),而不是无休止的等待或者看  到系统崩溃

隔离前:

 

 隔离后:

b)信号隔离:

信号隔离也可以用于限制并发访问,防止阻塞扩散, 与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过信号申请, 如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销。信号量的大小可以动态调整, 线程池大小不可以

        4. 服务熔断

远程服务不稳定或网络抖动时暂时关闭,就叫服务熔断。

现实世界的断路器大家肯定都很了解,断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路  被烧毁。

软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数/失败率达到一定阈值,就跳闸,断路  器打开——此时,请求直接返回,而不去调用原本调用的逻辑。跳闸一段时间后(例如10秒),断路器会进入半开状

态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不  成功,断路器继续回到打开状态,过段时间再进入半开状态尝试——通过跳闸,应用可以保护自己,而且避免浪费资  源;而通过半开的设计,可实现应用的自我修复

所以,同样的道理,当依赖的服务有大量超时时,在让新的请求去访问根本没有意义,只会无畏的消耗现有资源。比如   我们设置了超时时间为1s,如果短时间内有大量请求在1s内都得不到响应,就意味着这个服务出现了异常,此时就没有必  要再让其他的请求去访问这个依赖了,这个时候就应该使用断路器避免资源浪费。

 

服务降级

有服务熔断,必然要有服务降级。

所谓降级,就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的fallback(回退)回调,  返回一个缺省值。 例如:(备用接口/缓存/mock数据)  。这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强,当然这也要看适合的业务场景。

 sentinel 

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel   是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定

性。

源码地址:https://github.com/alibaba/Sentinel

官方文档:https://github.com/alibaba/Sentinel/wiki Sentinel具有以下特征:

丰富的应用场景Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。

完备的实时监控Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据, 甚至 500 台以下规模的集群的汇总运行情况。

广泛的开源生态Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring CloudDubbogRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel

完善的 SPI 扩展点Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。

阿里云提供了 企业级的 Sentinel 服务,应用高可用服务 AHAS

Sentinel和Hystrix对比

Sentinel快速开始

添加依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.1</version>
</dependency>

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>1.8.1</version>
</dependency>

controller层

@SentinelResource注解实现

@SentinelResource 注解用来标识资源是否被限流、降级。

blockHandler: 定义当资源内部发生了BlockException应该进入的方法(捕获的是Sentinel定义的异常) fallback: 定义的是资源内部发生了Throwable应该进入的方法

exceptionsToIgnore:配置fallback可以忽略的异常

package com.example.demo.controller;

import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import jdk.nashorn.internal.runtime.regexp.joni.exception.InternalException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@RestController
public class AnnCtrl {
    private static final String RESOURCE_NAME_HELLO2="h2";
    private static final String RESOURCE_NAME_SAY="say";

    @RequestMapping("/h2")
    @SentinelResource(value = RESOURCE_NAME_HELLO2,fallback = "errorFallbackHandler",blockHandler = "flowRuleExceptionHandler")
    public String h2ann(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int i=1/0;
        return "h2ann";
    }

    /**
     * 接口发生错误 程序走的方法
     * @param e
     * @return
     * 此方法的返回值要与上面接口的返回值一样
     */
    public String errorFallbackHandler(Throwable e){
        return "接口错误";
    }

    /**
     * sentinel 限流后 程序走的方法
     * @param exception
     * @return
     * 此方法的返回值要与上面接口的返回值一样
     */
    public String flowRuleExceptionHandler(BlockException exception){
        exception.printStackTrace();
        return "被流控阻止了";
    }

//    @PostConstruct
//    private void initFlowRule(){
//        List<FlowRule> rules = new ArrayList<FlowRule>();
//        FlowRule qps1 = new FlowRule();
//        qps1.setResource(RESOURCE_NAME_HELLO2);  //这个流控规则关联的资源
//        qps1.setGrade(RuleConstant.FLOW_GRADE_QPS);  //设置这个流控规则等级是 QPS
//        qps1.setCount(1);  //设置QPS的数量单位:次/秒
//        rules.add(qps1);
//        FlowRuleManager.loadRules(rules);  //将规则集合存放到流控规则管理器上 updateValue
//    }

    @RequestMapping("/say")
    @SentinelResource(value = RESOURCE_NAME_SAY,entryType = EntryType.IN,blockHandler = "degHandler")
    public String sayHello() throws InternalException {
        throw new RuntimeException("我是异常。。。");
    }

    public String degHandler(BlockException ex){
        return "熔断降级服务";
    }

    @PostConstruct
    public void degRule(){
        List<DegradeRule> drs = new ArrayList<>();
        DegradeRule dr = new DegradeRule();
        dr.setResource(RESOURCE_NAME_SAY);
        dr.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
        dr.setCount(2);
        dr.setMinRequestAmount(2); //熔断触发的最小请求数,请求数小于该值,即使异常比率超出阈值也不会熔断
        dr.setStatIntervalMs(10*1000); //统计时长
        dr.setTimeWindow(10); //熔断时长
        drs.add(dr);
        DegradeRuleManager.loadRules(drs);
    }
}

上下这两种方法(类)不要同时存在(这里是指同样的限流规则只可以写在一个类里面)

package com.example.demo.controller;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.slots.block.BlockException;
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.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@RestController
public class InitCtrl {
    private static final String RESOURCE_NAME_HELLO="hello";

    @RequestMapping("/hello")
    public String sayHello(){
        Entry entry = null;
        try {
            entry = SphU.entry(RESOURCE_NAME_HELLO);
            return "看见了吗?";
        } catch (BlockException e) {
            return "你的请求被拦截,限流了";
        } catch (Exception e) {
            Tracer.traceEntry(e,entry);
        } finally {
            if(entry!=null){
                entry.exit();
            }
        }
        return null;
    }

    @PostConstruct
    private void initFlowRule(){
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule qps = new FlowRule();
        qps.setResource(RESOURCE_NAME_HELLO);  //这个流控规则关联的资源
        qps.setGrade(RuleConstant.FLOW_GRADE_QPS);  //设置这个流控规则等级是 QPS
        qps.setCount(1);  //设置QPS的数量单位:次/秒
        rules.add(qps);

//        FlowRule qps1 = new FlowRule();
//        qps1.setResource("h2");  //这个流控规则关联的资源
//        qps1.setGrade(RuleConstant.FLOW_GRADE_QPS);  //设置这个流控规则等级是 QPS
//        qps1.setCount(1);  //设置QPS的数量单位:次/秒
//        rules.add(qps1);

        FlowRule qps2 = new FlowRule();
        qps2.setResource("h2");  //这个流控规则关联的资源
        qps2.setGrade(RuleConstant.FLOW_GRADE_THREAD);  //设置这个流控规则等级是 THREAD
        qps2.setLimitApp("default");  //不限定来源
        qps2.setCount(1);  //设置THREAD的数量单位:个/秒  1秒内只有1个线程访问
        rules.add(qps2);

        FlowRuleManager.loadRules(rules);  //将规则集合存放到流控规则管理器上 updateValue
    }
}

yml配置

spring:
  application:
    name: mydemo
server:
  port: 9501

启动类

package com.example.demo;

import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public SentinelResourceAspect sentinelResourceAspect(){
        return new SentinelResourceAspect();
    }
}

缺点:

业务侵入性很强,需要在controller中写入非业务代码.

配置不灵活 若需要添加新的受保护资源 需要手动添加 init方法来添加流控规则

Linux 上启动sentinel控制台

下载控制台 jar 包并在本地启动:可以参见 此处文档

Releases · alibaba/Sentinel · GitHub

将jar包上传Linux上,启动,端口号可以任意起

java -Dserver.port=8858 -Dsentinel.dashboard.auth.username=qjc -Dsentinel.dashboard.auth.password=abc123 -jar /opt/sentinel-dashboard-1.8.1.jar

访问http://ip地址:8858/ ,默认用户名密码: sentinel/sentinel,我这里已改为qjc/abc123

Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包,所以要确保客户端有访问量; 

添加依赖

<sentinel.version>1.8.1</sentinel.version>        


<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>${sentinel.version}</version>
</dependency>
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>${sentinel.version}</version>
</dependency>
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>${sentinel.version}</version>
</dependency>

yml配置

server:
  port: 9100

spring:
  application:
    name: sent_dashboard

controller层

package com.example.demo.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class InitCtrl {
    @RequestMapping("/say")
    @SentinelResource(value = "say",blockHandler = "blockHandler")
    public String sayHello(){
        return "hello";
    }

    public String blockHandler(BlockException ex){
        return "限流";
    }
}

启动前添加sentinel的服务通信

 启动类

package com.example.demo;

import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public SentinelResourceAspect sentinelResourceAspect(){
        return new SentinelResourceAspect();
    }
}

先访问接口,然后再访问刷新sentinel的控制台,可能会很卡,需要多刷新可能才会出来

 但此时这边这个实时监控没有值,无论接口怎么刷都不行

windows启动sentinel控制台

打开cmd命令台,切换D盘

java -Dserver.port=8858 -Dsentinel.dashboard.auth.username=qjc -Dsentinel.dashboard.auth.password=abc123 -jar d:\Download/JavaSoftware/sentinel-dashboard-1.8.1.jar

地址改一下

访问http://localhost:8858/#/login, 登录后

 此时对于接口访问规则的配置就可以在控制台上进行选择操作了,

 但是这种方式也存在一个问题,就是项目与这个控制台的重启,会导致所配规则丢失

sentinel的持久化模式

sentinel规则的推送有下面三种模式:

推送模式

说明

优点

原始模式

API 将规则推送至客户端并直接更新到内存中,扩展写数据源

(WritableDataSource)

简单,无任何依赖

Pull 模式

扩展写数据源

(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文

件 等

简单,无任何依赖;规则持久化

Push 模式

扩展读数据源

(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境

下一般采用 push 模式的数据源。

规则持久化;一致性;快速

这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能

用于生产环境。

拉模式

pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册

数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport

WritableDataSourceRegistry 中。

​​​​​​​推模式

生产环境下一般更常用的是 push 模式的数据源。对于 push 模式的数据源,如远程配置中心

ZooKeeper, Nacos, Apollo等等),推送的操作不应由 Sentinel 客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本

地。因此推送规则正确做法应该是 配置中心控制台/Sentinel 控制台 配置中心 → Sentinel 数据源 → Sentinel,而不是经 Sentinel 数据源推送至配置中心。这样的流程就非常清晰了:

 基于Nacos配置中心控制台实现推送

 添加依赖

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

nacos配置中心中配置流控规则

 yml配置

server:
  port: 8006
spring:
  application:
    name: exp-deg-model
  cloud:
    nacos:
      discovery:
        server-addr: http://192.168.88.190:8850
        username: nacos
        password: nacos
        namespace: public
    sentinel:
      transport:
        dashboard: 127.0.0.1:8858
      datasource:
        flow_demo:
          nacos:
            server-addr: http://192.168.88.190:8850
            username: nacos
            password: nacos
            namespace: public
            dataId: deg-demo #配置中心
            ruleType: flow

规则类型,可从源码看到

 controller层

package com.qjc.expenses.expdeg.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/expdeg")
public class InitCtrl {

    @RequestMapping(value = "/flow",method = RequestMethod.GET)
    @SentinelResource(value = "flow",blockHandler = "bkHander")
    public String sayHello(){
        return "查看数据";
    }

    public String bkHander(BlockException ex){
        return "熔断降级";
    }
}

启动类

package com.qjc.expenses.expdeg;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DegApplication {
    public static void main(String[] args) {
        SpringApplication.run(DegApplication.class,args);
    }
}

然后进入浏览器测试,点击速度 过快时就会出现熔断降级

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值