基于SpringBoot+Redis实现一个简单的限流器

 更新时间:2023年08月15日 11:08:57   作者:冰点.  
在Spring Boot中使用Redis和过滤器实现请求限流,过滤器将在每个请求到达时检查请求频率,并根据设定的阈值进行限制,这样可以保护您的应用程序免受恶意请求或高并发请求的影响,本文我们通过Spring Boot +Redis 实现一个轻量级的消息队列,需要的朋友可以参考下

1.基础介绍

1.1. 限流场景

假设我们有一个API接口,需要限制每个用户在一段时间内的请求频率。比如每秒只允许请求100次等等的业务需求。

1.2. 实现限流逻辑:

使用Redis的计数器功能可以实现基于时间窗口的限流算法。通过在Redis中存储请求计数器和过期时间,可以控制单位时间内的请求频率。在需要进行限流的接口或方法中,使用Redis的原子操作(如INCR和EXPIRE)来增加计数器并设置过期时间。

在每个请求到达时,检查计数器的值是否超过设定的阈值,如果超过则拒绝请求,否则允许请求继续执行。

2.步骤

2.1. 引入依赖

<dependencies>
    <!-- Spring Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

2.2. 配置文件

# Redis连接配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=your_password
spring.redis.database=0
# Redis连接池配置
spring.redis.jedis.pool.max-active=50
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=5
spring.redis.jedis.pool.max-wait=-1

在上面的配置中,您可以根据实际情况修改以下属性:

  • spring.redis.host:Redis服务器的主机名或IP地址。
  • spring.redis.port:Redis服务器的端口号。
  • spring.redis.password:Redis服务器的密码(如果有的话)。
  • spring.redis.database:Redis数据库的索引,默认为0。

另外,您还可以配置Redis连接池的属性,以控制连接池的行为。在示例配置中,设置了以下连接池属性:

  • spring.redis.jedis.pool.max-active:连接池中的最大活动连接数。
  • spring.redis.jedis.pool.max-idle:连接池中的最大空闲连接数。
  • spring.redis.jedis.pool.min-idle:连接池中的最小空闲连接数。
  • spring.redis.jedis.pool.max-wait:从连接池获取连接的最大等待时间(毫秒),-1表示无限等待。

如果 使用的是YAML格式的配置文件(application.yml),可以将上述配置转换为相应的格式:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: your_password
    database: 0
  redis.jedis.pool:
    max-active: 50
    max-idle: 10
    min-idle: 5
    max-wait: -1

请根据您的实际Redis服务器配置进行调整,并根据需要添加其他相关配置,如超时设置、SSL配置等。

2.3. 核心源码

  • 实现请求限流过滤器

    创建一个实现javax.servlet.Filter接口的请求限流过滤器。在过滤器中,使用Redis的计数器功能来实现请求限流逻辑。

    示例中,RequestLimitFilter是一个实现了javax.servlet.Filter接口的请求限流过滤器。它使用Redis的计数器功能来实现请求限流逻辑。每个请求到达时,根据客户端的IP地址作为Redis的键,增加计数器的值并设置过期时间为指定的时间窗口。如果计数器超过了设定的阈值(这里是100),则返回HTTP 429 Too Many Requests响应

示例中使用的是RedisTemplate<String, String>来操作Redis, 可以根据需要调整为适合您的数据类型和操作方式的RedisTemplate。

@Component
public class RequestLimitFilter implements Filter {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private static final String REQUEST_LIMIT_PREFIX = "requestLimit:";
    private static final long REQUEST_LIMIT = 100; // 请求限制数量
    private static final long TIME_WINDOW = 60; // 时间窗口(单位:秒)
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String ipAddress = getClientIpAddress(httpRequest);
        String key = REQUEST_LIMIT_PREFIX + ipAddress;
        Long counter = redisTemplate.opsForValue().increment(key, 1);
        if (counter == 1) {
            redisTemplate.expire(key, TIME_WINDOW, TimeUnit.SECONDS);
        }
        if (counter > REQUEST_LIMIT) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            httpResponse.getWriter().write("请求频率超过限制,请稍后再试!");
            return;
        }
        chain.doFilter(request, response);
    }
  private String getClientIpAddress(HttpServletRequest request) {
    String ipAddress = request.getHeader("X-Forwarded-For");
    if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
        ipAddress = request.getHeader("Proxy-Client-IP");
    }
    if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
        ipAddress = request.getHeader("WL-Proxy-Client-IP");
    }
    if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
        ipAddress = request.getRemoteAddr();
    }
    return ipAddress;
}
}

优化后

public class RequestLimitFilter implements Filter {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private static final String REQUEST_LIMIT_PREFIX = "requestLimit:";
    private static final long REQUEST_LIMIT = 100; // 请求限制数量
    private static final long TIME_WINDOW = 60; // 时间窗口(单位:秒)
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String ipAddress = getClientIpAddress(httpRequest);
        String key = REQUEST_LIMIT_PREFIX + ipAddress;
        Long counter = redisTemplate.opsForValue().increment(key, 1);
        if (counter == 1) {
            redisTemplate.expire(key, TIME_WINDOW, TimeUnit.SECONDS);
        }
        if (counter > REQUEST_LIMIT) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            try (PrintWriter writer = httpResponse.getWriter()) {
                writer.write("请求频率超过限制,请稍后再试!");
            }
            return;
        }
        chain.doFilter(request, response);
    }
    private String getClientIpAddress(HttpServletRequest request) {
      ...
        return ipAddress;
    }
}

再优化一下加入布隆过滤器

使用布隆过滤器减少对Redis的访问:布隆过滤器是一种高效的概率数据结构,可以用于快速判断元素是否存在于集合中。在限制请求频率时,可以使用布隆过滤器来减少对Redis的访问。只有在布隆过滤器判断请求不是重复请求时,才进行Redis操作。

public class RequestLimitFilter implements Filter {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private static final String REQUEST_LIMIT_PREFIX = "requestLimit:";
    private static final long REQUEST_LIMIT = 100; // 请求限制数量
    private static final long TIME_WINDOW = 60; // 时间窗口(单位:秒)
    private BloomFilter<String> bloomFilter;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化布隆过滤器
        int expectedInsertions = 1000; // 预期插入数量
        double falsePositiveProbability = 0.01; // 误判率
        bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, falsePositiveProbability);
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String ipAddress = getClientIpAddress(httpRequest);
        if (bloomFilter.mightContain(ipAddress)) {
            // 布隆过滤器判断可能是重复请求,直接放行
            chain.doFilter(request, response);
            return;
        }
        String key = REQUEST_LIMIT_PREFIX + ipAddress;
        Long counter;
        boolean isNewKey = false;
        try {
            counter = redisTemplate.opsForValue().increment(key, 1);
            if (counter == 1) {
                redisTemplate.expire(key, TIME_WINDOW, TimeUnit.SECONDS);
                isNewKey = true;
            }
        } catch (Exception e) {
            // 处理Redis操作异常
            // 可以选择记录日志或采取适当的处理措施
            e.printStackTrace();
            chain.doFilter(request, response);
            return;
        }
        if (counter > REQUEST_LIMIT) {
            if (isNewKey) {
                // 删除新创建的键,避免无限增长
                redisTemplate.delete(key);
            }
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            try (PrintWriter writer = httpResponse.getWriter()) {
                writer.write("请求频率超过限制,请稍后再试!");
            }
            return;
        }
        bloomFilter.put(ipAddress); // 将IP地址添加到布隆过滤器
        chain.doFilter(request, response);
    }
    @Override
    public void destroy() {
        // 清理资源,如关闭Redis连接等
    }
    private String getClientIpAddress(HttpServletRequest request) {
        // 获取客户端IP地址的逻辑
        // ...
    }
}

在上述代码中,我们引入了布隆过滤器来减少对Redis的访问。如果布隆过滤器判断请求可能是重复请求,则直接放行,无需进行Redis操作。同时,我们还添加了对Redis操作异常的处理,并在限流超过阈值时删除新创建的键,以避免无限增长。请根据实际情况进行适当调整和完善。

  • 注册过滤器
    在Spring Boot应用程序的配置类中注册过滤器,以便它能够在请求处理过程中生效。
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private RequestLimitFilter requestLimitFilter;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(requestLimitFilter);
    }
}

通过将过滤器添加到addInterceptors方法中,它将被注册为Spring Boot应用程序的全局过滤器,并在请求到达时执行限流逻辑。

3.总结

其实上面我们写完的还是有问题的

  • 如果系统部署在多个节点上,可以考虑使用分布式限流算法,如令牌桶算法或漏桶算法。这些算法可以在分布式环境中平衡请求的处理,并保证全局的请求限制。
  • 将请求限流的参数,如请求限制数量和时间窗口,配置为可动态调整的参数。可以使用注解或配置文件来管理这些参数,以便在运行时进行调整,而无需重新编译代码。

到此这篇关于基于SpringBoot+Redis实现一个简单的限流器的文章就介绍到这了,更多相关SpringBoot+Redis实现限流器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java模拟单链表和双端链表数据结构的实例讲解

    Java模拟单链表和双端链表数据结构的实例讲解

    这篇文章主要介绍了Java模拟单链表和双端链表数据结构的实例,注意这里的双端链表不是双向链表,是在单链表的基础上保存有对最后一个链接点的引用,需要的朋友可以参考下
    2016-04-04
  • java可以作为第一门编程语言学习吗

    java可以作为第一门编程语言学习吗

    在本篇内容里小编给JAVA零基础的网友分享一篇关于java可以作为第一门编程语言学习吗的文章,有兴趣的朋友们可以参考下。
    2020-11-11
  • Springboot文件上传出现找不到指定系统路径的解决

    Springboot文件上传出现找不到指定系统路径的解决

    这篇文章主要介绍了Springboot文件上传出现找不到指定系统路径的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 一篇文章从无到有详解Spring中的AOP

    一篇文章从无到有详解Spring中的AOP

    。Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的松耦合目的,这篇文章主要给大家介绍了关于Spring中AOP的相关资料,需要的朋友可以参考下
    2021-08-08
  • 详解Spring AOP 拦截器的基本实现

    详解Spring AOP 拦截器的基本实现

    本篇文章主要介绍了详解Spring AOP 拦截器的基本实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • SpringBoot前后端分离实现验证码操作

    SpringBoot前后端分离实现验证码操作

    验证码的功能是防止非法用户恶意去访问登录接口而设置的一个功能,今天我们就来看看在前后端分离的项目中,SpringBoot是如何提供服务的
    2022-05-05
  • 详解Java中的BigDecimal

    详解Java中的BigDecimal

    这篇文章主要介绍了Java中的BigDecimal的使用方法,帮助大家更好的理解和使用Java,感兴趣的朋友可以了解下
    2020-09-09
  • 浅析Spring IOC 依赖查找你需要知道的几种方式

    浅析Spring IOC 依赖查找你需要知道的几种方式

    这篇文章主要介绍了浅析Spring IOC 依赖查找的几种方式,Spring是Java面试中最常考的,学Java的小伙伴快来看看吧
    2021-08-08
  • 一篇文章带你深入了解javaIO基础

    一篇文章带你深入了解javaIO基础

    这篇文章主要介绍了java 基础知识之IO总结的相关资料,Java中的I/O分为两种类型,一种是顺序读取,一种是随机读取,需要的朋友可以参考下,希望对你有帮助
    2021-08-08
  • Spring整合Quartz Job以及Spring Task的实现方法

    Spring整合Quartz Job以及Spring Task的实现方法

    下面小编就为大家分享一篇Spring整合Quartz Job以及Spring Task的实现方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12

最新评论