RRateLimiter的使用

Redisson 的 RRateLimiter 是一个分布式限流器,用于在分布式系统中控制访问速率。

基本用法

导入jar

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

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.24.3</version>
        </dependency>

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

配置类

package com.qfedu.ratelimit.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient getRedisson() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://127.0.0.1:6379")
                .setRetryInterval(5000)
                .setTimeout(10000)
                .setConnectTimeout(10000);
        return Redisson.create(config);
    }
}

通过控制器测试

@RestController
public class TestController {

    @Resource
    private RedissonClient redissonClient;

    @GetMapping("/test")
    public String test() {
        // 根据key获取限流器实例
        RRateLimiter rateLimiter = redissonClient.getRateLimiter("limit:1");
        // 初始化限流器,本例表示5秒内产生3个许可     
        rateLimiter.trySetRate(RateType.OVERALL, 3, 5, RateIntervalUnit.SECONDS);

        // 尝试获取许可,获取到返回true,否则返回false
        boolean ret1 = rateLimiter.tryAcquire(1);
        System.out.println(ret1);
        boolean ret2 = rateLimiter.tryAcquire(1);
        System.out.println(ret2);
        boolean ret3 = rateLimiter.tryAcquire(1);
        System.out.println(ret3);
        // 第四次获取许可,返回false,表示不能访问
        boolean ret4 = rateLimiter.tryAcquire(1);
        System.out.println(ret4);
        return "success";
    }

}

关于trySetRate的说明

其中trySetRaet()的第一个参数表示RateType有两个值:

  • RateType.OVERALL: 全局限流(所有实例共享)

  • RateType.PER_CLIENT: 按客户端限流(需要配置客户端标识)

第二个参数表示限流的最大次数

第三个参数表示限流的时间间隔

第四个参数表示限流的时间间隔的单位

通过查看源码,其内部执行了如下的lua脚本

redis.call('hsetnx', KEYS[1], 'rate', ARGV[1]);
redis.call('hsetnx', KEYS[1], 'interval', ARGV[2]);
return redis.call('hsetnx', KEYS[1], 'type', ARGV[3]);

其内部使用hsetnx,如果对应的key和field存在,则不会创建field

通过自定义注解和AOP进行封装

工具类

package com.qfedu.ratelimit.aspect;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class IpAddressUtil {

    /**
     * 获取客户端真实IP地址
     */
    public static String getClientIpAddress() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String[] headers = {
                "X-Forwarded-For",
                "X-Real-IP",
                "Proxy-Client-IP",
                "WL-Proxy-Client-IP",
                "HTTP_CLIENT_IP",
                "HTTP_X_FORWARDED_FOR"
        };

        // 1. 检查各种代理头信息
        for (String header : headers) {
            String ip = request.getHeader(header);
            if (isValidIp(ip)) {
                return getFirstIp(ip);
            }
        }

        // 2. 直接获取远程地址
        String remoteAddr = request.getRemoteAddr();
        if (isValidIp(remoteAddr)) {
            return remoteAddr;
        }

        throw new RuntimeException("无法获取远程ip地址");
    }

    /**
     * 验证IP地址是否有效
     */
    private static boolean isValidIp(String ip) {
        return ip != null &&
                !ip.isEmpty() &&
                !"unknown".equalsIgnoreCase(ip) &&
                !"0:0:0:0:0:0:0:1".equals(ip) &&
                !"127.0.0.1".equals(ip);
    }

    /**
     * 处理多个IP的情况(如X-Forwarded-For: client, proxy1, proxy2)
     */
    private static String getFirstIp(String ip) {
        if (ip.contains(",")) {
            String[] ips = ip.split(",");
            for (String singleIp : ips) {
                String trimmedIp = singleIp.trim();
                if (isValidIp(trimmedIp)) {
                    return trimmedIp;
                }
            }
        }
        return ip.trim();
    }
}
package com.qfedu.ratelimit.aspect;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class TokenUtils {

    /**
     * 通过token获取用户id
     *
     * @return
     */
    public static Integer getUidFromToken() {
        // 动态获取HttpServletRequest对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        String token = request.getHeader("Authorization");
//        if (token == null || token.isEmpty()) {
//            throw new RuntimeException("请重新登录");
//        }
//        Claims claims = JwtUtils.parseJWT(token);
//        Object uid = claims.get("uid");
//        return Integer.valueOf(uid.toString());

        // 本例为了方便测试,写死返回的用户id
        return 123;
    }
}

枚举

package com.qfedu.ratelimit.aspect;


public enum RateLimitTypeEnum {

    TYPE_IP(1, "根据IP限流"),
    TYPE_USER(2, "根据用户限流");

    private int type;
    private String desc;

    RateLimitTypeEnum(int type, String desc) {
        this.type = type;
        this.desc = desc;
    }
}

自定义注解

package com.qfedu.ratelimit.aspect;

import org.redisson.api.RateIntervalUnit;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {

    RateLimitTypeEnum type() default RateLimitTypeEnum.TYPE_USER;

    // String key() default "";

    long rate() default 10;

    long interval() default 60;

    RateIntervalUnit unit() default RateIntervalUnit.SECONDS;

    String message() default "请求过于频繁,请稍后再试";
}

切面类

package com.qfedu.ratelimit.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class RateLimitAspect {

    @Autowired
    private RedissonClient redissonClient;

    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {

        String suffix = null;
        if (rateLimit.type() == RateLimitTypeEnum.TYPE_IP) {
            suffix = IpAddressUtil.getClientIpAddress();
        } else if (rateLimit.type() == RateLimitTypeEnum.TYPE_USER) {
            suffix = TokenUtils.getUidFromToken().toString();
        }

        String key = "rate:limit:" + suffix;

        RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
        rateLimiter.trySetRate(RateType.OVERALL,
                rateLimit.rate(),
                rateLimit.interval(),
                rateLimit.unit());

        if (!rateLimiter.tryAcquire()) {
            throw new RuntimeException(rateLimit.message());
        }

        return joinPoint.proceed();
    }

}

测试控制层方法

    @RateLimit(type = RateLimitTypeEnum.TYPE_USER, rate = 10, interval = 60)
    @GetMapping("/test2")
    public String test2() {
        return "success";
    }

根据配置,执行到第11次时,报错

RRateLimiter使用的限流算法

RRateLimiter 使用的是令牌桶算法(Token Bucket Algorithm)

令牌桶算法的工作机制:

  1. 令牌生成:系统以固定速率向桶中添加令牌

  2. 令牌消耗:请求需要获取令牌才能被处理

  3. 桶容量:桶有最大容量,防止令牌无限累积

  4. 限流逻辑:当桶中有足够令牌时请求通过,否则被限流

令牌桶 vs 漏桶算法

特性令牌桶算法漏桶算法
突发流量允许一定程度的突发严格限制,平滑输出
灵活性更灵活,可应对突发更严格,输出恒定
实现复杂度相对简单相对复杂
适用场景API限流、流量控制网络流量整形

漏桶算法(Leaky Bucket Algorithm)核心概念:
漏桶算法是一种流量整形算法,它以恒定的速率处理请求,无论输入流量多么不规则。

工作机制:
请求到达,请求以任意速率进入桶中,相当于水流入,水量增加
恒定处理,以固定速率从桶中取出请求处理,相当于水流出,水以恒定速率从底部漏出
溢出拒绝:当桶满时,新请求被丢弃或拒绝,类似水满则溢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值