请求接口方法限流的简单实现思路(RateLimiter)

1. 介绍

参考b站up主极海,实现一个简单的接口限流方案,基于IP+方法(粒度)实现,可以简单防止接口攻击,限制某个IP频繁访问接口某个方法等,避免恶意刷接口造成的服务器故障。

2. 实现方案

  • 采用aop思想实现,对全部controller层方法进行限制。
  • 限流方式:RateLimiter + Cache(Guava),采用Cache存储某个”ip+方法“对应的RateLimiterr,并设置过期策略。
2.1 导入jar包
<!--Guava-->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.1-jre</version>
</dependency>
<!--aop-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 测试接口
package com.example.webtest.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Janson
 * @Description 测试接口
 * @Date 2023/7/10
 */
@RestController
@RequestMapping("")
public class RatelimterTest {
    
    
    
    @GetMapping("testGet")
    public String testGet(){
    
    
        return "testGet";
    }
}
2.3 限流接口
package com.example.webtest.around;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.RateLimiter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

/**
 * @author Janson
 * @Description 简单接口限流方案
 * @Date 2023/7/10
 */
@Component
@Aspect
public class IpLimiterAspect {
    
    
    
    // 每秒创建一个令牌,令牌越多,每秒支持的请求次数越多。
    private int DEFAULT_LIMITER_COUNT_PER_SECOND = 1;
    // 创建guava缓存,给的过期时间,防止缓存数据量过大
    Cache<String, RateLimiter> limiterCache = CacheBuilder
            .newBuilder()
            .expireAfterAccess(10, TimeUnit.MINUTES)
            .build();
    @Autowired
    private HttpServletRequest httpServletRequest;
    
    /*
    * 匹配路径:* com.example.webtest.web..*.*(..)
    *       1. 第一个 * : 支持任意返回结果;
    *       2. web后面 .. , 表示覆盖web包及其子包,如果是 . ,则中覆盖web包下的接口,不包含web子包接口;
    *       3. 第二个 * :web包及其子包下所以类;
    *       4. 第三个 * : 所有方法;
    *       5. (..) : 表示方法可以支持任意参数。
    * */
    @Around("execution(* com.example.webtest.web..*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        // 如果采用了nginx代理,前端在请求时需要在header中写入真实ip: X-Real-IP = ip
        String ip = httpServletRequest.getHeader("X-Real-IP");
        // 没有采用nginx,则直接获取远程主机ip
        String remoteHost = httpServletRequest.getRemoteHost();
        Signature signature = joinPoint.getSignature();
        
        MethodSignature methodSignature = (MethodSignature) signature;
        // 获取类名称
        String name = joinPoint.getTarget().getClass().getName();
        // 获取方法名称
        String name2 = methodSignature.getName();
        String methodName = name + "." + name2;
        // 拼接缓存key
        String recodeKey = ip + "->" + methodName;
        // 尝试从缓存中获取key值为recodeKey 的缓存rateLimiter,如果获取不到,则创建一个rateLimiter
        RateLimiter rateLimiter = limiterCache.get(recodeKey,
                                                   () -> RateLimiter.create(DEFAULT_LIMITER_COUNT_PER_SECOND));
        // 尝试获取令牌,拿不到令牌则禁止访问。
        if (!rateLimiter.tryAcquire()){
    
    
            System.out.println("操作太频繁,限流了");
            return "操作失败";
        }
        return joinPoint.proceed();
    }

}

以上实现了一个简单的接口限流(防刷)方案,一般对于轻量级项目,可以解决接口防攻击问题。

猜你喜欢

转载自blog.csdn.net/qq_42102911/article/details/131632968