首页 > 其他分享 >使用Sentinel实现隔离、限流

使用Sentinel实现隔离、限流

时间:2024-02-05 14:45:12浏览次数:27  
标签:String 限流 线程 Sentinel import public 资源 隔离

功能对比

 SentinelHystrixresilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 基于异常比率、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于 RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件的形式 接口的形式
基于注解的支持 支持 支持 支持
限流 基于 QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
流量整形 支持预热模式、匀速器模式、预热排队模式 不支持 简单的 Rate Limiter 模式
系统自适应保护 支持 不支持 不支持
控制台 提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等 简单的监控查看 不提供控制台,可对接其它监控系统

 

Sentinel 基本概念

资源

资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。

只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

规则

围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

流量控制

流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状。

流量控制有以下几个角度:

  • 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
  • 运行指标,例如 QPS、线程池、系统负载等;
  • 控制的效果,例如直接限流、冷启动、排队等。

Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。

熔断降级

什么是熔断降级

除了流量控制以外,降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。这个问题和 Hystrix 里面描述的问题是一样的。

image

Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。

熔断降级设计理念

在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。

Hystrix 通过线程池的方式,来对依赖(在我们的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配。

Sentinel 对这个问题采取了两种手段:

  • 通过并发线程数进行限制

和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。

  • 通过响应时间对资源进行降级

除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。

 

实战

假如我们有一个高并发场景,需要做抢购秒杀,需要获取用户资料,服务类的接口需要限流保护。

实体类

复制代码
package com.xin.sentinel.demo.entity;

public class User {
    int id;
    String name;
    int age;
    int level;
    String address;
    String password;
    String phone;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public User(String name) {
        this.id = -1;
        this.name = name;
    }

    public User() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", level=" + level +
                ", address='" + address + '\'' +
                ", password='" + password + '\'' +
                ", phone='" + phone + '\'' +
                '}';
    }
}
复制代码

服务类

复制代码
package com.xin.sentinel.demo.service;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
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.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.xin.sentinel.demo.entity.User;
import com.xin.sentinel.demo.dao.DB;
import org.springframework.stereotype.Service;

import java.util.Collections;

@Service
public class UserService {

    public static final String USER_RES = "userResource";

    public UserService(){
        // 定义热点限流的规则,对第一个参数设置 qps 限流模式,阈值为5
        FlowRule rule = new FlowRule();
        rule.setResource(USER_RES);
        // 限流类型,qps
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 设置阈值
        rule.setCount(4);
        // 限制哪个调用方
        rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
        // 基于调用关系的流量控制
        rule.setStrategy(RuleConstant.STRATEGY_DIRECT);
        // 流控策略
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        FlowRuleManager.loadRules(Collections.singletonList(rule));
    }

    /**
     * SphU 包含了 try-catch 风格的 API。用这种方式,当资源发生了限流之后会抛出 BlockException。
     * 这个时候可以捕捉异常,进行限流之后的逻辑处理.
     * @param uid
     * @return
     */
    public User getUser(int uid){
        Entry entry = null;
        // 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
        try {
            // 流控代码
            entry = SphU.entry(USER_RES);
            // 业务代码
            User user = new User();
            user.setId(uid);
            user.setName("user-" + uid);
            DB.InsertUser(user); //长耗时的工作
            return user;
        }catch(BlockException e){
            // 被限流了
            System.out.println("[getUser] has been protected! Time="+System.currentTimeMillis());
        }finally {
            if(entry!=null){
                entry.exit();
            }
        }
        return null;
    }


    /**
     * 通过 @SentinelResource 注解定义资源并配置 blockHandler 和 fallback 函数来进行限流之后的处理
     * @param id
     * @return
     */
    @SentinelResource(blockHandler = "blockHandlerForGetUser")
    public User getUserById(String id) {
        throw new RuntimeException("getUserById command failed");
    }

    // blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
    public User blockHandlerForGetUser(String id, BlockException ex) {
        return new User("admin");
    }


}
复制代码

控制器

复制代码
package com.xin.sentinel.demo.controller;

import com.xin.sentinel.demo.entity.User;
import com.xin.sentinel.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class Demo {

    @Autowired
    private UserService userService;
    /**
     * 获取用户信息
     */
    @GetMapping("/getUser")
    public @ResponseBody User getUser(@RequestParam("id") int id) {
        return userService.getUser(id);
    }


}
复制代码

 

流量规则的定义

重要属性:

Field说明默认值
resource 资源名,资源名是限流规则的作用对象  
count 限流阈值  
grade 限流阈值类型,QPS 或线程数模式 QPS 模式
limitApp 流控针对的调用来源 default,代表不区分调用来源
strategy 调用关系限流策略:直接、链路、关联 根据资源本身(直接)
controlBehavior 流控效果(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流 直接拒绝

同一个资源可以同时有多个限流规则。

通过代码定义流量控制规则

理解上面规则的定义之后,我们可以通过调用 FlowRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则,比如:

复制代码
private static void initFlowQpsRule() {
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule1 = new FlowRule();
    rule1.setResource(resource);
    // Set max qps to 20
    rule1.setCount(20);
    rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule1.setLimitApp("default");
    rules.add(rule1);
    FlowRuleManager.loadRules(rules);
}
复制代码

流程图

运行上面的demo,还有日志输出,目录类似:C:\Users\Administrator\logs\csp

1589891884000|2020-05-19 20:38:04|userResource|1|0|1|0|13|0|0|0

含义分别是:

流量控制主要有两种统计类型,一种是统计线程数,另外一种则是统计 QPS。类型由 FlowRule.grade 字段来定义。其中,0 代表根据并发数量来限流,1 代表根据 QPS 来进行流量控制。

线程控制隔离

线程数限流用于保护业务线程数不被耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对高线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离),或者使用信号量来控制同时请求的个数(信号量隔离)。这种隔离方案虽然能够控制线程数量,但无法控制请求排队时间。当请求过多时排队也是无益的,直接拒绝能够迅速降低系统压力。Sentinel线程数限流不负责创建和管理线程池(对,说的就是hystrix),而是简单统计当前请求上下文的线程个数,如果超出阈值,新的请求会被立即拒绝。

线程隔离的例子

public class FlowThreadDemo {

    private static AtomicInteger pass = new AtomicInteger();
    private static AtomicInteger block = new AtomicInteger();
    private static AtomicInteger total = new AtomicInteger();
    private static AtomicInteger activeThread = new AtomicInteger();

    private static volatile boolean stop = false;
    private static final int threadCount = 100;

    private static int seconds = 60 + 40;
    private static volatile int methodBRunningTime = 2000;

    public static void main(String[] args) throws Exception {
        System.out.println(
            "MethodA will call methodB. After running for a while, methodB becomes fast, "
                + "which make methodA also become fast ");
        tick();
        initFlowRule();

        for (int i = 0; i < threadCount; i++) {
            Thread entryThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        Entry methodA = null;
                        try {
                            TimeUnit.MILLISECONDS.sleep(5);
                            methodA = SphU.entry("methodA");
                            activeThread.incrementAndGet();
                            Entry methodB = SphU.entry("methodB");
                            TimeUnit.MILLISECONDS.sleep(methodBRunningTime);
                            methodB.exit();
                            pass.addAndGet(1);
                        } catch (BlockException e1) {
                            block.incrementAndGet();
                        } catch (Exception e2) {
                            // biz exception
                        } finally {
                            total.incrementAndGet();
                            if (methodA != null) {
                                methodA.exit();
                                activeThread.decrementAndGet();
                            }
                        }
                    }
                }
            });
            entryThread.setName("working thread");
            entryThread.start();
        }
    }

    private static void initFlowRule() {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule1 = new FlowRule();
        rule1.setResource("methodA");
        // set limit concurrent thread for 'methodA' to 20
        rule1.setCount(20);
        rule1.setGrade(RuleConstant.FLOW_GRADE_THREAD);
        rule1.setLimitApp("default");

        rules.add(rule1);
        FlowRuleManager.loadRules(rules);
    }

    private static void tick() {
        Thread timer = new Thread(new TimerTask());
        timer.setName("sentinel-timer-task");
        timer.start();
    }

    static class TimerTask implements Runnable {

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            System.out.println("begin to statistic!!!");

            long oldTotal = 0;
            long oldPass = 0;
            long oldBlock = 0;

            while (!stop) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                }
                long globalTotal = total.get();
                long oneSecondTotal = globalTotal - oldTotal;
                oldTotal = globalTotal;

                long globalPass = pass.get();
                long oneSecondPass = globalPass - oldPass;
                oldPass = globalPass;

                long globalBlock = block.get();
                long oneSecondBlock = globalBlock - oldBlock;
                oldBlock = globalBlock;

                System.out.println(seconds + " total qps is: " + oneSecondTotal);
                System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal
                    + ", pass:" + oneSecondPass
                    + ", block:" + oneSecondBlock
                    + " activeThread:" + activeThread.get());
                if (seconds-- <= 0) {
                    stop = true;
                }
                if (seconds == 40) {
                    System.out.println("method B is running much faster; more requests are allowed to pass");
                    methodBRunningTime = 20;
                }
            }

            long cost = System.currentTimeMillis() - start;
            System.out.println("time cost: " + cost + " ms");
            System.out.println("total:" + total.get() + ", pass:" + pass.get()
                + ", block:" + block.get());
            System.exit(0);
        }
    }
}

QPS隔离

当 QPS 超过某个阈值的时候,则采取措施进行流量控制。流量控制的手段包括下面 3 种,对应 FlowRule 中的 controlBehavior 字段:

  1. 直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式。该方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。具体的例子参见 FlowqpsDemo

  2. 冷启动(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式。该方式主要用于系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮的情况。具体的例子参见 WarmUpFlowDemo

  1. 匀速器(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式。这种方式严格控制了请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法

 参考链接 https://www.cnblogs.com/starcrm/p/12919456.html

标签:String,限流,线程,Sentinel,import,public,资源,隔离
From: https://www.cnblogs.com/JavaYuYin/p/18007948

相关文章

  • 面试官:Sentinel是如何实现限流的?
    限流是一种通过控制系统对外提供的资源、服务或接口的访问数量或速率,以保护系统免受过载的一种策略。它的目的是确保系统能够在承受范围内提供稳定和可靠的服务,避免因过多的请求而导致系统崩溃、资源耗尽或响应延迟过高的情况发生。在Sentinel中,实现限流的方法有以下两种:......
  • 【学习笔记】Python 环境隔离
    目录前言venvvenv环境管理venv包管理virtualenv以及virtualenvwrapper安装virtualenvwrapper环境管理virtualenvwrapper包管理condaconda环境管理conda包管理总结参考资料Python作为最常用的脚本语言,有着非常丰富的第三方库,但是这也导致了Python的环境管理非常必要。......
  • 论虚拟机部署和容器化部署的隔离性区别
    虚拟机部署的隔离性相对容器化部署要好的原因主要涉及到虚拟机和容器技术的底层实现方式。完全隔离:虚拟机:虚拟机通过虚拟化技术模拟整个操作系统,每个虚拟机都有自己的内核、文件系统和网络栈。这意味着虚拟机之间的隔离性非常高,一个虚拟机的问题不会影响其他虚拟机。容器:容器......
  • 【GEE】基于GEE可视化和下载Sentinel2 L2A数据(去云、镶嵌、裁剪、筛选波段)
    ​    今天连续发了4篇关于Landsat8数据的下载代码,本来都不想再发GEE这个专栏的文章了,但是又想了想都快过年了,赶紧把手里的代码余货都分享出去吧,省的心里有压力。本篇文章主要分享了GEE可视化和下载Sentinel2L2A数据。    代码主要包含了时间、云量筛选数据......
  • Docker 与 Linux Cgroups:资源隔离的魔法之旅
    这篇文章主要介绍了Docker如何利用Linux的ControlGroups(cgroups)实现容器的资源隔离和管理。最后通过简单Demo演示了如何使用Go和cgroups交互。<!--more-->如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。搜索公众号【探索云原......
  • 【21.0】MySQL进阶知识之事务隔离机制
    【一】数据库读现象的本质是数据库在高并发场景下多个同时执行的事务带来的影响。【二】数据库三大读现象在数据库中,不同的事务隔离级别可能会导致脏读(DirtyRead)、不可重复读(Non-repeatableRead)和幻读(PhantomRead)等问题的出现。【1】脏读(1)概述事务1和事务2并发执行......
  • Redis-哨兵(sentinel)
    Redis-哨兵(sentinel)说明吹哨人巡查监控后台master主机是否故障,如果故障了则根据投票数自动将某一个从库转换为新主库,继续对外服务。配置哨兵前置条件:开启三台虚拟机。架构:每台虚拟机各启动一个redis服务以及各1个redis哨兵首先配置1主2从的redis关系修改redis.conf配置文......
  • 小红书被限流的十大原因,该怎么解决?
    1.笔记中出现引流内容:如果笔记中出现了二薇码、薇心号、或者其它平台的引流信息,那么很大程度被系统判定为广告、站外引流,从而被限流。2.私信引流:及其不推荐,如果在私信中出现引流信息,系统很容易识别到,一旦核实无误,将对其账号进行禁言处理,严重还会限流或者封号。3.频繁删除或者隐藏笔......
  • 事务的隔离级别及脏读,不可重复读,幻读等问题
    事务隔离级别以及对应的问题如上所示。读未提交:在修改数据时在没有提交时就修改了数据库,如果修改回滚则又修改为原值,这样的话在修改与回滚之间读取的数据就是不准确的,会产生脏读现象。脏读现象是读取到未修改的数据,即是数据逻辑上不存在的数据(因为回滚未提交),而下面产生的问题均......
  • client-go令牌桶限流配置
    funcaddListWatchCfgAndClient(){ cfg,err:=clientcmd.BuildConfigFromFlags("","/root/.kube/config") iferr!=nil{ klog.Fatalf("Errorbuildingkubeconfig:%s",err.Error()) } cfg.QPS=5 cfg.Burst=10 kubeClient......