基于Redis限流方案(并发数控制)

为满足项目中特定用户的多产品并发控制需求,本文详细介绍了如何利用计数器模式结合Redis实现有效的流量控制。通过具体代码示例,展示了如何在高并发场景下确保线程安全,同时限制最大并发数,防止系统过载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近项目上需要做一个针对某用户下的某产品的限流需求,要求可配置,即时成效,也就是说多个用户下多个产品要有不同的并发控制。

由于之前并没有这方面的经验,经过一番查阅资料,发现令牌桶算法可以非常有效的控制流量,而且又有谷歌的guava提供的基于令牌桶算法的实现类RateLimiter(令牌桶算法这里不做描述,需要了解可以自行Google),但是由于不能满足项目上的需求,经过内部讨论并没有使用。

最终方案

最终采用方式也非常简单粗暴——计数器模式;原理非常简单,用户进入时,计数器+1,退出后-1,但是要做好线程同步,否者计数器很有可能变成负数。

这里的计时器采用Redis作为缓存,根据用户+产品作为key,缓存当前并发量,大致代码如下:

@Autowired
private RedisUtils redisUtils;

@Test
public void contextLoads() {

    String key = "1000_0001";
    Object object = new Object();
    for (int i = 0; i < 7; i++) {     // 请求数 7
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    int current = Integer.valueOf(redisUtils.get(key));    //初始值 0
                    if (current < 5) {   // 限流5
                        redisUtils.increment(key);     // 缓存并发数 +1
                    } else {
                        System.out.println("并发超过5,请稍后再试");    // 熔断
                        return;
                    }
                }
                try {
                    Thread.sleep(3000);     // 业务逻辑
                    redisUtils.decrement(key);      // 释放并发数 -1
                    System.out.println("当前并发" + redisUtils.get(key));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    while (true) {

    }
}

运行结果:

可以看到,想要的效果已经实现了,接下来看下Redis中的并发量:

多并发测试

将此方案带到业务代码中,并将最大并发量配置成5,使用Jmeter开启10个线程,测试一下结果

后话

经过大量测试后,发现此方式能有效的控制并发,但是并不意味着这是最好的处理方式,由于操作缓存需要用到线程同步,可能性能方面会大打折扣,而且若出现网络或其他故障,极有可能导致缓存中的并发量不能减掉。

本人还将持续更新此博客,记录后续可能遇到的问题或一些可优化的地方。

本博客内容均为原创,转载请注明出处。

<think>好的,我现在要帮用户找到用Java实现基于Redis的令牌桶算法进行限流的示例或方法。首先,我需要回忆一下用户提供的引用内容,看看有没有相关的信息。用户提到了三个引用,其中引用[1]和[2]都是关于Bucket4j的,这是一个Java的令牌桶库,但可能它本身是基于内存的,而不是基于Redis的。引用[3]提到计数器算法,可以用Redis的INCR和EXPIRE命令,以及Lua脚本实现分布式限流,但用户现在需要的是令牌桶算法,不是计数器。 首先,我需要确定Bucket4j是否支持Redis作为后端存储。根据引用[1]和[2]的描述,Bucket4j是一个基于令牌桶的库,但示例中是在Spring Boot中使用,可能默认是单机的。可能需要查看Bucket4j的文档是否有分布式支持,比如使用Redis。不过,根据我的了解,Bucket4j本身主要提供本地限流,如果需要分布式,可能需要扩展或者结合其他工具。 用户的需求是使用JavaRedis实现令牌桶算法。可能需要手动实现,或者找到结合两者的库。如果Bucket4j不支持Redis,那么可能需要自己编写代码,利用Redis的特性,如Lua脚本来保证原子性操作。 接下来,我需要考虑令牌桶算法在Redis中的实现方式。令牌桶的基本原理是系统以固定速率向桶中添加令牌,请求需要获取令牌才能被处理,否则被限流。在分布式环境中,需要确保操作的原子性,避免并发问题。Redis的Lua脚本可以在服务端原子执行,适合处理这种情况。 可能的步骤包括: 1. 使用Redis存储桶的当前令牌数和最后填充时间。 2. 每次请求到来时,通过Lua脚本计算自上次填充以来应添加的令牌数,更新当前令牌数。 3. 如果令牌足够,则扣除令牌,允许请求;否则拒绝。 然后,我需要查找是否有现有的Java库或示例代码实现这个逻辑。如果Bucket4j不支持,可能需要自己编写。例如,用户提供的引用[3]提到使用Redis的INCR和EXPIRE,但那是计数器算法。令牌桶需要更复杂的时间计算和令牌补充逻辑。 现在,考虑如何用JavaRedis实现。可能需要编写一个Lua脚本,处理令牌的计算和扣除。例如,Lua脚本可以接受参数,如当前时间、填充速率、桶容量等,然后计算可用令牌,更新Redis中的值,并返回是否允许请求。 接着,思考如何将这个过程整合到Java代码中。可以使用Spring Data Redis或者Jedis等库来执行Lua脚本。需要处理脚本的加载和执行,以及可能的异常情况。 此外,还需要考虑性能问题,例如减少Redis的调用次数,确保脚本的高效性。Lua脚本在Redis中执行是单线程的,所以不会有并发问题,但需要保证脚本尽量快速执行。 最后,可能需要参考现有的实现或文档,看看有没有类似的例子。例如,可能有人已经写过类似的博客或GitHub项目,用RedisJava实现令牌桶。但由于用户提供的引用中没有直接相关的例子,可能需要根据算法逻辑自行实现。 总结,用户需要的是基于Redis的分布式令牌桶限流实现,而现有的Bucket4j可能不直接支持,因此需要结合Redis的Lua脚本和Java客户端来实现。接下来,我应该提供一个示例的步骤和代码,展示如何用Lua脚本和Java代码实现这一机制,并解释关键部分的工作原理。</think>针对您的问题,这里提供一个基于Redis和Lua脚本实现的Java令牌桶限流方案: ### 实现步骤说明 1. **核心算法原理** 令牌桶算法以固定速率生成令牌,请求需获取令牌才能执行。数学表达式为: $$ tokens = \min(capacity, tokens + (currentTime - lastRefillTime) \times rate) $$ 其中$capacity$是桶容量,$rate$是令牌生成速率(个/毫秒)[^3] 2. **Redis数据结构设计** ```bash KEY: rate_limit:{bucket_key} VALUE: { "tokens": 10, # 当前令牌数 "lastRefill": 1672531200000 # 最后刷新时间戳(毫秒) } ``` 3. **Lua脚本实现(token_bucket.lua)** ```lua local key = KEYS[1] local requested = tonumber(ARGV[1]) local capacity = tonumber(ARGV[2]) local rate = tonumber(ARGV[3]) local now = tonumber(ARGV[4]) local data = redis.call("HMGET", key, "tokens", "lastRefill") local tokens = tonumber(data[1]) or capacity local lastRefill = tonumber(data[2]) or now -- 计算时间间隔内生成的令牌 local delta = math.max(now - lastRefill, 0) local newTokens = delta * rate tokens = math.min(tokens + newTokens, capacity) -- 判断令牌是否足够 if tokens >= requested then tokens = tokens - requested redis.call("HMSET", key, "tokens", tokens, "lastRefill", now) redis.call("PEXPIRE", key, 86400000) -- 设置24小时过期 return 1 -- 允许通过 else redis.call("HMSET", key, "tokens", tokens, "lastRefill", lastRefill) return 0 -- 限流 end ``` 4. **Java调用示例(Spring Boot)** ```java @Service public class RateLimitService { private static final String LUA_SCRIPT = "token_bucket.lua"; @Autowired private StringRedisTemplate redisTemplate; private DefaultRedisScript<Long> redisScript; @PostConstruct public void init() { redisScript = new DefaultRedisScript<>(); redisScript.setLocation(new ClassPathResource(LUA_SCRIPT)); redisScript.setResultType(Long.class); } public boolean allowRequest(String key, int capacity, double ratePerSecond) { List<String> keys = Collections.singletonList("rate_limit:" + key); double ratePerMillis = ratePerSecond / 1000.0; long now = System.currentTimeMillis(); return redisTemplate.execute(redisScript, keys, 1, // 请求1个令牌 capacity, // 桶容量 ratePerMillis, now ) == 1L; } } ``` ### 关键特性说明 1. **原子性保证**:通过Lua脚本确保令牌计算和扣除的原子操作[^3] 2. **时间精度**:使用毫秒级时间计算令牌生成量 3. **自动过期**:超过24小时未使用的限流桶自动清除 4. **参数可配置**:支持动态调整速率(ratePerSecond)和容量(capacity) ### 实际应用示例 ```java // 接口限流配置:每秒10个请求,桶容量20 @RestController public class ApiController { @Autowired private RateLimitService rateLimitService; @GetMapping("/api") public ResponseEntity<String> apiEndpoint(@RequestParam String userId) { if (!rateLimitService.allowRequest(userId, 20, 10.0)) { return ResponseEntity.status(429).body("请求过快"); } return ResponseEntity.ok("业务数据"); } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值