Java实现系统限流

简介: 限流是保障系统高可用的方式之一,也是大厂高频面试题,如果面试官问一句,“如何实现每秒钟1000个请求的限流?”,你要是分分钟给他写上几种限流方案,那岂不香哉,哈哈!话不多说,我来列几种常用限流实现方式。

在微服务系统中,缓存、限流、熔断是保证系统高可用的三板斧,今天我们就来聊聊限流。

限流是保障系统高可用的方式之一,也是大厂高频面试题,如果面试官问一句,“如何实现每秒钟1000个请求的限流?”,你要是分分钟给他写上几种限流方案,那岂不香哉,哈哈!话不多说,我来列几种常用限流实现方式。

1、Guava RateLimiter

Guava是Java领域很优秀的开源项目,包含了日常开发常用的集合、String、缓存等, 其中RateLimiter是常用限流工具。

RateLimiter是基于令牌桶算法实现的,如果每秒10个令牌,内部实现,会每100ms生产1个令牌。

使用Guava RateLimiter,如下:

  1. 引入pom依赖:
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>23.0</version>
</dependency>
  1. 代码:
public class GuavaRateLimiterTest {
    //比如每秒生产10个令牌,相当于每100ms生产1个令牌
    private RateLimiter rateLimiter = RateLimiter.create(10);

    /**
     * 模拟执行业务方法
     */
    public void exeBiz() {
        if (rateLimiter.tryAcquire(1)) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程" + Thread.currentThread().getName() + ":执行业务逻辑");
        } else {
            System.out.println("线程" + Thread.currentThread().getName() + ":被限流");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        GuavaRateLimiterTest limiterTest = new GuavaRateLimiterTest();
        Thread.sleep(500);//等待500ms,让limiter生产一些令牌

        //模拟瞬间生产100个线程请求
        for (int i = 0; i < 100; i++) {
            new Thread(limiterTest::exeBiz).start();
        }
    }
}

2、滑窗计数

打个比方,某接口每秒允许100个请求,设置一个滑窗,窗口中有10个格子,每个格子占100ms,每100ms移动一次。滑动窗口的格子划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。

代码如下:

/**
 * 滑窗计数器
 */
public class SliderWindowRateLimiter implements Runnable {
    //每秒允许的最大访问数
    private final long maxVisitPerSecond;
    //将每秒时间划分N个块
    private final int block;
    //每个块存储的数量
    private final AtomicLong[] countPerBlock;
    //滑动窗口划到了哪个块儿,可以理解为滑动窗口的起始下标位置
    private volatile int index;
    //目前总的数量
    private AtomicLong allCount;

    /**
     * 构造函数
     *
     * @param block,每秒钟划分N个窗口
     * @param maxVisitPerSecond 每秒最大访问数量
     */
    public SliderWindowRateLimiter(int block, long maxVisitPerSecond) {
        this.block = block;
        this.maxVisitPerSecond = maxVisitPerSecond;
        countPerBlock = new AtomicLong[block];
        for (int i = 0; i < block; i++) {
            countPerBlock[i] = new AtomicLong();
        }
        allCount = new AtomicLong(0);
    }

    /**
     * 判断是否超过最大允许数量
     *
     * @return
     */
    public boolean isOverLimit() {
        return currentQPS() > maxVisitPerSecond;
    }

    /**
     * 获取目前总的访问数
     *
     * @return
     */
    public long currentQPS() {
        return allCount.get();
    }

    /**
     * 请求访问进来,判断是否可以执行业务逻辑
     */
    public void visit() {
        countPerBlock[index].incrementAndGet();
        allCount.incrementAndGet();

        if (isOverLimit()) {
            System.out.println(Thread.currentThread().getName() + "被限流" + ",currentQPS:" + currentQPS() + ",index:" + index);
        } else {
            System.out.println(Thread.currentThread().getName() + "执行业务逻辑" + ",currentQPS:" + currentQPS() + ",index:" + index);
        }
    }

    /**
     * 定时执行器,
     * 每N毫秒滑块移动一次,然后再设置下新滑块的初始化数字0,然后新的请求会落到新的滑块上
     * 同时总数减掉新滑块上的数字,并且重置新的滑块上的数量
     */
    @Override
    public void run() {
        index = (index + 1) % block;
        long val = countPerBlock[index].getAndSet(0);
        allCount.addAndGet(-val);
    }

    public static void main(String[] args) {
        SliderWindowRateLimiter sliderWindowRateLimiter = new SliderWindowRateLimiter(10, 100);

        //固定的速率移动滑块
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.scheduleAtFixedRate(sliderWindowRateLimiter, 100, 100, TimeUnit.MILLISECONDS);

        //模拟不同速度的请求
        new Thread(() -> {
            while (true) {
                sliderWindowRateLimiter.visit();
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        //模拟不同速度的请求
        new Thread(() -> {
            while (true) {
                sliderWindowRateLimiter.visit();
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

3、信号量

利用Semaphore,每隔固定速率,释放Semaphore的资源。线程获取到资源,则执行业务代码。

代码如下:

public class SemaphoreOne {
    private static Semaphore semaphore = new Semaphore(10);

    public static void bizMethod() throws InterruptedException {
        if (!semaphore.tryAcquire()) {
            System.out.println(Thread.currentThread().getName() + "被拒绝");
            return;
        }

        System.out.println(Thread.currentThread().getName() + "执行业务逻辑");
        Thread.sleep(500);//模拟处理业务逻辑需要1秒
        semaphore.release();
    }

    public static void main(String[] args) {

        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                semaphore.release(10);
                System.out.println("释放所有锁");
            }
        }, 1000, 1000);

        for (int i = 0; i < 10000; i++) {
            try {
                Thread.sleep(10);//模拟每隔10ms就有1个请求进来
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(() -> {
                try {
                    SemaphoreOne.bizMethod();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

4、令牌桶

令牌桶算法:一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌,如有剩余容量则添加,没有则放弃。如果有请求进来,则需要先从桶里获取令牌,当桶里没有令牌可取时,则拒绝任务。

令牌桶的优点是:可以改变添加令牌的速率,一旦提高速率,则可以处理突发流量。

代码如下:

public class TokenBucket {
    /**
     * 定义的桶
     */
    public class Bucket {
        //容量
        int capacity;
        //速率,每秒放多少
        int rateCount;
        //目前token个数
        AtomicInteger curCount = new AtomicInteger(0);

        public Bucket(int capacity, int rateCount) {
            this.capacity = capacity;
            this.rateCount = rateCount;
        }

        public void put() {
            if (curCount.get() < capacity) {
                System.out.println("目前数量==" + curCount.get() + ", 我还可以继续放");
                curCount.addAndGet(rateCount);
            }
        }

        public boolean get() {
            if (curCount.get() >= 1) {
                curCount.decrementAndGet();
                return true;
            }
            return false;
        }
    }

    @Test
    public void testTokenBucket() throws InterruptedException {

        Bucket bucket = new Bucket(5, 2);

        //固定线程,固定的速率往桶里放数据,比如每秒N个
        ScheduledThreadPoolExecutor scheduledCheck = new ScheduledThreadPoolExecutor(1);
        scheduledCheck.scheduleAtFixedRate(() -> {
            bucket.put();
        }, 0, 1, TimeUnit.SECONDS);

        //先等待一会儿,让桶里放点token
        Thread.sleep(6000);

        //模拟瞬间10个线程进来拿token
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                if (bucket.get()) {
                    System.out.println(Thread.currentThread() + "获取到了资源");
                } else {
                    System.out.println(Thread.currentThread() + "被拒绝");
                }
            }).start();
        }

        //等待,往桶里放token
        Thread.sleep(3000);

        //继续瞬间10个线程进来拿token
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                if (bucket.get()) {
                    System.out.println(Thread.currentThread() + "获取到了资源");
                } else {
                    System.out.println(Thread.currentThread() + "被拒绝");
                }
            }).start();
        }
    }
}

5、总结

本文主要介绍了几种限流方法:Guava RateLimiter、简单计数、滑窗计数、信号量、令牌桶,当然,限流算法还有漏桶算法、nginx限流等等。我所写的这些方法只是个人在实际项目总使用过的,或者是早年参加阿里笔试时写过的方式。

如果你更好的想法,也欢迎跟我一起交流!

本篇完结!感谢你的阅读,欢迎点赞 关注 收藏 私信!!!

原文链接:http://www.mangod.top/articles/2023/03/15/1678873704036.htmlhttps://mp.weixin.qq.com/s/Gidt510r12tYpplmNHkfsA

相关文章
|
27天前
|
设计模式 消息中间件 传感器
Java 设计模式之观察者模式:构建松耦合的事件响应系统
观察者模式是Java中常用的行为型设计模式,用于构建松耦合的事件响应系统。当一个对象状态改变时,所有依赖它的观察者将自动收到通知并更新。该模式通过抽象耦合实现发布-订阅机制,广泛应用于GUI事件处理、消息通知、数据监控等场景,具有良好的可扩展性和维护性。
209 8
|
1月前
|
移动开发 监控 小程序
java家政平台源码,家政上门清洁系统源码,数据多端互通,可直接搭建使用
一款基于Java+SpringBoot+Vue+UniApp开发的家政上门系统,支持小程序、APP、H5、公众号多端互通。涵盖用户端、技工端与管理后台,支持多城市、服务分类、在线预约、微信支付、抢单派单、技能认证、钱包提现等功能,源码开源,可直接部署使用。
154 23
|
1月前
|
安全 前端开发 Java
使用Java编写UDP协议的简易群聊系统
通过这个基础框架,你可以进一步增加更多的功能,例如用户认证、消息格式化、更复杂的客户端界面等,来丰富你的群聊系统。
161 11
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
Java与生成式AI:构建内容生成与创意辅助系统
生成式AI正在重塑内容创作、软件开发和创意设计的方式。本文深入探讨如何在Java生态中构建支持文本、图像、代码等多种生成任务的创意辅助系统。我们将完整展示集成大型生成模型(如GPT、Stable Diffusion)、处理生成任务队列、优化生成结果以及构建企业级生成式AI应用的全流程,为Java开发者提供构建下一代创意辅助系统的完整技术方案。
142 10
|
1月前
|
人工智能 监控 Java
Java与AI智能体:构建自主决策与工具调用的智能系统
随着AI智能体技术的快速发展,构建能够自主理解任务、制定计划并执行复杂操作的智能系统已成为新的技术前沿。本文深入探讨如何在Java生态中构建具备工具调用、记忆管理和自主决策能力的AI智能体系统。我们将完整展示从智能体架构设计、工具生态系统、记忆机制到多智能体协作的全流程,为Java开发者提供构建下一代自主智能系统的完整技术方案。
350 4
|
1月前
|
机器学习/深度学习 分布式计算 Java
Java与图神经网络:构建企业级知识图谱与智能推理系统
图神经网络(GNN)作为处理非欧几里得数据的前沿技术,正成为企业知识管理和智能推理的核心引擎。本文深入探讨如何在Java生态中构建基于GNN的知识图谱系统,涵盖从图数据建模、GNN模型集成、分布式图计算到实时推理的全流程。通过具体的代码实现和架构设计,展示如何将先进的图神经网络技术融入传统Java企业应用,为构建下一代智能决策系统提供完整解决方案。
275 0
|
2月前
|
JavaScript Java 大数据
基于JavaWeb的销售管理系统设计系统
本系统基于Java、MySQL、Spring Boot与Vue.js技术,构建高效、可扩展的销售管理平台,实现客户、订单、数据可视化等全流程自动化管理,提升企业运营效率与决策能力。
|
2月前
|
安全 Cloud Native Java
Java 模块化系统(JPMS)技术详解与实践指南
本文档全面介绍 Java 平台模块系统(JPMS)的核心概念、架构设计和实践应用。作为 Java 9 引入的最重要特性之一,JPMS 为 Java 应用程序提供了强大的模块化支持,解决了长期存在的 JAR 地狱问题,并改善了应用的安全性和可维护性。本文将深入探讨模块声明、模块路径、访问控制、服务绑定等核心机制,帮助开发者构建更加健壮和可维护的 Java 应用。
243 0
|
2月前
|
NoSQL Java 关系型数据库
超全 Java 学习路线,帮你系统掌握编程的超详细 Java 学习路线
本文为超全Java学习路线,涵盖基础语法、面向对象编程、数据结构与算法、多线程、JVM原理、主流框架(如Spring Boot)、数据库(MySQL、Redis)及项目实战等内容,助力从零基础到企业级开发高手的进阶之路。
253 1
|
3月前
|
Java 数据库 前端开发
分享44个java系统,总有一款适合您
分享44个微信小程序,总有一款适合您
66 0