Spring Boot高并发设计:基于Redisson的分布式锁与限流实战

Spring Boot分布式锁选型与Redisson集成

微服务架构下,多个服务实例并发操作共享资源是常见问题。Spring Boot生态中分布式锁实现有多种选择:基于数据库的悲观锁、ZooKeeper临时节点、Redis SETNX。Redisson在Redis基础上提供了可重入锁、公平锁、读写锁等完整实现,API简洁且性能优异,是Spring Boot项目的首选方案。

Maven依赖引入:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.34.0</version>
</dependency>

application.yml配置单节点Redis:

spring:
  redis:
    host: 10.0.0.100
    port: 6379
    password: ${REDIS_PASSWORD}
    database: 0
redisson:
  threads: 16
  nettyThreads: 32

可重入分布式锁核心用法与防死锁设计

Redisson的RLock实现了java.util.concurrent.locks.Lock接口,使用方式与JUC锁一致:

@Service
public class OrderService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public String createOrder(String productId, String userId) {
        String lockKey = "order:lock:" + productId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // waitTime: 获取锁最大等待时间
            // leaseTime: 锁持有时间(防死锁关键)
            boolean acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
            if (!acquired) {
                throw new BusinessException("系统繁忙,请稍后重试");
            }
            
            // 业务逻辑:检查库存、扣减、创建订单
            return doCreateOrder(productId, userId);
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BusinessException("操作被中断");
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

leaseTime设为10秒是防死锁的核心:即使持有锁的服务宕机,10秒后锁自动释放。Redisson通过后台看门狗(watchdog)线程续期,默认每10秒检查并续期30秒,业务执行完毕正常释放时看门狗停止。

API接口限流:令牌桶与滑动窗口双方案

高并发场景下限流比限锁更常见。Redisson提供两种限流器:

方案1:RRateLimiter令牌桶限流

@Component
public class RateLimitAspect {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public boolean tryAcquire(String key, long permits, long rate, long interval) {
        RRateLimiter limiter = redissonClient.getRateLimiter(key);
        limiter.trySetRate(RateType.OVERALL, rate, interval, RateIntervalUnit.SECONDS);
        return limiter.tryAcquire(permits);
    }
}

// 在Controller中使用
@GetMapping("/api/products")
public Result listProducts() {
    if (!rateLimitAspect.tryAcquire("api:products", 1, 100, 1)) {
        return Result.fail(429, "请求过于频繁");
    }
    return Result.ok(productService.list());
}

令牌桶允许突发流量,适合读多写少的查询接口。参数rate=100表示每秒100个令牌,超量请求直接拒绝。

方案2:RScript滑动窗口计数限流

滑动窗口比固定窗口更精确,无临界点突刺问题:

@Service
public class SlidingWindowLimiter {
    
    @Autowired
    private RedissonClient redissonClient;
    
    private static final String LUA_SCRIPT = 
        "local key = KEYS[1] " +
        "local now = tonumber(ARGV[1]) " +
        "local window = tonumber(ARGV[2]) " +
        "local limit = tonumber(ARGV[3]) " +
        "redis.call('ZREMRANGEBYSCORE', key, 0, now - window) " +
        "local count = redis.call('ZCARD', key) " +
        "if count < limit then " +
        "  redis.call('ZADD', key, now, now .. ':' .. math.random()) " +
        "  redis.call('PEXPIRE', key, window) " +
        "  return 1 " +
        "end " +
        "return 0";
    
    public boolean tryAcquire(String key, long limit, long windowMs) {
        RScript script = redissonClient.getScript();
        Long result = script.eval(
            RScript.Mode.READ_WRITE,
            LUA_SCRIPT,
            RScript.ReturnType.INTEGER,
            Collections.singletonList(key),
            String.valueOf(System.currentTimeMillis()),
            String.valueOf(windowMs),
            String.valueOf(limit)
        );
        return result != null && result == 1;
    }
}

滑动窗口利用Redis Sorted Set存储每个请求的时间戳,查询时移除窗口外的旧记录,统计窗口内请求数。Lua脚本保证原子性,避免并发计数不准。

服务治理:分布式锁监控与降级策略

生产环境需监控锁的获取失败率和持有时长。Redisson原生支持JMX指标暴露:

# application.yml
redisson:
  jmxEnabled: true

关键监控指标:锁等待超时次数(tryLock失败计数)、锁持有时长P99(超过5秒需告警)、看门狗续期失败次数(Redis连接异常导致)。

降级策略:当Redis不可用时,分布式锁退化为本地锁,保证服务可用性但牺牲一致性:

@Service
public class ResilientLock {
    
    @Autowired(required = false)
    private RedissonClient redissonClient;
    
    public Lock acquireLock(String key) {
        if (redissonClient != null && isRedisHealthy()) {
            return redissonClient.getLock(key);  // 分布式锁
        }
        return new ReentrantLock();  // 降级为本地锁
    }
    
    private boolean isRedisHealthy() {
        try {
            return redissonClient.getNodesGroup().pingAll();
        } catch (Exception e) {
            return false;
        }
    }
}

降级方案在Redis短暂故障时维持服务运转,故障恢复后自动切回分布式锁。对于库存扣减等强一致性场景,降级时应直接拒绝请求而非使用本地锁。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-gao-bing-fa-she-ji-ji-yu-redisson-de-fen-bu-shi/

(0)
小编小编
上一篇 14小时前
下一篇 14小时前

相关推荐