【Java-EE进阶】SpringBoot针对某个IP限流问题

目录

简介

[1. 使用Guava的RateLimiter实现限流](#1. 使用Guava的RateLimiter实现限流)

添加Guava依赖

实现RateLimiter限流逻辑

限流管理类

控制器中应用限流逻辑

[2. 使用计数器实现限流](#2. 使用计数器实现限流)

限流管理类

控制器中应用限流逻辑


简介

针对某个IP进行限流以防止恶意点击是一种常见的反爬虫和防止DoS的措施。限流策略通过对某个IP的访问频率进行控制,防止恶意用户对应用造成负面的影响。

以下是实现限流的步骤和方法,在Java后端通常这样实现:

1. 使用Guava的RateLimiter实现限流

Guava库提供了一个简单而高效的限流工具:RateLimiter,可以方便的实现针对IP的访问频率控制。

添加Guava依赖

首先,在pom.xml文件中添加Guava依赖:

复制代码
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1-jre</version>
</dependency>
实现RateLimiter限流逻辑
限流管理类

创建一个类来管理针对IP的限流:

复制代码
import com.google.common.util.concurrent.RateLimiter;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class RateLimiterManager {

    private final ConcurrentMap<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();
    private final double permitsPerSecond = 1.0; // 每秒允许1次请求

    public boolean tryAcquire(String ip) {
        RateLimiter rateLimiter = rateLimiterMap.computeIfAbsent(ip, k -> RateLimiter.create(permitsPerSecond));
        return rateLimiter.tryAcquire();
    }
}
控制器中应用限流逻辑

在Spring Boot控制器中应用限流逻辑:

复制代码
import com.xfusion.rate1.limit.RateLimiterManager;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@RestController
@RequestMapping("/test")
public class TestController {
    private final RateLimiterManager rateLimiterManager=new RateLimiterManager();
    @RequestMapping("/t1")
    public void test1(HttpServletRequest request, HttpServletResponse response) throws IOException {
       String param=request.getRemoteAddr();
       if(rateLimiterManager.tryAcquire(param)) {
          response.getWriter().write("Request processed for " + param);
       } else {
           response.setStatus(HttpServletResponse.SC_FORBIDDEN);
           response.getWriter().write("Request limit for " + param);
        }
    }
}

private final ConcurrentMap<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();
  • ConcurrentMap<String, RateLimiter>

    • 使用ConcurrentMap来存储每个IP的限流器(RateLimiter)。
    • ConcurrentMap接口允许高效地进行并发访问和更新,确保线程安全。
  • new ConcurrentHashMap<>()

    • 实例化一个ConcurrentHashMap,它是ConcurrentMap的常用实现。这种数据结构支持线程安全的读写操作,适合限流场景。

      private final double permitsPerSecond = 1.0; // 每秒允许1次请求

  • permitsPerSecond

    • 定义一个double类型的常量permitsPerSecond,值为1.0

    • 表示每秒允许1次请求的限流速率。RateLimiter根据此值来创建相应的限流器。

      RateLimiter rateLimiter = rateLimiterMap.computeIfAbsent(ip, k -> RateLimiter.create(permitsPerSecond));

  • computeIfAbsent

    • computeIfAbsent方法用于检查map中是否已经存在为该IP准备的RateLimiter
    • 若不存在,则使用k -> RateLimiter.create(permitsPerSecond)创建一个新的RateLimiter。这个lambda部分表示为未存在的IP创建一个新的RateLimiter,限流速率为permitsPerSecond
  • RateLimiter.create(permitsPerSecond)

    • 调用RateLimiter类的静态方法create,以指定速率创建一个新的限流器实例。这使得每秒最多处理一个请求。

      return rateLimiter.tryAcquire();

  • rateLimiter.tryAcquire()

    • 尝试获取一个请求许可。在给定的限流速率范围内,如果成功获取许可,则返回true,否则返回false

    • tryAcquire使得在请求达到速率限制时,予以限制,而不使请求排队。

      response.setStatus(HttpServletResponse.SC_FORBIDDEN);

如果我们发现请求达到了上限的时候,设置相应的状态码,然后前端会根据相应的状态码来完成请求,同时就防止这个ip然后再进行访问。

2. 使用计数器实现限流

计数器限流比较简单,通过记录每个IP的请求次数并在指定时间窗口内进行限流。

限流管理类

创建一个类来管理限流逻辑:

复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Configuration
@Component
public class CounterRateLimiter {
    private final ConcurrentHashMap<String, AtomicInteger> requestCounts = new ConcurrentHashMap<>();
    private final int maxRequestsPerMinute = 10;

    public boolean tryAcquire(String ipAddress) {
        AtomicInteger requestCount = requestCounts.computeIfAbsent(ipAddress, k -> new AtomicInteger(0));
        int currentCount = requestCount.incrementAndGet();
        log.info("IP: " + ipAddress + ", Current Count: " + currentCount);
        return currentCount <= maxRequestsPerMinute;
    }
    public void resetCounts() {
        log.info("Reset counts");
        requestCounts.clear();
    }

    @Scheduled(fixedRate = 10000)
    public void resetCountsScheduled() {
        log.info("刷新这个ip的次数");
        resetCounts();
    }
}

在开启定时任务的时候,要在Application上添加上@EnableScheduling这个注解

复制代码
@SpringBootApplication
@Configuration
@EnableScheduling
public class Rate1Application {

    public static void main(String[] args) {
        SpringApplication.run(Rate1Application.class, args);
    }

}
控制器中应用限流逻辑

在Spring Boot控制器中应用限流逻辑,并定期重置计数器:

复制代码
private final CounterRateLimiter counterRateLimiter;
    public TestController(CounterRateLimiter counterRateLimiter) {
        this.counterRateLimiter = counterRateLimiter;
    }

    @RequestMapping("/t2")
    public void test2(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String param = request.getRequestURI();
        if (counterRateLimiter.tryAcquire(param)) {
            log.info("打印日志1");
            response.getWriter().write("Request processed for test2 " + param);
        } else {
            log.info("打印日志2");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.getWriter().write("Request limit for test2" + param);
        }
    }

@Scheduled(fixedRate = 10000)
public void resetCountsScheduled() {
    log.info("刷新这个ip的次数");
    resetCounts();
}
  • @Scheduled(fixedRate = 10000) :注解用于配置定时任务,每10秒执行一次。
    • fixedRate:设定定时任务的执行频率,这里设置为每10000毫秒(即每10秒)。
  • resetCountsScheduled 方法 :定时任务调用 resetCounts 方法清空计数器。
    • 日志记录:记录定时任务执行。

    • 调用 resetCounts :每10秒自动调用 resetCounts 方法重置计数器。

      public void resetCounts() {
      log.info("Reset counts");
      requestCounts.clear();
      }

resetCounts:用于重置计数器。

  • 日志记录:记录重置计数器操作。
  • clear :清空 requestCounts 中的所有键值对,重置所有IP的计数器
相关推荐
黑马源码库miui520865 分钟前
JAVA同城打车小程序APP打车顺风车滴滴车跑腿源码微信小程序打车源码
java·微信·微信小程序·小程序·uni-app
MadPrinter12 分钟前
SpringBoot学习日记 Day11:博客系统核心功能深度开发
java·spring boot·后端·学习·spring·mybatis
dasseinzumtode12 分钟前
nestJS 使用ExcelJS 实现数据的excel导出功能
前端·后端·node.js
淦出一番成就15 分钟前
Java反序列化接收多种格式日期-JsonDeserialize
java·后端
Java中文社群17 分钟前
Hutool被卖半年多了,现状是逆袭还是沉寂?
java·后端
程序员蜗牛1 小时前
9个Spring Boot参数验证高阶技巧,第8,9个代码量直接减半!
后端
yeyong1 小时前
咨询kimi关于设计日志告警功能,还是有启发的
后端
库森学长1 小时前
2025年,你不能错过Spring AI,那个汲取了LangChain灵感的家伙!
后端·openai·ai编程
爱吃苹果的日记本1 小时前
开学第一课
java
Java水解1 小时前
Spring Boot 启动流程详解
spring boot·后端