使用 Spring Boot 实现限流功能:从理论到实践

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 【6月更文挑战第18天】在微服务和高并发系统中,限流(Rate Limiting)是一种非常重要的技术手段,用于保护系统免受过载,确保服务的稳定性。限流可以控制请求的速率,防止单个客户端或恶意用户消耗过多的资源,从而影响其他用户。

在微服务和高并发系统中,限流(Rate Limiting)是一种非常重要的技术手段,用于保护系统免受过载,确保服务的稳定性。限流可以控制请求的速率,防止单个客户端或恶意用户消耗过多的资源,从而影响其他用户。

一、限流的理论基础

常见的限流算法包括:

  1. 固定窗口计数算法(Fixed Window Counter):将时间分为固定的窗口,计数当前窗口内的请求数。
  2. 滑动窗口计数算法(Sliding Window Counter):在固定窗口计数的基础上,引入滑动窗口,细化时间粒度。
  3. 漏桶算法(Leaky Bucket):请求以恒定的速率流出,处理请求时模拟水从桶中流出。
  4. 令牌桶算法(Token Bucket):系统以恒定的速率向桶中添加令牌,请求到达时取走令牌,没有令牌则拒绝服务。

二、Spring Boot 实现限流

使用 Spring Boot 实现限流,可以通过以下几种方式:

  1. 基于过滤器(Filter)的限流实现
  2. 使用第三方库,如 Bucket4j
  3. 使用 Redis 实现分布式限流

下面我们分别介绍这些方法的实现。

方法一:基于过滤器的限流实现

1.1 创建过滤器

首先,我们创建一个限流过滤器,通过 AtomicIntegerSemaphore 来控制请求速率。

java复制代码

import org.springframework.stereotype.Component;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.Semaphore;

@Component
public class RateLimitingFilter implements Filter {

    private static final int MAX_REQUESTS_PER_SECOND = 10;
    private Semaphore semaphore = new Semaphore(MAX_REQUESTS_PER_SECOND);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (semaphore.tryAcquire()) {
            try {
                chain.doFilter(request, response);
            } finally {
                semaphore.release();
            }
        } else {
            ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
        }
    }

    @Override
    public void destroy() {
    }
}

1.2 配置过滤器

在 Spring Boot 应用中,过滤器自动注册,只需要添加 @Component 注解即可。不过,你也可以手动配置:

java复制代码

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<RateLimitingFilter> loggingFilter() {
        FilterRegistrationBean<RateLimitingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new RateLimitingFilter());
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }
}

方法二:使用 Bucket4j 实现限流

2.1 添加依赖

pom.xml 中添加 Bucket4j 依赖:

xml复制代码

<dependency>
    <groupId>com.github.vladimir-bukhtoyarov</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>6.2.0</version>
</dependency>

2.2 创建限流过滤器

java复制代码

import com.github.bucket4j.Bandwidth;
import com.github.bucket4j.Bucket;
import com.github.bucket4j.Refill;
import org.springframework.stereotype.Component;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.Duration;

@Component
public class Bucket4jRateLimitingFilter implements Filter {

    private static final int CAPACITY = 10;
    private static final int REFILL_TOKENS = 10;
    private static final Duration REFILL_DURATION = Duration.ofSeconds(1);

    private final Bucket bucket;

    public Bucket4jRateLimitingFilter() {
        Bandwidth limit = Bandwidth.classic(CAPACITY, Refill.greedy(REFILL_TOKENS, REFILL_DURATION));
        this.bucket = Bucket.builder().addLimit(limit).build();
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (bucket.tryConsume(1)) {
            chain.doFilter(request, response);
        } else {
            ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
        }
    }

    @Override
    public void destroy() {
    }
}

2.3 配置过滤器

与方法一类似,配置过滤器即可:

java复制代码

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<Bucket4jRateLimitingFilter> loggingFilter() {
        FilterRegistrationBean<Bucket4jRateLimitingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new Bucket4jRateLimitingFilter());
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }
}

方法三:使用 Redis 实现分布式限流

3.1 添加依赖

pom.xml 中添加 Redis 和 Redisson 依赖:

xml复制代码

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.15.6</version>
</dependency>

3.2 配置 Redis 和 Redisson

application.properties 中配置 Redis:

properties复制代码

spring.redis.host=localhost
spring.redis.port=6379

3.3 创建 Redis 限流过滤器

java复制代码

import org.redisson.api.RRateLimiter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class RedisRateLimitingFilter implements Filter {

    @Autowired
    private RedissonClient redissonClient;

    private RRateLimiter rateLimiter;

    @Bean
    public RRateLimiter rateLimiter() {
        RRateLimiter rateLimiter = redissonClient.getRateLimiter("rateLimiter");
        rateLimiter.trySetRate(RRateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);
        return rateLimiter;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.rateLimiter = rateLimiter();
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (rateLimiter.tryAcquire(1)) {
            chain.doFilter(request, response);
        } else {
            ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
        }
    }

    @Override
    public void destroy() {
    }
}

3.4 配置过滤器

与之前的方法一样,配置过滤器:

java复制代码

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<RedisRateLimitingFilter> loggingFilter() {
        FilterRegistrationBean<RedisRateLimitingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new RedisRateLimitingFilter());
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }
}

结论

在本指南中,我们介绍了三种在 Spring Boot 中实现限流的方法:

  1. 基于过滤器的简单限流实现。
  2. 使用第三方库 Bucket4j 实现限流。
  3. 使用 Redis 实现分布式限流。

每种方法都有其优缺点和适用场景,可以根据具体需求选择合适的方案。希望本文能帮助你在项目中实现限流功能,保障系统的稳定性和可靠性。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
4天前
|
消息中间件 缓存 Java
手写模拟Spring Boot启动过程功能
【11月更文挑战第19天】Spring Boot自推出以来,因其简化了Spring应用的初始搭建和开发过程,迅速成为Java企业级应用开发的首选框架之一。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,帮助读者深入理解其工作机制。
21 3
|
4天前
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
20 0
|
17天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
95 62
|
14天前
|
前端开发 Java easyexcel
SpringBoot操作Excel实现单文件上传、多文件上传、下载、读取内容等功能
SpringBoot操作Excel实现单文件上传、多文件上传、下载、读取内容等功能
54 8
|
15天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
34 2
|
19天前
|
JSON Java API
springboot集成ElasticSearch使用completion实现补全功能
springboot集成ElasticSearch使用completion实现补全功能
23 1
|
20天前
|
数据采集 Java 数据安全/隐私保护
Spring Boot 3.3中的优雅实践:全局数据绑定与预处理
【10月更文挑战第22天】 在Spring Boot应用中,`@ControllerAdvice`是一个强大的工具,它允许我们在单个位置处理多个控制器的跨切面关注点,如全局数据绑定和预处理。这种方式可以大大减少重复代码,提高开发效率。本文将探讨如何在Spring Boot 3.3中使用`@ControllerAdvice`来实现全局数据绑定与预处理。
56 2
|
23天前
|
SQL Java 数据库
Spring Boot与Flyway:数据库版本控制的自动化实践
【10月更文挑战第19天】 在软件开发中,数据库的版本控制是一个至关重要的环节,它确保了数据库结构的一致性和项目的顺利迭代。Spring Boot结合Flyway提供了一种自动化的数据库版本控制解决方案,极大地简化了数据库迁移管理。本文将详细介绍如何使用Spring Boot和Flyway实现数据库版本的自动化控制。
23 2
|
23天前
|
存储 Java 数据管理
强大!用 @Audited 注解增强 Spring Boot 应用,打造健壮的数据审计功能
本文深入介绍了如何在Spring Boot应用中使用`@Audited`注解和`spring-data-envers`实现数据审计功能,涵盖从添加依赖、配置实体类到查询审计数据的具体步骤,助力开发人员构建更加透明、合规的应用系统。
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
143 1