Redis分布式锁
代码示例:
/**
* 可能会因为并发问题导致没有全部业务成功
*
* @return
*/
@Transactional(rollbackFor = Exception.class)
public DataLog generatorData(List<AVO> list,
Long teamId,
AtomicInteger number,
Long currentUserId,
OperatorTypeEnum operatorType) {
List<A> AList = new ArrayList<>();
List<B> BList = new ArrayList<>();
List<C> logsList = new ArrayList<>();
ZonedDateTime now = ZonedDateTime.now();
List<data> dataList = new ArrayList<>();
for (dataVO data : list) {
if (number.get() > 0) {
Long dataId = data.getId();
//设置redis数据锁
Boolean lock = dataLockHandler.lockData(dataId);
if (lock != null && lock) {
data.setCurrentStatus(StatusEnum.PROGRESSING);
data.setCurrentOperatorId(currentUserId);
data.setCurrentWorkTeamId(teamId);
dataList.add(data);
// 更新成功
C logs = C.builder()
.dataId(data.getId())
.operateTime(now)
.operatorId(currentUserId)
.operatorText(EMPTY_STRING)
.data(data)
.build();
logsList.add(logs);
A dataNode = data.getDataNode();
dataNode.setNodeStatus(StatusEnum.PROGRESSING);
AList.add(dataNode);
B B = B.builder()
.startDate(now)
.workTeamId(teamId).build();
BList.add(B);
number.decrementAndGet();
}
}
}
return new DataLog(AList, BList, dataList, logsList);
}
@Service
@RequiredArgsConstructor
public class DataLockHandler {
private final RedisTemplate redisTemplate;
/**
* 加锁
* 如key不存在就set值,并返回1 如果存在不进行操作,并返回0
*/
public Boolean lockData(Long dataId) {
return redisTemplate.opsForValue().setIfAbsent(LOCK_KEY + dataId, "", 20, TimeUnit.MINUTES);
}
}
setIfAbsent 的工作原理:
原子性操作:
setIfAbsent 会尝试在 Redis 中为某个 key 设置一个值。
如果 key 不存在,Redis 会将 key 和 value 存入数据库,并返回 true,表示操作成功。
如果 key 已存在,Redis 不会覆盖现有的值,并返回 false,表示操作失败。
设置过期时间:
通过为 key 设置过期时间(例如 20 分钟),确保锁在一定时间后自动释放,防止因某些异常情况导致锁长时间无法释放(即死锁)。
在 Redis 的 Java 客户端(如 Spring Data Redis)中,可以使用 setIfAbsent 的过期时间参数来指定锁的存活时间。
Redis 加锁方法的作用是通过分布式锁来确保多个进程或线程在并发环境下对相同的业务数据进行操作时,能够避免数据竞争和并发修改的问题。
Redis 加锁详细作用说明:
1. 分布式环境下的并发控制
在微服务架构或多服务器部署的环境中,多个服务或线程可能会同时对相同的数据进行操作,这种并发操作可能导致数据的不一致或重复处理。为了避免这种情况,必须对操作进行同步控制,即每次只能有一个进程或线程修改某个数据。
Redis 加锁机制通过以下方式实现了这种控制:
- 当一个线程想要操作特定业务数据时,先通过 Redis 尝试获取一把锁。
- 如果获取锁成功(即
setIfAbsent
返回true
),表示此线程可以安全地操作该业务数据。 - 其他线程在尝试获取相同锁时会失败(因为锁已经存在,
setIfAbsent
返回false
),这些线程需要等待锁释放后才能继续操作。
2. 避免重复处理和数据一致性
在高并发情况下,如果没有锁机制,多个线程可能会同时对相同的数据进行处理,造成以下问题:
- 重复操作:同一个业务数据可能被多个线程同时修改,导致数据状态不一致。
- 数据覆盖:最后一个执行完成的操作可能覆盖其他线程的修改,导致数据丢失。
加锁的好处:
- 通过 Redis 锁,只允许一个线程修改该业务数据,避免了重复处理和数据覆盖的问题。
- 在高并发情况下,可以确保每个业务数据只被一次有效操作。
3. 提高系统的可靠性
Redis 分布式锁提供了锁的自动过期机制,防止锁永远不会释放,导致死锁问题:
- 锁设置了 20 分钟的过期时间(
20, TimeUnit.MINUTES
),即使某个线程在操作过程中崩溃或者遇到意外问题导致没有主动释放锁,Redis 也会在 20 分钟后自动释放该锁,其他线程可以重新获取锁并继续操作。 - 这使得系统在发生故障或异常时,不会进入不可恢复的状态,从而提高了系统的可靠性。
4. 减轻数据库负担
加锁操作是在 Redis 中进行的,Redis 是内存型数据库,执行速度非常快,延迟极低。相比直接在数据库层面进行锁控制(如悲观锁、乐观锁),通过 Redis 加锁能够极大地减轻数据库的负载,并提高系统的整体响应速度。
5. 实现分布式锁的高效性
Redis 是一个分布式存储系统,天然支持跨多个服务器或进程的锁控制。因此,使用 Redis 作为分布式锁的存储介质,能实现以下功能:
- 多个服务实例部署在不同的服务器或进程中,也能通过 Redis 共享同一把锁。
- Redis 的
setIfAbsent
操作是原子性的,保证了锁的高效和可靠性,避免了并发问题。
使用场景:
- 防止多线程/多进程同时操作相同数据:如订单处理、支付状态更新等场景下,避免多个请求同时对同一个订单进行处理。
- 限流/限量控制:可以限制某些资源(如库存、优惠券)在同一时间点只能被一个线程或进程获取,防止超发或超卖。
- 任务分配:在任务调度系统中,可以使用锁机制确保同一时间只有一个调度器能够处理某个任务,避免任务重复执行。
总结:
这个 Redis 加锁方法的主要作用是确保在分布式或多线程环境中,业务数据不会被多个线程或进程并发修改,保证数据的一致性、可靠性以及系统的稳定性。通过 Redis 分布式锁,可以有效解决并发竞争、数据重复处理、数据覆盖等问题,提升系统性能并减少对数据库的压力。
本文作者:chillymint
本文链接:https://www.cnblogs.com/chillymint/p/18427523
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步