限制IP对接口的调用频率_基于Redis实现

Ashe / 2023-08-01 / 原文

pom.xml引入依赖

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

限制调用频率逻辑实现

	private boolean limitIpCallThis(String key) {
		Long increment = stringRedisTemplate.opsForValue().increment(key, 1);
		switch (increment.intValue()) {
			case 1:
				// 第一次调用,初始化过期时间60分钟
				stringRedisTemplate.expire(key, 60, TimeUnit.MINUTES);
				break;
			case 5:
				// 达到阈值后,刷新过期时间为5分钟
				stringRedisTemplate.expire(key, 5, TimeUnit.MINUTES);
				break;
			default:
				break;
		}
		return increment > 5;
	}

由请求方IP组成Redis的key,请求次数自增1。达到阈值5,即可通过此方法返回的true来提示请求者操作频繁

String.format("您的操作过于频繁,请%s分钟后再试! ", stringRedisTemplate.getExpire(key, TimeUnit.MINUTES))

这段代码可以放入Controller层的入口,也可结合AOP切面编程,给接口做调用频率限制。

AOP依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

 自定义注解

/**
 * 接口调用次数限制
 */
@Target(ElementType.METHOD) // 作用于方法
@Retention(RetentionPolicy.RUNTIME) // 生命周期
public @interface Limit {

    /**
     * 阈值,达到阈值拒绝请求
     */
    long threshold() default 0;

    /**
     * 重置时间,重置时间结束方解除限制
     */
    long reset() default 0;

    /**
     * 重置时间单位,默认分钟,可自定义设置
     */
    TimeUnit unit() default MINUTES;

}

 切面逻辑

    @Around("@annotation(xxxx.annotation.Limit)")
    public Object limit(ProceedingJoinPoint joinPoint) throws Throwable {
        // 取出注解实例
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Limit limit = method.getAnnotation(Limit.class);
        // TODO 根据接口Limit实例的阈值和重置时间做对应处理 (改动上文的limitIpCallThis方法,当该方法返回true抛出异常即可)
        ...
        return joinPoint.proceed();
    }

 使用示例

 

个人思考

如果多个人在同一公司使用相同的公用网络,他们的请求经过网络后会共享一个公共IP地址。在这种情况下,他们的请求将具有相同的公共IP地址。

如果公司使用的是代理服务器或者NAT(Network Address Translation)技术,每个人在同一公司内使用的计算机可能具有不同的内部IP地址。当请求经过代理服务器或者NAT时,这些内部IP地址会被映射到一个公共IP地址上。在这种情况下,每个人的请求可能具有不同的IP地址。

我所使用的场景是发送手机验证码接口,因此在Redis的key上,我不仅使用了IP还使用了phoneNumber,在应对不同的请求者具有相同IP的极端情况下,可巧妙避开由此引发的异常。当然,也可使用jwtToken组成Redis的key。

由此我想起之前调用亚马逊某个接口时,其限流措施就是每天对应token的使用次数,由于每个账号的token不同,那么这个token就是对应的业务主键,以此做基准来限制调用频率。