分布式锁与限流器
约 845 字大约 3 分钟
布欧-Lewyon
2026-05-15
分布式锁
基于 SETNX 的简易锁
public class SimpleRedisLock {
private final StringRedisTemplate redis;
private final String lockKey;
private final String lockValue; // UUID,防止误删
public boolean tryLock(long expireSeconds) {
this.lockValue = UUID.randomUUID().toString();
Boolean result = redis.opsForValue()
.setIfAbsent(lockKey, lockValue, expireSeconds, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
public void unlock() {
String script = """
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
else
return 0
end
""";
// Lua 保证原子性:只有锁的持有者才能释放
redis.execute(new DefaultRedisScript<>(script, Long.class),
List.of(lockKey), lockValue);
}
}// 使用
SimpleRedisLock lock = new SimpleRedisLock(redis, "lock:order:123");
try {
if (lock.tryLock(30)) {
// 业务逻辑
}
} finally {
lock.unlock();
}Redisson 分布式锁(推荐生产使用)
@Service
public class OrderService {
@Autowired
private RedissonClient redisson;
public void createOrder(Order order) {
RLock lock = redisson.getLock("lock:order:create:" + order.getUserId());
lock.lock(30, TimeUnit.SECONDS); // 锁 30 秒,看门狗自动续期
try {
// 1. 检查库存
int stock = checkStock(order.getProductId());
if (stock < order.getQuantity()) {
throw new RuntimeException("库存不足");
}
// 2. 扣减库存
deductStock(order.getProductId(), order.getQuantity());
// 3. 创建订单
saveOrder(order);
} finally {
lock.unlock();
}
}
// 公平锁(按请求顺序)
public void fairLockExample() {
RLock fairLock = redisson.getFairLock("lock:fair");
fairLock.lock();
try { /* 业务 */ } finally { fairLock.unlock(); }
}
// 联锁(同时锁定多个资源)
public void multiLockExample() {
RLock lock1 = redisson.getLock("lock:product:1");
RLock lock2 = redisson.getLock("lock:product:2");
RLock multiLock = redisson.getMultiLock(lock1, lock2);
multiLock.lock();
try { /* 跨资源事务 */ } finally { multiLock.unlock(); }
}
}秒杀场景
@PostMapping("/seckill")
public String seckill(Long productId, Long userId) {
String lockKey = "seckill:" + productId;
RLock lock = redisson.getLock(lockKey);
if (!lock.tryLock()) {
return "系统繁忙";
}
try {
// 1. 检查库存
Integer stock = Integer.parseInt(
redis.opsForValue().get("stock:" + productId));
if (stock <= 0) {
return "已售罄";
}
// 2. 扣库存(Lua 保证原子)
String lua = """
local stock = redis.call('DECR', KEYS[1])
if stock >= 0 then
return 1
else
redis.call('INCR', KEYS[1]) -- 恢复库存
return 0
end
""";
DefaultRedisScript<Long> script = new DefaultRedisScript<>(lua, Long.class);
Long success = redis.execute(script, List.of("stock:" + productId));
if (success != 1) {
return "已售罄";
}
// 3. 异步创建订单
sendOrderCreationTask(productId, userId);
return "抢购成功";
} finally {
lock.unlock();
}
}分布式限流器
滑动窗口(SortedSet)
@Component
public class SlidingWindowRateLimiter {
@Autowired
private StringRedisTemplate redis;
// 滑动窗口限流:窗口大小 60 秒,最多 10 次
public boolean allowRequest(String key, int maxRequests, long windowSeconds) {
String windowKey = "rate:" + key;
long now = System.currentTimeMillis();
long windowStart = now - windowSeconds * 1000;
// Lua 脚本原子执行
String lua = """
-- 1. 删除窗口外的旧记录
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[1])
-- 2. 统计当前窗口请求数
local count = redis.call('ZCARD', KEYS[1])
-- 3. 不超过限制则添加新记录
if tonumber(count) < tonumber(ARGV[2]) then
redis.call('ZADD', KEYS[1], ARGV[3], ARGV[3])
redis.call('EXPIRE', KEYS[1], ARGV[4])
return 1
else
return 0
end
""";
DefaultRedisScript<Long> script = new DefaultRedisScript<>(lua, Long.class);
Long result = redis.execute(script, List.of(windowKey),
String.valueOf(windowStart),
String.valueOf(maxRequests),
String.valueOf(now),
String.valueOf(windowSeconds));
return Long.valueOf(1).equals(result);
}
}// 使用:API 限流(每个用户每分钟 30 次)
if (!rateLimiter.allowRequest("api:user:" + userId, 30, 60)) {
throw new RuntimeException("请求过于频繁");
}令牌桶(Redisson)
@Service
public class TokenBucketRateLimiter {
@Autowired
private RedissonClient redisson;
public boolean tryAcquire(String resource, int permits) {
RRateLimiter limiter = redisson.getRateLimiter("limiter:" + resource);
// 初始化:每秒 100 个令牌
limiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.SECONDS);
return limiter.tryAcquire(permits);
}
// 等待直到获取令牌
public void acquire(String resource) {
RRateLimiter limiter = redisson.getRateLimiter("limiter:" + resource);
limiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.SECONDS);
limiter.acquire(); // 阻塞直到获取
}
}小结
| 方案 | 优点 | 缺点 | 推荐 |
|---|---|---|---|
SETNX + Lua | 轻量无依赖 | 无自动续期 | 简单场景 |
Redisson RLock | 自动续期、可重入、公平锁 | 依赖 Redisson | 生产推荐 |
| 滑动窗口 ZSet | 精确 | 内存消耗大 | 精确限流 |
Redisson RRateLimiter | 简单易用 | 需 Redisson | 通用限流 |
- 简易锁:
SET key NX EX + Lua释放。 - Redisson 锁:看门狗自动续期,防止业务未完成锁过期。
- 限流器:滑动窗口(精确)或令牌桶(平滑)。
上一节:分布式缓存 下一节:Session 共享与消息队列
