在高并发情况下,如果直接用 Redis 作为限流工具,可能会面临以下问题:
- Redis 承受不了高并发请求,短时间内大量
INCR
、ZADD
操作可能导致 Redis 过载。 - 网络压力大,所有限流请求都依赖 Redis,导致 网络 IO 和 Redis CPU 资源耗尽。
- 单点故障,如果 Redis 宕机,整个限流系统可能失效。
解决方案
为了避免 Redis 被打爆,建议采用 分层限流 方案,结合本地限流、缓存和异步队列,提高系统稳定性。
方案 1:本地限流 + Redis 限流
思路: 先用 JVM 本地限流(Guava RateLimiter、滑动窗口) 拦截大部分请求,减少 Redis 压力,只有必要时才访问 Redis。
具体实现
- 在应用服务器本地使用 Guava RateLimiter 进行 令牌桶限流:
RateLimiter rateLimiter = RateLimiter.create(1000); // 每秒 1000 个请求 if (!rateLimiter.tryAcquire()) {throw new RuntimeException("请求过多,请稍后再试"); }
- 仅在本地限流放行后,再使用 Redis 进行全局限流:
boolean allowed = redisRateLimiter.tryAcquire("rate_limit:user_123", 1000, 1); if (!allowed) {throw new RuntimeException("超出系统限流!"); }
优点
✅ 减少 Redis 压力,大部分请求本地拦截,不会打爆 Redis
✅ 减少网络 IO,不需要每次都访问 Redis
方案 2:分布式限流 + 本地缓存
思路: 先用 本地缓存(Caffeine、Guava Cache) 记录最近请求状态,减少 Redis 访问次数。
具体实现
- 用本地缓存存储用户的请求记录
Cache<String, Integer> cache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.SECONDS) // 1 秒过期.maximumSize(100000) // 限制存储大小.build();
- 每次请求先查询本地缓存,如果超过阈值,直接拒绝
String key = "rate_limit:user_123"; Integer count = cache.getIfPresent(key); if (count != null && count >= 100) {throw new RuntimeException("限流中"); } cache.put(key, (count == null ? 1 : count + 1));
- 如果本地缓存没拦截,则访问 Redis
boolean allowed = redisRateLimiter.tryAcquire(key, 100, 1); if (!allowed) {throw new RuntimeException("超出 Redis 限流"); }
优点
✅ 减少 Redis 访问次数,只有本地缓存没拦截时才访问 Redis
✅ 加速请求处理,本地缓存读写比 Redis 快
方案 3:Redis 限流 + Lua 脚本原子操作
思路: 直接用 Lua 脚本保证限流逻辑的原子性,减少 Redis 服务器的多次
INCR
/EXPIRE
调用,避免 Redis 负载过高。
Lua 脚本(限流 + 过期)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local expire_time = tonumber(ARGV[2])local current = redis.call("INCR", key)
if current == 1 thenredis.call("EXPIRE", key, expire_time)
endif current > limit thenreturn 0
elsereturn 1
end
Java 调用
String luaScript = "..."; // 上述 Lua 脚本
List<String> keys = Collections.singletonList("rate_limit:user_123");
List<String> args = Arrays.asList("100", "1"); // 100 次/秒
Long result = (Long) jedis.eval(luaScript, keys, args);
if (result == 0) {throw new RuntimeException("请求过多,限流!");
}
优点
✅ Redis 端一次执行完成,不会有多个请求打爆 Redis
✅ 高并发情况下仍然保持原子性
方案 4:Redis + Kafka/MQ 异步削峰
思路: 让高并发请求先进入 消息队列(Kafka、RabbitMQ),然后 消费者限流消费,防止 Redis 被打爆。
具体实现
- 请求先进入 Kafka/RabbitMQ 队列
rabbitTemplate.convertAndSend("rate_limit_queue", "user_123");
- 消费者限流处理
@RabbitListener(queues = "rate_limit_queue", concurrency = "2") public void process(String userId) {boolean allowed = redisRateLimiter.tryAcquire("rate_limit:" + userId, 100, 1);if (allowed) {System.out.println("请求通过:" + userId);} else {System.out.println("请求被限流:" + userId);} }
优点
✅ 请求不会直接打爆 Redis,而是排队处理
✅ 能够处理突发流量,削峰填谷
总结
方案 | 适用场景 | 主要优点 |
---|---|---|
本地限流 + Redis | 高并发 API | 先本地拦截,减少 Redis 访问 |
本地缓存 + Redis | 频繁调用的限流 | 减少 Redis 请求,提高性能 |
Redis Lua 脚本 | 原子性限流 | 避免多次 Redis 操作,降低负载 |
Redis + MQ | 超高并发 | 用消息队列削峰,防止 Redis 爆炸 |
推荐组合:
高并发 场景下,建议 本地限流 + Redis 限流 + MQ 削峰 结合使用,达到最优效果!