后端下载限速(redis记录实时并发,bucket4j动态限速)

news2025/6/10 7:30:08
  • ✅ 使用 Redis 记录 所有用户的实时并发下载数
  • ✅ 使用 Bucket4j 实现 全局下载速率限制(动态)
  • ✅ 支持 动态调整限速策略
  • ✅ 下载接口安全、稳定、可监控

🧩 整体架构概览

模块功能
Redis存储全局并发数和带宽令牌桶状态
Bucket4j + Redis分布式限速器(基于令牌桶算法)
Spring Boot Web提供文件下载接口
AOP / Interceptor(可选)用于统一处理限流逻辑

📦 1. Maven 依赖(pom.xml

<dependencies>

    <!-- Spring Boot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- Redis 连接池 -->
    <dependency>
        <groupId>io.lettuce.core</groupId>
        <artifactId>lettuce-core</artifactId>
    </dependency>

    <!-- Bucket4j 核心与 Redis 集成 -->
    <dependency>
        <groupId>com.github.vladimir-bukhtoyarov</groupId>
        <artifactId>bucket4j-core</artifactId>
        <version>5.3.0</version>
    </dependency>
    <dependency>
        <groupId>com.github.vladimir-bukhtoyarov</groupId>
        <artifactId>bucket4j-redis</artifactId>
        <version>5.3.0</version>
    </dependency>

</dependencies>

🛠️ 2. Redis 工具类:记录全局并发数

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

@Component
public class GlobalDownloadCounter {

    private final StringRedisTemplate redisTemplate;
    private final DefaultRedisScript<Long> incrScript;
    private final DefaultRedisScript<Long> decrScript;

    public static final String KEY_CONCURRENT = "global:download:concurrent";
    private static final long TTL_SECONDS = 60; // 自动清理僵尸计数

    public GlobalDownloadCounter(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;

        // Lua 脚本:原子增加并发数并设置过期时间
        String scriptIncr = """
            local key = KEYS[1]
            local ttl = tonumber(ARGV[1])
            local count = redis.call('GET', key)
            if not count then
                redis.call('SET', key, 1)
                redis.call('EXPIRE', key, ttl)
                return 1
            else
                count = tonumber(count) + 1
                redis.call('SET', key, count)
                redis.call('EXPIRE', key, ttl)
                return count
            end
        """;
        incrScript = new DefaultRedisScript<>(scriptIncr, Long.class);

        // Lua 脚本:原子减少并发数
        String scriptDecr = """
            local key = KEYS[1]
            local count = redis.call('GET', key)
            if not count or tonumber(count) <= 0 then
                return 0
            else
                count = tonumber(count) - 1
                redis.call('SET', key, count)
                return count
            end
        """;
        decrScript = new DefaultRedisScript<>(scriptDecr, Long.class);
    }

    public long increment() {
        return redisTemplate.execute(incrScript, Collections.singletonList(KEY_CONCURRENT), TTL_SECONDS).longValue();
    }

    public long decrement() {
        return redisTemplate.execute(decrScript, Collections.singletonList(KEY_CONCURRENT)).longValue();
    }

    public long getCurrentCount() {
        String value = redisTemplate.opsForValue().get(KEY_CONCURRENT);
        return value == null ? 0 : Long.parseLong(value);
    }
}

⚙️ 3. Bucket4j 配置:分布式限速器(带 Redis)

import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Refill;
import io.github.bucket4j.distributed.proxy.ProxyManager;
import io.github.bucket4j.distributed.proxy.RedisProxyManager;
import io.github.bucket4j.redis.lettuce.cas.LettuceReactiveProxyManager;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

@Configuration
public class BandwidthLimiterConfig {

    @Bean
    public RedisClient redisClient() {
        return RedisClient.create("redis://localhost:6379");
    }

    @Bean
    public StatefulRedisConnection<String, String> redisConnection(RedisClient redisClient) {
        return redisClient.connect();
    }

    @Bean
    public ProxyManager<String> proxyManager(StatefulRedisConnection<String, String> connection) {
        return LettuceReactiveProxyManager.builder()
                .build(connection.reactive());
    }

    @Bean
    public Bandwidth globalBandwidthLimit() {
        // 默认 10MB/s
        return Bandwidth.classic(10 * 1024 * 1024, Refill.greedy(10 * 1024 * 1024, Duration.ofSeconds(1)));
    }

    @Bean
    public Bucket globalBandwidthLimiter(ProxyManager<String> proxyManager, Bandwidth bandwidthLimit) {
        return proxyManager.builder().build("global:bandwidth:limiter", bandwidthLimit);
    }
}

📡 4. 下载接口实现

import io.github.bucket4j.Bucket;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;

@RestController
@RequestMapping("/api/download")
public class DownloadController {

    private static final int MAX_CONCURRENT_DOWNLOADS = 100;

    private final GlobalDownloadCounter downloadCounter;
    private final Bucket bandwidthLimiter;

    public DownloadController(GlobalDownloadCounter downloadCounter, Bucket bandwidthLimiter) {
        this.downloadCounter = downloadCounter;
        this.bandwidthLimiter = bandwidthLimiter;
    }

    @GetMapping("/{fileId}")
    public void downloadFile(@PathVariable String fileId, HttpServletResponse response) throws IOException {

        long currentCount = downloadCounter.getCurrentCount();

        if (currentCount >= MAX_CONCURRENT_DOWNLOADS) {
            response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
            response.getWriter().write("Too many downloads. Please try again later.");
            return;
        }

        downloadCounter.increment();

        try {
            // 设置响应头
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment; filename=" + fileId + ".bin");

            ServletOutputStream out = response.getOutputStream();

            // 文件路径(示例)
            String filePath = "/path/to/files/" + fileId + ".bin";
            if (!Files.exists(Paths.get(filePath))) {
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                response.getWriter().write("File not found.");
                return;
            }

            byte[] buffer = new byte[8192]; // 每次读取 8KB
            RandomAccessFile file = new RandomAccessFile(filePath, "r");
            int bytesRead;

            while ((bytesRead = file.read(buffer)) != -1) {
                if (bytesRead > 0) {
                    boolean consumed = bandwidthLimiter.tryConsume(bytesRead);
                    if (!consumed) {
                        Thread.sleep(100); // 等待令牌生成
                        continue;
                    }
                    out.write(buffer, 0, bytesRead);
                    out.flush();
                }
            }

            file.close();
            out.close();

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 恢复中断状态
        } finally {
            downloadCounter.decrement();
        }
    }
}

🔁 5. 动态调整下载速率接口(可选)

@RestController
@RequestMapping("/api/limit")
public class RateLimitController {

    private final Bucket bandwidthLimiter;
    private final Bandwidth globalBandwidthLimit;

    public RateLimitController(Bucket bandwidthLimiter, Bandwidth globalBandwidthLimit) {
        this.bandwidthLimiter = bandwidthLimiter;
        this.globalBandwidthLimit = globalBandwidthLimit;
    }

    @PostMapping("/set-bandwidth")
    public String setBandwidth(@RequestParam int mbPerSecond) {
        Bandwidth newLimit = Bandwidth.classic(mbPerSecond * 1024 * 1024,
                Refill.greedy(mbPerSecond * 1024 * 1024, Duration.ofSeconds(1)));
        bandwidthLimiter.replaceConfiguration(newLimit);
        return "Global bandwidth limit updated to " + mbPerSecond + " MB/s";
    }
}

📊 6. 监控接口(可选)

@GetMapping("/monitor/concurrent")
public ResponseEntity<Long> getConcurrentDownloads() {
    return ResponseEntity.ok(downloadCounter.getCurrentCount());
}

🧪 7. 测试建议

你可以使用 curl 或 Postman 发起多并发请求测试:

for i in {1..200}; do
  curl -X GET "http://localhost:8080/api/download/file1" --output "file$i.bin" &
done

观察是否触发限流、并发控制是否生效。


✅ 总结

组件作用
Redis分布式存储并发数和限流令牌桶
Lua 脚本原子操作并发计数器
Bucket4j + Redis全局下载速率限制
Spring Boot Controller处理下载逻辑
try-finally保证资源释放
动态接口 /set-bandwidth支持运行时修改限速

📌 扩展建议(可选)

  • 将限流逻辑封装到 AOP 切面
  • 添加 Prometheus 指标暴露并发数、限流次数等
  • 使用 Nginx 或 Gateway 做额外的限流保护
  • 加入用户身份识别,支持 用户级限速
  • 使用 Kafka 异步记录日志或审计下载行为

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2406353.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3 手动封装城市三级联动

要做的功能 示意图是这样的&#xff0c;因为后端给的数据结构 不足以使用ant-design组件 的联动查询组件 所以只能自己分装 组件 当然 这个数据后端给的不一样的情况下 可能组件内对应的 逻辑方式就不一样 毕竟是 三个 数组 省份 城市 区域 我直接粘贴组件代码了 <temp…

Linux【5】-----编译和烧写Linux系统镜像(RK3568)

参考&#xff1a;讯为 1、文件系统 不同的文件系统组成了&#xff1a;debian、ubuntu、buildroot、qt等系统 每个文件系统的uboot和kernel是一样的 2、源码目录介绍 目录 3、正式编译 编译脚本build.sh 帮助内容如下&#xff1a; Available options: uboot …

Heygem50系显卡合成的视频声音杂音模糊解决方案

如果你在使用50系显卡有杂音的情况&#xff0c;可能还是官方适配问题&#xff0c;可以使用以下方案进行解决&#xff1a; 方案一&#xff1a;剪映替换音色&#xff08;简单适合普通玩家&#xff09; 使用剪映换音色即可&#xff0c;口型还是对上的&#xff0c;没有剪映vip的&…

Gitlab + Jenkins 实现 CICD

CICD 是持续集成&#xff08;Continuous Integration, CI&#xff09;和持续交付/部署&#xff08;Continuous Delivery/Deployment, CD&#xff09;的缩写&#xff0c;是现代软件开发中的一种自动化流程实践。下面介绍 Web 项目如何在代码提交到 Gitlab 后&#xff0c;自动发布…

无头浏览器技术:Python爬虫如何精准模拟搜索点击

1. 无头浏览器技术概述 1.1 什么是无头浏览器&#xff1f; 无头浏览器是一种没有图形用户界面&#xff08;GUI&#xff09;的浏览器&#xff0c;它通过程序控制浏览器内核&#xff08;如Chromium、Firefox&#xff09;执行页面加载、JavaScript渲染、表单提交等操作。由于不渲…

SDU棋界精灵——硬件程序ESP32实现opus编码

一、 ​​音频处理框架​ 该项目基于Espressif的音频处理框架构建,核心组件包括 ESP-ADF 和 ESP-SR,以下是完整的音频处理框架实现细节: 1.核心组件 (1) 音频前端处理 (AFE - Audio Front-End) ​​main/components/audio_pipeline/afe_processor.c​​功能​​: 声学回声…

Spring AI中使用ChatMemory实现会话记忆功能

文章目录 1、需求2、ChatMemory中消息的存储位置3、实现步骤1、引入依赖2、配置Spring AI3、配置chatmemory4、java层传递conversaionId 4、验证5、完整代码6、参考文档 1、需求 我们知道大型语言模型 &#xff08;LLM&#xff09; 是无状态的&#xff0c;这就意味着他们不会保…

Qt 按钮类控件(Push Button 与 Radio Button)(1)

文章目录 Push Button前提概要API接口给按钮添加图标给按钮添加快捷键 Radio ButtonAPI接口性别选择 Push Button&#xff08;鼠标点击不放连续移动快捷键&#xff09; Radio Button Push Button 前提概要 1. 之前文章中所提到的各种跟QWidget有关的各种属性/函数/方法&#…

生成对抗网络(GAN)损失函数解读

GAN损失函数的形式&#xff1a; 以下是对每个部分的解读&#xff1a; 1. ⁡, ​ &#xff1a;这个部分表示生成器&#xff08;Generator&#xff09;G的目标是最小化损失函数。 &#xff1a;判别器&#xff08;Discriminator&#xff09;D的目标是最大化损失函数。 GAN的训…

汇编语言学习(三)——DoxBox中debug的使用

目录 一、安装DoxBox&#xff0c;并下载汇编工具&#xff08;MASM文件&#xff09; 二、debug是什么 三、debug中的命令 一、安装DoxBox&#xff0c;并下载汇编工具&#xff08;MASM文件&#xff09; 链接&#xff1a; https://pan.baidu.com/s/1IbyJj-JIkl_oMOJmkKiaGQ?pw…

数据可视化交互

目录 【实验目的】 【实验原理】 【实验环境】 【实验步骤】 一、安装 pyecharts 二、下载数据 三、实验任务 实验 1&#xff1a;AQI 横向对比条形图 代码说明&#xff1a; 运行结果&#xff1a; 实验 2&#xff1a;AQI 等级分布饼图 实验 3&#xff1a;多城市 AQI…

安宝特方案丨从依赖经验到数据驱动:AR套件重构特种装备装配与质检全流程

在高压电气装备、军工装备、石油测井仪器装备、计算存储服务器和机柜、核磁医疗装备、大型发动机组等特种装备生产型企业&#xff0c;其产品具有“小批量、多品种、人工装配、价值高”的特点。 生产管理中存在传统SOP文件内容缺失、SOP更新不及、装配严重依赖个人经验、产品装…

【JavaEE】万字详解HTTP协议

HTTP是什么&#xff1f;-----互联网的“快递小哥” 想象我们正在网上购物&#xff1a;打开淘宝APP&#xff0c;搜索“蓝牙耳机”&#xff0c;点击商品图片&#xff0c;然后下单付款。这一系列操作背后&#xff0c;其实有一个看不见的“快递小哥”在帮我们传递信息&#xff0c;…

华为云Flexus+DeepSeek征文 | MaaS平台避坑指南:DeepSeek商用服务开通与成本控制

作者简介 我是摘星&#xff0c;一名专注于云计算和AI技术的开发者。本次通过华为云MaaS平台体验DeepSeek系列模型&#xff0c;将实际使用经验分享给大家&#xff0c;希望能帮助开发者快速掌握华为云AI服务的核心能力。 目录 作者简介 前言 一、技术架构概览 1.1 整体架构设…

【动态规划】B4336 [中山市赛 2023] 永别|普及+

B4336 [中山市赛 2023] 永别 题目描述 你做了一个梦&#xff0c;梦里有一个字符串&#xff0c;这个字符串无论正着读还是倒着读都是一样的&#xff0c;例如&#xff1a; a b c b a \tt abcba abcba 就符合这个条件。 但是你醒来时不记得梦中的字符串是什么&#xff0c;只记得…

可下载旧版app屏蔽更新的app市场

软件介绍 手机用久了&#xff0c;app越来越臃肿&#xff0c;老手机卡顿成常态。这里给大家推荐个改善老手机使用体验的方法&#xff0c;还能帮我们卸载不需要的app。 手机现状 如今的app不断更新&#xff0c;看似在优化&#xff0c;实则内存占用越来越大&#xff0c;对手机性…

claude3.7高阶玩法,生成系统架构图,国内直接使用

文章目录 零、前言一、操作指南操作指导 二、提示词模板三、实战图书管理系统通过4o模型生成系统描述通过claude3.7生成系统架构图svg代码转换成图片 在线考试系统通过4o模型生成系统描述通过claude3.7生成系统架构图svg代码转换成图片 四、感受 零、前言 现在很多AI大模型可以…

河北对口计算机高考MySQL笔记(完结版)(2026高考)持续更新~~~~

MySQL 基础概念 数据&#xff08;Data&#xff09;&#xff1a;文本&#xff0c;数字&#xff0c;图片&#xff0c;视频&#xff0c;音频等多种表现形式&#xff0c;能够被计算机存储和处理。 **数据库&#xff08;Data Base—简称DB&#xff09;&#xff1a;**存储数据的仓库…

2025-06-01-Hive 技术及应用介绍

Hive 技术及应用介绍 参考资料 Hive 技术原理Hive 架构及应用介绍Hive - 小海哥哥 de - 博客园https://cwiki.apache.org/confluence/display/Hive/Home(官方文档) Apache Hive 是基于 Hadoop 构建的数据仓库工具&#xff0c;它为海量结构化数据提供类 SQL 的查询能力&#xf…

AI书签管理工具开发全记录(十八):书签导入导出

文章目录 AI书签管理工具开发全记录&#xff08;十八&#xff09;&#xff1a;书签导入导出1.前言 &#x1f4dd;2.书签结构分析 &#x1f4d6;3.书签示例 &#x1f4d1;4.书签文件结构定义描述 &#x1f523;4.1. ​整体文档结构​​4.2. ​核心元素类型​​4.3. ​层级关系4.…