Redis 实现分布式锁

1.分布式下的情况

分布式与单机情况下最大的不同在于其不是多线程而是多进程
多线程由于可以共享堆内存,因此可以简单的采取内存作为标记存储位置。而进程之间甚至可能都不在同一台物理机上,因此需要将标记存储在一个所有进程都能看到的地方。

2.分布式锁

当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。
与单机模式下的锁不仅需要保证进程可见,还需要考虑进程与锁之间的网络问题
分布式锁还是可以将标记存在内存,只是该内存不是某个进程分配的内存而是公共内存如 Redis、Memcache。至于利用数据库、文件等做锁与单机的实现是一样的,只要保证标记能互斥就行。

3.Redis 实现分布式锁的思想

分布式与单机的区别,无非在于锁大概率不是在一台机器上加的,或者说,多台服务器开启了多个服务,根本无法确定同时进行操作的两个线程是在一个服务上。那么对于锁而言,单机情况是同进程下多线程,等于说锁是对所有线程可见的,那么在分布式下,如果将锁放到整个分布式系统都可见的地方,自然能够完成锁的操作。只是不会是说利用 Java 语言的锁,例如 synchronized 关键字等。
这个解决方案有很多**,Redis 是其中一种。即将锁的数据放在 Redis 中。等于说,获取锁就是设置一个 key,然后放到 Redis 上,而线程进行操作的时候先去 Redis 上查看是否有这个 key。完成了整个锁的思想。**

等于说对于锁,还是有抢的过程:使用 setnx,若返回 1 说明抢锁成功,同时完成了加锁操作,根本没有这个值,否则就是失败。反之,使用 del 删除 key,就是解锁

4.原子性的问题

通过setnx加锁会非常容易引起死锁,因为如果线程在执行过程中挂掉了,没有释放锁,那么就会造成死锁。这个解决方法非常简单,考虑在加锁后,使用 expire 设定超时时间,超时自动释放。

上述的方法有一个比较重要的问题,就是执行了两个命令,等于说是非原子性的操作。这样有可能引起非常致命的错误,所以解决方法上需要考虑用 set 命令代替。

set key value EX time

5.误删锁的问题

虽然设置了超时时间,但是超时时间的设定却有待商榷。因为无法确定这个方法会不会出现问题,假设方法出现了问题,设定的超时时间是 20s,方法来个超级大的慢查询,20s 还没有执行成功。此时 key 自动失效,等于说方法没有执行成功却释放了锁

此时其他线程的请求会获得锁,然后执行方法。如果恰好该线程也出问题了,而之前的方法执行完毕,执行了 del 等于说提前将别的锁给解了。

解决方法就是确定,锁只有当前线程才能解,那么就是在设置 key 的时候,value 还什么都没设置呢,将线程 id 设置进去就可以了。

而这样解决了误删锁的问题,还有自动释放锁的问题。或者说是超时时间的设定问题。一个解决方法是,设定一个时间,时间无所谓,但是给线程开启一个守护进程。当守护进程发现时间快到了,不管方法执行情况,直接续时。这样可能会消费很长的时间,但是保证线程必然会保持锁直到完成。如果碰见挂掉的情况,此时守护进程会跟随挂掉,所以自然不能续时,保证了不会死锁。
也可以看到,上述条件可能会引起饥饿,但是这个就不是分布式锁的问题了,而是程序的问题。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章