在单进程多线程的情况下,为了防止多个线程竞争共享资源,需要使用锁。java中提供了显示锁和隐式锁。
但是在分布式部署的多进程下,就必须使用分布式锁。
分布式锁的条件
- 保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行
// 加锁, unique_value作为客户端唯一性的标识
SET lock_key unique_value NX PX 10000
Redis的set命令可以增加NX选项,不存在即设置;只有在键值对不存在时,才会进行设置,否则不做赋值操作
PX选项,可以设置key的过期时间
1、某个客户端加锁之后,在操作共享数据的时候发生了异常,锁没有被释放,导致其它客户端无法拿到锁!
- 在finally中进行锁的释放
- PX选项设置锁的过期时间,这样即使有锁的客户端发送了异常,无法主动释放锁,Redis也会删除过期的锁变量。其它的客户端在锁过期之后,就可以重新获取锁。
2、客户端A加锁之后,执行业务操作共享数据,客户端B进行释放锁的操作这样就会导致客户端A的锁被误释放,这时客户端C正好申请加锁,可以成功的获取锁。这样一来客户端A和客户端C就可以同时操作共享数据了。
- 解铃还须系铃人,在加锁的时候每个客户端都使用唯一的标识,在释放锁的时候判断锁变量的值是否等于执行释放锁操作的客户端的唯一标识
锁的释放包含了读取锁变量、判断值、删除锁变量的多个操作,而 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,从而保证了锁释放操作的原子性。
带NX和PX选项的SET命令以及lua脚本实现了单节点的Redis分布式锁。
但是只有一个Redis节点来保存锁变量。当Redis实例发生故障宕机时,无法进行锁操作。所以我们要保证锁的高可用性,就要使用多节点的Redis来实现分布式锁了;
让客户端和多个独立的 Redis 实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁了,否则加锁失败。这样一来,即使有单个 Redis 实例发生故障,因为锁变量在其它实例上也有保存,所以,客户端仍然可以正常地进行锁操作,锁变量并不会丢失。
客户端只有在满足下面的这两个条件时,才能认为是加锁成功
- 客户端从超过半数(大于等于 N/2+1)的 Redis 实例上成功获取到了锁
- 客户端获取锁的总耗时没有超过锁的有效时间