分布式锁

摘自:

https://www.cnblogs.com/seesun2012/p/9214653.html

 

一、分布式

1、CAP理论

任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)、分期容错性(Partition tolerance)最多同时只能满足两项。

 

2、分布式

  • 分布式不是多线程,而是多进程
  • 多线程可以共享堆内存,将标记直接存储在内存就可以实现锁。而分布式运行的各个进程很有可能分布在不同的机器上。因此需要将标记存储在所有进程都能看到的地方。

 

二、分布式锁

  • 当在分布式模型下,数据只有一份,因此需要利用锁技术控制对数据修改的进程数。
  • 锁要保证进程之间可见,还要考虑网络之间的问题。
  • 分布式锁可以将标记存在内存,但是内存是进程共享的内从,集群环境,例如Redis。还可以利用数据库,保证标记能互斥就可以。

 

三、需要的分布式锁

  • 在分布式部署的应用集群环境中,同一个方法在同一时刻只能被一台机器中的一个线程调用。
  • 这把锁是一把可重入锁(避免死锁)
  • 有高可用的获取锁和释放锁功能
  • 获取锁和释放锁的性能要好

 

四、基于数据库的分布式锁

 

思路:利用主键唯一的特性,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,当方法执行完毕之后,想要释放锁的话,删除这条数据库记录即可。

上面这种简单的实现有以下几个问题:

  • 这把锁强依赖数据库的可用性,如果数据库是单点的,那么一旦数据库挂掉,会导致业务系统不可用
  • 这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其它线程无法再获取该锁
  • 这把锁是非阻赛的,一旦insert失败就会报错
  • 这把锁是非重入的,同一个线程在没有释放锁之前,无法获得该锁。
  • 非公平锁
  • 采用主键防重,在大并发情况下会造成锁表的情况。

 

上述问题的解决:

  • 增加备用数据库,一旦一个挂了就立即切换
  • 对于没有失效时间的问题,用定时任务清理超时数据
  • 非阻塞的,用while循环直到insert成功
  • 非重入的,增加字段,记录主机信息和线程信息,如果主机信息和线程信息在下一次操作可以查到,那么就直接分配
  • 非公平的,再建一张中间表,将等待锁的线程全部记录下来,并根据创建时间排序,先创建的先获取锁
  • 最后一个问题,解决办法是在程序执行过程中生产主键。

 

 

五、基于Redis做分布式锁

1、基于setnx(),expire()法

  • setnx(key, value),set if not exists,该方法是原子的。如果key不存在,则当前key设置成功,返回1。如果当前key已经存在,则设置当前key失败,返回0。
  • expire(),设置过期时间

setnx()如果返回0则返回占位失败,如果返回1则占位失败。

expire()命令对lockkey设置超时时间,为的是避免死锁行为

执行完业务代码后可以通过delete来删除。

该方法存在死锁问题

 

2、setnx(),get(),getset()法

为了解决死锁问题,新方法

  • setnx(lockkey, 当前时间+过期时间),1则获取锁成功,0则没有获取锁,转为2。
  • get(lockkey)获取值oldExpireTime,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转为3
  • 计算newExpireTime = 当前时间 + 过期时间,然后getset(lockkey, newExpireTime)会返回当前lockkey的值currentExpireTime
  • 判断currentExpireTime与oldExpireTime是否相等,如果相等,说明当前getset()设置成功,获取了锁。如果不等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。
  • 在获取锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行delete释放锁;如果大于锁设置的超时时间,则不需要再对锁进行处理
posted @ 2019-02-08 17:21  ylxn  阅读(222)  评论(0编辑  收藏  举报