You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot Redis并发访问:仓库层锁实现方案咨询

两种Redis锁方案对比与实现

方案选择建议

RedisLockRegistry

适合已引入Spring Integration、希望快速实现分布式锁,或是需要重入锁、自动锁过期等成熟特性的场景。它是Spring官方封装的锁实现,能减少重复造轮子带来的bug。

自定义Redis命令锁

适合不想引入额外依赖、追求轻量性能的场景。直接基于Spring Data Redis的setIfAbsent命令实现,代码更简洁,但需要自行处理锁的安全性(比如防止误释放其他线程的锁)。


方案一:RedisLockRegistry实现

1. 引入依赖(若未添加)

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-redis</artifactId>
</dependency>

2. 配置锁Registry Bean

@Configuration
public class RedisLockConfig {
    @Bean
    public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
        // 第二个参数是锁前缀,用于区分不同业务的锁资源
        return new RedisLockRegistry(redisConnectionFactory, "item-lock");
    }
}

3. 业务层加锁逻辑

Save操作互斥锁

@Service
public class ItemsService {
    private final ItemRepository itemRepository;
    private final RedisLockRegistry redisLockRegistry;

    public ItemsService(ItemRepository itemRepository, RedisLockRegistry redisLockRegistry) {
        this.itemRepository = itemRepository;
        this.redisLockRegistry = redisLockRegistry;
    }

    public void saveItem(Item item) {
        String lockKey = "item:" + item.getId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        try {
            // 5秒内尝试获取锁,超时则放弃
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                item.setLastAccessedTime(LocalDateTime.now());
                itemRepository.save(item);
            } else {
                throw new RuntimeException("无法获取锁,Item保存失败: " + item.getId());
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("保存操作被中断", e);
        } finally {
            // 确保当前线程持有锁时才释放
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

定时清理任务加锁

@Component
public class ItemCleanupTask {
    private final ItemRepository itemRepository;
    private final RedisLockRegistry redisLockRegistry;
    @Value("${item.expire.hours:24}")
    private long expireHours;

    public ItemCleanupTask(ItemRepository itemRepository, RedisLockRegistry redisLockRegistry) {
        this.itemRepository = itemRepository;
        this.redisLockRegistry = redisLockRegistry;
    }

    @Scheduled(fixedRate = 3600000) // 每小时执行一次
    public void cleanupExpiredItems() {
        LocalDateTime expireTime = LocalDateTime.now().minusHours(expireHours);
        List<Item> expiredItems = itemRepository.findByLastAccessedTimeBefore(expireTime);

        for (Item item : expiredItems) {
            String lockKey = "item:" + item.getId();
            Lock lock = redisLockRegistry.obtain(lockKey);
            try {
                // 短超时尝试获取锁,避免批量处理阻塞
                if (lock.tryLock(1, TimeUnit.SECONDS)) {
                    // 二次检查过期状态,防止查询后Item被访问更新
                    Item latestItem = itemRepository.findById(item.getId()).orElse(null);
                    if (latestItem != null && latestItem.getLastAccessedTime().isBefore(expireTime)) {
                        itemRepository.delete(latestItem);
                    }
                }
                // 获取锁失败则跳过,下次任务再处理
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    }
}

方案二:自定义Redis命令锁实现

1. 锁工具类Bean

@Component
public class RedisLockUtil {
    private final RedisTemplate<String, Object> redisTemplate;
    private static final long DEFAULT_LOCK_EXPIRE = 30; // 默认锁过期30秒,防止死锁

    public RedisLockUtil(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    // 获取锁,返回是否成功
    public boolean tryLock(String lockKey, long expireSeconds) {
        Boolean result = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, Thread.currentThread().getId(), expireSeconds, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(result);
    }

    // 释放锁,仅释放当前线程持有的锁
    public void unlock(String lockKey) {
        String currentThreadId = String.valueOf(Thread.currentThread().getId());
        String lockValue = (String) redisTemplate.opsForValue().get(lockKey);
        if (currentThreadId.equals(lockValue)) {
            redisTemplate.delete(lockKey);
        }
    }
}

2. 业务层与定时任务使用锁

Save操作

@Service
public class ItemsService {
    private final ItemRepository itemRepository;
    private final RedisLockUtil redisLockUtil;

    public ItemsService(ItemRepository itemRepository, RedisLockUtil redisLockUtil) {
        this.itemRepository = itemRepository;
        this.redisLockUtil = redisLockUtil;
    }

    public void saveItem(Item item) {
        String lockKey = "item:" + item.getId();
        try {
            boolean locked = redisLockUtil.tryLock(lockKey, 30);
            if (!locked) {
                throw new RuntimeException("无法获取锁,Item保存失败: " + item.getId());
            }
            item.setLastAccessedTime(LocalDateTime.now());
            itemRepository.save(item);
        } finally {
            redisLockUtil.unlock(lockKey);
        }
    }
}

定时清理任务

@Component
public class ItemCleanupTask {
    private final ItemRepository itemRepository;
    private final RedisLockUtil redisLockUtil;
    @Value("${item.expire.hours:24}")
    private long expireHours;

    public ItemCleanupTask(ItemRepository itemRepository, RedisLockUtil redisLockUtil) {
        this.itemRepository = itemRepository;
        this.redisLockUtil = redisLockUtil;
    }

    @Scheduled(fixedRate = 3600000)
    public void cleanupExpiredItems() {
        LocalDateTime expireTime = LocalDateTime.now().minusHours(expireHours);
        List<Item> expiredItems = itemRepository.findByLastAccessedTimeBefore(expireTime);

        for (Item item : expiredItems) {
            String lockKey = "item:" + item.getId();
            try {
                if (redisLockUtil.tryLock(lockKey, 10)) {
                    Item latestItem = itemRepository.findById(item.getId()).orElse(null);
                    if (latestItem != null && latestItem.getLastAccessedTime().isBefore(expireTime)) {
                        itemRepository.delete(latestItem);
                    }
                }
            } finally {
                redisLockUtil.unlock(lockKey);
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者mike

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 16:04:53