java学习之使用redis简单实现限流功能

事先声明基于redis实现滑动时间窗实现限流这种方式我们工作中一般不会使用,因为目前市面上对于大体量的服务使用的都是springCloud netFlix或者spring CloundAlibaba(主流)他们都有各自的限流组件( Netflix Hystrix , sentinel), 小体量服务又基本用不到限流,这里可以当成是时间窗口算法的一种学习

滑动时间窗口限流是一种基于时间的流量控制策略,本质就是规定一个时间大小的窗口,值统计在这个窗口内的请求,然后滑动这个窗口;至于怎么滑动窗口,可以那请求的当前时间-设置的窗口大小来完成

具体实现:

主要步骤:

1.定义窗口的大小,如30秒

2.每次收到请求,就定义一个zset存储到redis中

3.然后通过ZREMVRANGBYSCORE命令来删除窗口起始时间之前的数据

4.最后通过ZCARD命令获取zset集合中的成员数量,及窗口内的请求量

public class SlidingWindowRateLimiter {
    private final Jedis jedis;
    public SlidingWindowRateLimiter(Jedis jedis) {
        this.jedis = jedis;
    }
    public boolean isAllowed(String key, int limit, long windowSize, long currentTime) {
        // 计算时间窗口的起始时间
        long windowStart = currentTime - windowSize;
        // 移除时间窗口之前的数据
        jedis.zremrangeByScore(key, "-inf", String.valueOf(windowStart));
        // 获取当前时间窗口内的请求数量
        long currentRequests = jedis.zcount(key, "-inf", "+inf");
        // 判断请求数量是否超过限制
        if (currentRequests < limit) {
            // 添加当前请求的时间戳到有序集合
            jedis.zadd(key, currentTime, String.valueOf(currentTime));
            return true;  // 请求通过限流
        } else {
            return false;  // 请求被限流
        }
    }
}

这里会存在并发问题建议使用lua脚本实现

package com.tuxing.re.annotation;

import redis.clients.jedis.Jedis;

public class SlidingWindowRateLimiter {
    private Jedis jedis;
    private String key;
    private int limit;
    public SlidingWindowRateLimiter(Jedis jedis, String key, int limit) {
        this.jedis = jedis;
        this.key = key;
        this.limit = limit;
    }
    public boolean isAllowed(String key) {
        // 当前时间
        long currentTime = System.currentTimeMillis();
        // 使用lua脚本保证原子性
        String script = "local window_start = ARGV[1] - 30000\n" +
                "redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', window_start)\n" +
                "local current_requests = redis.call('ZCARD', KEYS[1])\n" +
                "if current_requests < tonumber(ARGV[2]) then\n" +
                "    redis.call('ZADD', KEYS[1], ARGV[1], ARGV[1])\n" +
                "    return 1\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        Object result = jedis.eval(script, 1, key, String.valueOf(currentTime), String.valueOf(limit));

        return (int) result == 1;
    }
}

使用示例:

package com.tuxing.re.annotation;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author ln
 * @version 1.0
 * @className RateLimitFilter
 * @description 基础描述:
 * @date 2024/5/31 10:49
 */
@Component
public class RateLimitFilter implements Filter {

    public static final Integer LIMIT = 100;// 限制次数

    public static final Integer WINDOW_SIZE_SECONDS = 30;// 窗口大小
    @Autowired
    private SlidingWindowRateLimiter slidingWindowRateLimiter;

    @Resource(name = "againRedisTemplate")
    private RedisTemplate<String, Object> redisTemplate;


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String apiKey = httpRequest.getHeader("X-API-KEY");
        boolean allowed = slidingWindowRateLimiter.isAllowed(apiKey);
        if (allowed) {
            httpResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            httpResponse.getWriter().write("too many request");
        }
    }
}
  • 9
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是基于JavaRedis实现令牌桶限流的代码示例: ```java import redis.clients.jedis.Jedis; import redis.clients.jedis.Response; import redis.clients.jedis.Transaction; public class RedisTokenBucket { private static final String REDIS_KEY = "token_bucket"; private static final int MAX_TOKENS = 100; // 令牌桶容量 private static final int REFILL_RATE = 10; // 每秒添加的令牌数 public boolean allowRequest() { Jedis jedis = new Jedis("localhost"); // 连接本地的Redis服务器 try { Transaction transaction = jedis.multi(); transaction.zremrangeByScore(REDIS_KEY, "-inf", String.valueOf(System.currentTimeMillis() - 1000)); Response<Long> countResponse = transaction.zcard(REDIS_KEY); transaction.zadd(REDIS_KEY, System.currentTimeMillis(), String.valueOf(System.currentTimeMillis())); transaction.zrange(REDIS_KEY, 0, -1); transaction.expire(REDIS_KEY, 1); transaction.exec(); long count = countResponse.get(); return count <= MAX_TOKENS; } finally { jedis.close(); } } } ``` 该代码使用Redis的有序集合(Sorted Set)来实现令牌桶限流。每个请求在处理之前都需要从令牌桶中获取一个令牌,只有拿到令牌才能进行其他的业务逻辑。令牌桶的容量为100,每秒添加10个令牌。当令牌桶满时,新的令牌会被丢弃或拒绝。 使用示例: ```java public class Main { public static void main(String[] args) { RedisTokenBucket tokenBucket = new RedisTokenBucket(); for (int i = 0; i < 10; i++) { if (tokenBucket.allowRequest()) { System.out.println("Request " + (i + 1) + ": Allowed"); } else { System.out.println("Request " + (i + 1) + ": Denied"); } } } } ``` 运行上述代码,将输出类似以下结果: ``` Request 1: Allowed Request 2: Allowed Request 3: Allowed Request 4: Allowed Request 5: Allowed Request 6: Denied Request 7: Denied Request 8: Denied Request 9: Denied Request 10: Denied ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值