限流算法深入

这篇具有很好参考价值的文章主要介绍了限流算法深入。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

限流定义及目的

当系统流量达到系统或下游承受能力的阈值时对系统进行限流控制以防止系统或下游挂掉,减少影响面。

限流组成:阈值及限流策略。阈值是指系统单位时间接收到的请求qps总数;限流策略是指限流行业触发后对应的系统行为,如失败拒绝还是放入等待队列。

应用场景

如秒杀业务,商品变更消息等场景。

秒杀业务:通常来说是保护全链路系统不挂;特性是瞬时流量大;超过阈值时拒绝策略通常是返回失败。

商品变更消息:业务场景是需要将商品变更消息推送给下游商家,下游商家系统能力有限,因此需要做限制;这个场景更多是保护下游系统不挂(整条系统链路上最小短板)。

分类

4大类:计数,滑动窗口,漏桶,令牌。

计数(固定时间窗口)

单位时间窗口内进行计数,超过阈值则进行限流。

算法限制:只能限制一整秒流量。若出现第1秒中后500ms和第2秒中前500ms超过阈值则此时不生效。

实现代码

import exception.BlockException;
import java.util.concurrent.atomic.AtomicInteger;
public class CalNumProcessor {
    //限流窗口大小
    private static int WINDOW_TIME_MILL = 1000;
    //阈值
    private static final int LIMIT_NUMS = 10;
    //计数器
    private AtomicInteger count = new AtomicInteger(0);
    //开始时间
    private Long startTime;

    public static void main(String[] args) throws InterruptedException {
        CalNumProcessor calNumProcessor=new CalNumProcessor();
        for(int i=0;i<1000;i++){
            System.out.println(i);
            calNumProcessor.tryIncAndLimit();
            Thread.sleep(200);
        }
    }

    /**
     * 进行限流计数,超过限流阈值时会抛BlockException
     */
    public void tryIncAndLimit() {
        //当前时间
        long curMill = System.currentTimeMillis();
        //开始时间初始化
        if (startTime == null) {
            startTime = curMill;
        }
        if ((curMill - startTime) > WINDOW_TIME_MILL) {
            //若时间计数超过一秒则重置计数为0并重新设置计数开始时间
            count.set(0);
            startTime = curMill;
        }
        int after = count.incrementAndGet();
        if (after > LIMIT_NUMS) {
            //超过阈值
            throw new BlockException();
        }
    }
}

滑动窗口

计数有个问题在于流量放大无法限流。如1秒为计数窗口。则第一秒后半秒和第二秒前半秒累计可能超过qps限制,但由于不是一个时间窗口此时反而不能限制住。

解决思路:每次请求计数每过一个时间窗口单位进行滑动计数。

整体算法思路详细

1.构建n个时间窗口单位,每个窗口单位有对应qps变量及窗口开始时间;初始化时间窗口单位的qps为0及开始时间为当前时间;

2.每次获取qps时计算当前时间应该在哪个时间窗口单位;

3.循环处理时间窗口,如果发现当前时间与时间窗口单位超过1秒(时间窗口单位最大时间)则重置窗口开始时间及qps为0。

4.将当前时间对应时间窗口qps加1;

5.返回所有时间窗口单位qps累计值;

限流算法深入,java,算法,开发语言

代码实现

import java.time.LocalTime;
import java.util.concurrent.atomic.AtomicInteger;

public class TimeWindow {

    /**
     * 时间窗口大小,如1秒
     */
    private int windowTimeSize;

    /**
     * 窗口数
     */
    private int windowNum;

    private Window[] windows;

    private int maxQps;

    public TimeWindow(int maxQps, int windowTimeSize, int windowNum) {
        this.maxQps = maxQps;
        this.windowTimeSize = windowTimeSize;
        this.windowNum = windowNum;
        windows = new Window[windowNum];
        for (int i = 0; i < windowNum; i++) {
            windows[i] = new Window();
            windows[i].setQps(new AtomicInteger(0));
            windows[i].setStartTime(System.currentTimeMillis());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int qps = 2;
        int count = 20;
        int sleep = 300;
        int success = 0;
        TimeWindow timeWindow = new TimeWindow(qps, 1000, 10);
        for (int i = 0; i < count; i++) {
            Thread.sleep(sleep);
            if (timeWindow.tryAcquire()) {
                success++;
                if (success % qps == 0) {
                    System.out.println(LocalTime.now() + ": success, ");
                } else {
                    System.out.print(LocalTime.now() + ": success, ");
                }
            } else {
                System.out.println(LocalTime.now() + ": fail");
            }
        }
        System.out.println();
        System.out.println("实际测试成功次数:" + success);
    }

    public boolean tryAcquire() {
        //计算当前时间落到哪个窗口位置
        int index = (int) (System.currentTimeMillis() % windowTimeSize) / (windowTimeSize / windowNum);
        //当前时间若超过时间累计窗口则重置窗口参数
        int r = 0;
        for (int i = 0; i < windowNum; i++) {
            Window curWindow = windows[i];
            if ((System.currentTimeMillis() - curWindow.getStartTime()) > windowTimeSize) {
                //当前时间减去时间窗口大于最大累计时间窗口大小则重置变量
                curWindow.setQps(new AtomicInteger(0));
                curWindow.setStartTime(System.currentTimeMillis());
            }
            //当前时间对应窗口累计qps
            if (index == i) {
                curWindow.getQps().incrementAndGet();
            }
            r += curWindow.getQps().get();
        }
        return r <= maxQps;
    }

    class Window {
        /**
         * qps
         */
        private AtomicInteger qps;

        /**
         * 开始时间
         */
        private long startTime;

        public AtomicInteger getQps() {
            return qps;
        }

        public void setQps(AtomicInteger qps) {
            this.qps = qps;
        }

        public long getStartTime() {
            return startTime;
        }

        public void setStartTime(long startTime) {
            this.startTime = startTime;
        }
    }
}

 漏桶算法

流出衡定,不能应对突发流量,能较好保护下游。

限流算法深入,java,算法,开发语言

令牌算法

优:能处理突出流量,流入相对衡定,流出允许有波动。秒杀场景适用。

这里核心概念:令牌桶(有令牌数及桶上限2个参数),令牌,获取令牌,存放令牌;

存放令牌策略有:1、有单独线路每秒加入n个令牌(相当于qps为n);2、懒计算:当获取令牌请求到来时进行计算,计算思路:Math.min(当前时间距离上次已存放令牌时间间隔秒数*令牌qps,令牌数上限)。

限流算法深入,java,算法,开发语言

代码

 RateLimiter rateLimiter = RateLimiter.create(2);
        for (int i = 0; i < 10; i++) {
            String time = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME);
            System.out.println(time + ":" + rateLimiter.tryAcquire());
            Thread.sleep(250);
        }

实现产品

guava,阿里的sentinal。TODO。

sentinel

sentinel地址:Sentinel工作主流程 · alibaba/Sentinel Wiki · GitHub

物理架构

限流算法深入,java,算法,开发语言

客户端限流逻辑架构

限流算法深入,java,算法,开发语言

限流流程

限流算法深入,java,算法,开发语言

  • 逻辑上sentinal分为2种处理:(1 )资源统计;(2) 处理,包含限流,降级,系统保护;
  • context保存在threadLocal,存储当前访问资源信息;
  • 每个资源对应的processorSlotChain只在第一次访问时才会创建,这个保存在static的chainMap中;当不存在对应资源的processorSlotChain时会初始化,这里会在syncronized锁;
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) { //根据资源获取对应的SlotChain
        ProcessorSlotChain chain = chainMap.get(resourceWrapper);
        if (chain == null) {
            synchronized (LOCK) {
                chain = chainMap.get(resourceWrapper);
                if (chain == null) {
                    // Entry size limit.
                    if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
                        return null;
                    }

                    chain = SlotChainProvider.newSlotChain();
                    Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
                        chainMap.size() + 1);
                    newMap.putAll(chainMap);
                    newMap.put(resourceWrapper, chain);
                    chainMap = newMap;
                }
            }
        }
        return chain;
    }
  • 各Slot说明        

StaticSlot用于实时统计资源qps/线程数,底层基于滑动窗口进行计数;

ClusterBuildSlot用于统计qps,通过数,线程数,异常数等信息,原始调用方的统计信息;

FlowSlot用于限流:策略有根据qps或线程数做降级;

DegradeSlot用于熔断降级:根据响应时间策略进行熔断;可根据异常数或响应时间进行熔断;拒绝策略有:立即拒绝,升温策略,排队策略(漏斗);

SystemSlot用于系统保护,可根据系统负载来进行保护处理;

实际业务应用

导购业务

业务背景:合作B端商家通过api来获取商品报价,而系统依赖的供应商会对我们进行限流;

解决方案:对每个B端商家进行限流,每个商家有独立限流配额,默认这个商家入驻时会给一个默认限流额度;通过sentinal的自定义限流进行处理;拒绝策略为直接拒绝;文章来源地址https://www.toymoban.com/news/detail-680059.html

String merchantCode; //商户code,外部请求传入
entry = SphU.entry("price_get_"+merchantCode);

到了这里,关于限流算法深入的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java 与其他编程语言的深入比较

    Java 是一种广泛使用的编程语言,它具有广泛的应用领域,例如 Web 开发、移动应用开发、桌面应用开发、游戏开发等。与其他编程语言相比,Java 具有以下优点: 跨平台性:Java 是一种跨平台的语言,因为它的代码可以被编译成字节码,然后在 Java 虚拟机 (JVM) 上运行。这使得

    2024年02月01日
    浏览(46)
  • 鸿蒙开发:深入了解Arkts语言中的Want对象及其运用

    Arkts语言中的 Want 是一种用于对象间信息传递的载体,主要用于应用组件之间的信息传递。本文将深入探讨 Want 的定义、用途、类型以及参数说明

    2024年02月04日
    浏览(44)
  • 限流算法之固定窗口算法

    固定窗口算法是一种常见的限流算法,它通过在固定长度的时间窗口内限制请求数量来实现限流。这种算法的原理是在每个时间窗口内,对到达的请求进行计数,如果计数达到限制值,则拒绝后续请求,直到窗口结束。 红色代表被拒绝的请求,绿色代表正常被放行的请求,阈

    2024年01月22日
    浏览(24)
  • 【微服务篇】深入理解资源隔离,限流,熔断原理(Hystrix、Resilience4j和Sentinel)

    限流、降级和资源隔离 是分布式系统设计中常用的三种技术手段,它们主要目的是增强系统的稳定性和可用性,尤其在高并发和不稳定网络环境下显得尤为重要 资源隔离通常有两种主要的实现方式: 线程池隔离和信号量隔离 。 线程池隔离 线程池隔离是通过为每个微服务或

    2024年04月11日
    浏览(45)
  • 算法:限流之令牌桶算法实现

    本章介绍令牌桶Token Bucket算法在流量限速场景的原理,以及C++实现和相关测试验证。 常见的限流算法有计数限流,固定窗口限流,滑动窗口限流,漏桶算法限流和令牌桶算法限流。令牌桶算法是限流算法的一种,其原理是系统会以一个恒定的速度往桶里放入固定数量的令牌,

    2024年02月09日
    浏览(34)
  • 《深入理解Java虚拟机》读书笔记:垃圾收集算法

    由于垃圾收集算法的实现涉及大量的程序细节,而且各个平台的虚拟机操作内存的方法又各不相同,因此本节不打算过多地讨论算法的实现,只是介绍几种算法的思想及其发展过程。 垃圾收集算法概要   标记-清除算法最基础的收集算法是“标记-清除”(Mark-Sweep)算法,算

    2024年02月13日
    浏览(67)
  • 鸿蒙开发:深入了解Arkts语言中的Want对象及其运用【鸿蒙专栏-23】

    Arkts语言中的 Want 是一种用于对象间信息传递的载体,主要用于应用组件之间的信息传递。本文将深入探讨 Want 的定义、用途、类型以及参数说明

    2024年02月05日
    浏览(58)
  • Java 算法篇-深入理解递归(递归实现:青蛙爬楼梯)

    🔥博客主页:  小扳_-CSDN博客 ❤感谢大家点赞👍收藏⭐评论✍     文章目录         1.0 递归的说明         2.0 用递归来实现相关问题         2.1 递归 - 阶乘         2.2 递归 - 反向打印字符串         2.3 递归 - 二分查找         2.4 递归 - 冒泡排序         2.5 递归

    2024年02月05日
    浏览(40)
  • 用C语言重写的原始Matlab OpenShoe算法:深入理解和实现步态分析的关键技术

    在许多领域,如医疗健康、体育科学、虚拟现实和机器人技术中,步态分析都是一个重要的研究领域。步态分析可以帮助我们理解人体运动的机制,评估疾病的影响,优化运动员的表现,甚至设计更自然的机器人运动。OpenShoe是一个开源的步态分析算法,最初是用Matlab编写的

    2024年02月13日
    浏览(47)
  • 【面试】限流算法有哪些?

    限流的实现算法有很多,但常见的限流算法有四种: 固定窗口限流算法、漏桶算法和令牌桶算法、滑动窗口限流算法 。下面我来分别介绍一下。 固定窗口限流算法(Fixed Window Rate Limiting Algorithm)是一种最简单的限流算法,其原理是在固定时间窗口(单位时间)内限制请求的数

    2023年04月12日
    浏览(25)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包