复述分布式锁的实现

  

<强>一、使用分布式锁要满足的几个条件:

1,系统是一个分布式系统(关键是分布式,单机的可以使用ReentrantLock或者同步代码块来实现)

2,共享资源(各个系统访问同一个资源,资源的载体可能是传统关系型数据库或者NoSQL)

3,同步访问(即有很多个进程同事访问同一个共享资源。没有同步访问,谁管你资源竞争不竞争)

<强>二,应用的场景例子

管理后台的部署架构(多台tomcat服务器+复述,【多台tomcat服务器访问一台复述】+ mysql【多台tomcat服务器访问一台服务器上的mysql】)就满足使用分布式锁的条件。多台服务器要访问复述,全局缓存的资源,如果不使用分布式锁就会出现问题。看如下伪代码:

长l N=0;//N从复述,获取值   如果(N<5) {   N + +;//N写回复述   }

上面的代码主要实现的功能:

从复述,获取值N,对数N值进行边界检查,自加1,然后N写回复述中。这种应用场景很常见,像秒杀,全局递增ID、IP访问限制等。

以IP访问限制来说,恶意攻击者可能发起无限次访问,并发量比较大,分布式环境下对N的边界检查就不可靠,因为从复述,读的N可能已经是脏数据。

传统的加锁的做法(如java的同步和锁)也没的用,因为这是分布式环境,这个同步问题的救火队员也束手无策。在这危急存亡之秋,分布式锁终于有用武之地了。

分布式锁可以基于很多种方式实现,比如动物园管理员,复述,…。不管哪种方式,他的基本原理是不变的:用一个状态值表示,锁对锁的占用和释放通过状态值来标识。

这里主要讲如何用复述,实现分布式锁。

<强>三、使用复述的setNX命令实现分布式锁

1,实现的原理

复述,为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对复述的连接并不存在竞争关系.redis的setNX命令可以方便的实现分布式锁。

2,基本命令解析

1) setNX(如果不存在组)

语法:

setNX键值将关键的值设为值,当且仅当关键不存在。

若给定的关键已经存在,则setNX不做任何动作。

setNX是“如果不存在”(如果不存在,则集)的简写

返回值:

设置成功,返回1 .

设置失败,返回0。

例子:

redis>#工作存在工作不存在   (整数)0      redis>SETNX工作“programmer"#工作设置成功   (整数)1      redis>SETNX工作“code-farmer"#尝试覆盖工作,失败   (整数)0      redis>得到工作#没有被覆盖   “programmer"

所以我们使用执行下面的命令

SETNX锁。foo & lt;当前Unix时间+锁定超时+ 1在

如返回1,则该客户端获得锁,把锁。foo的键值设置为时间值表示该键已被锁定,该客户端最后可以通过德尔锁。foo来释放该锁。

如返回0,表明该锁已被其他客户端取得,这时我们可以先返回或进行重试等对方完成或等待锁超时。

2) getSET

语法:

getSET键值将给定键的值设为值,并返回关键的旧值(旧值)。

当关键存在但不是字符串类型时,返回一个错误。

返回值:

返回给定关键的旧值。

当关键没有旧值时,也即是,关键不存在时,返回零。

3)

语法:

得到关键

返回值:

当关键不存在时,返回零,否则,返回关键的值。

如果不关键是字符串类型,那么返回一个错误

<强>四,解决死锁

上面的锁定逻辑有一个问题:如果一个持有锁的客户端失败或崩溃了不能释放锁,该怎么解决?

我们可以通过锁的键对应的时间戳来判断这种情况是否发生了,如果当前的时间已经大于锁。foo的值,说明该锁已失效,可以被重新使用。

发生这种情况时,可不能简单的通过德尔来删除锁,然后再SETNX一次(讲道理,删除锁的操作应该是锁拥有这执行的,这里只需要等它超时即可),当多个客户端检测到锁超时后都会尝试去释放它,这里就可能出现一个竞态条件,让我们模拟一下这个场景:

C0操作超时了,但它还持有着锁,C1和C2读取锁。foo检查时间戳,先后发现超时了。
C1发送DEL锁。foo
C1发送SETNX锁。foo并且成功了。
C2发送DEL锁。foo
C2发送SETNX锁。foo并且成功了。
这样一来,C1, C2都拿到了锁!问题大了!

复述分布式锁的实现