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
相关产品推荐
相关产品推荐

