是什么?
对于分布式场景,我们可以使用分布式锁,它是控制分布式系统之间互斥访问共享资源的一种方式。
比如说在一个分布式系统中,多台机器上部署了多个服务,当客户端一个用户发起一个数据插入请求时,如果没有分布式锁机制保证,那么那多台机器上的多个服务可能进行并发插入操作,导致数据重复插入,对于某些不允许有多余数据的业务来说,这就会造成问题。而分布式锁机制就是为了解决类似这类问题,保证多个服务之间互斥的访问共享资源,如果一个服务抢占了分布式锁,其他服务没获取到锁,就不进行后续操作。
怎么做?
先设置锁,在完成业务操作后,删除锁。其他请求先获取锁,未获取锁的时候,则等待。
setnx lock v100 //设置锁
if 设置成功 {
1.完成业务操作
2.释放锁
del lock
}
问题:系统报错中断或者无限延迟,锁无法释放,其他请求一直等待,系统阻塞
解决方案:设置过期时间
setnx lock v100
expire lock 20
问题:这两操作不是原子性,存在设置过期时间前,系统中断,锁无法释放
解决方案:
set lock v100 nx ex 20
仍有问题:请求A拿到锁,进行业务操作时请求阻塞,A锁过期后,请求B拿到锁后,A业务操作完成,A将B的锁释放掉了
解决方案:A只能释放A设置的锁,B只能释放B设置的锁
生成一个随机数 uuid
set lock uuid nx ex 20
//释放锁时比较uuid 和 lock的value值
if uuid == get lock {
//锁释放
}
问题: 虽然这样做可以避免大部分A释放B的可能性,但依然可能存在,uuid比较成功后,A锁过期,请求B拿到锁后,A业务操作完成,A将B的锁释放掉了,因为比较操作和删除操作不是原子性
解决方案:lua语言
if redis.call('get',KEYS[1]) == ARGV[1] then " +
"return redis.call('del',KEYS[1]) else return 0 end"
//get keys[1] 获取lock的value
//ARGV[1] uuid值
llua语言保证了 比较操作 和删除操作的原子性
为什么
为了确保分布式锁可用,我们至少要确保锁的实现同时满足一下四个条件
- 互斥性 :在任意时刻,只有一个客户端能持有锁
- 不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动释放锁,也能保证后续其他客户端能加锁----设置过期时间
- 解铃还须系铃人:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。–设置随机数uuid
- 加锁和解锁必须具有原子性—set lock value nx ex 10 和 lua语言