赞
踩
用来解决一个进程的多个线程同时操作同一资源的问题,如果是在分布式系统或者集群中,普通的锁是锁不住的。如图1:
用来解决多个进程同时操作同一资源的问题。他的原理就是所有的获取到同一把锁,无论有多少个服务,只有一个服务可以获取到锁,其他没有获得到的锁的服务则进行等待或者自旋等,直到锁释放,其他的服务才可以尝试获取到锁,如图2:
Redis有一个setnx可以实现分布式锁,sexnx在指定的key不存在时,会为key设置指定的值,若给定的key已存在,setnx不做任何操作。
- if(jedis.setnx(lock_stock,1) == 1){ //获取锁
- try {
- //需要执行的业务代码
- } finally {
- //释放锁
- jedis.del(lock_stock)
- }
使用setnx的命令进行加锁和释放锁,我们可以发现可能会存在死锁的现象,当我们在锁释放之前,服务宕机了,就会导致锁无法释放,那么其他的服务也就无法获取到锁,就会造成死锁,因此,我们在加锁时还应该为锁设置一个过期的时间。
- if(jedis.setnx(lock_stock,1) == 1){ //获取锁
- //设置锁超时
- expire(lock_stock,2)
- try {
- //需要执行的业务代码
- } finally {
- //释放锁
- jedis.del(lock_stock)
- }
- }
但是使用了expire设置了过期时间,仍会存在一种极值的现象,就是若在加锁和设置锁超时之间,服务发生了宕机,依然会发生死锁的现象,因此我们需要去保证加锁和释放的原子性。Redis中提供了一个set命令可以解决这个问题,相当于是加锁的设置过期时间的一个组合命令。
- if(set(lock_stock,1,"NX","EX",2) == 1){ //获取锁并设置超时
- try {
- //需要执行的业务代码
- } finally {
- //释放锁
- del(lock_stock)
- }
- }
- EX : 将键的过期时间设置为秒。 执行 SET key value EX seconds 的效果等同于执行 SETEX key seconds value 。
- PX : 将键的过期时间设置为毫秒。 执行 SET key value PX milliseconds 的效果等同于执行 PSETEX key milliseconds value 。
- NX : 只在键不存在时,才对键进行设置操作。 执行 SET key value NX 的效果等同于执行 SETNX key value 。
- XX : 只在键已经存在时, 才对键进行设置操作。
使用set命令进行加锁和释放锁,虽然保证了原子性,但是又出现了新的问题,如果我们需要执行的业务代码时间超过了锁设置的过期时间,那么在锁达到过期时间时会将这个锁释放,而在执行完业务后又进行了一次锁的删除,就会导致第二次删除的锁不是执行该业务时加的锁,造成锁的一个误删除现象。为解决这一问题,我们需要在删除锁的时候进行锁的判断,判断当前删除的锁是不是自己的锁。
- String uuid = UUID.randomUUID().toString();
- if(jedis.set(lock_stock,uuid,"NX","EX",5) == 1){ //获取锁并设置超时
- try {
- //执行自己的业务代码
- } finally {
- //获取锁的值
- String lockValue = jedis.get(lock_stock);
- //判断是不是自己的锁
- if(lockValue.equals(uuid)){
- //释放锁
- jedis.del(lock_stock)
- }
- }
- }
除了要判断锁,我们还应该去保证判断锁和删除锁的一个原子性,否则依然会有极值的问题,导致锁的误删除,因此这里使用Redis+Lua脚本来解决一致性的问题。
- String script = "if redis.call('get', KEYS[1]) == ARGV[1]
- then return redis.call('del', KEYS[1]) else return 0 end";
上述的这段Lua脚本,可以保证多个命令的原子性,其中
修改上述的代码为:
- String uuid = UUID.randomUUID().toString();
- if(jedis.set(lock_stock,uuid,"NX","EX",2) == 1){ //获取锁并设置超时
- try {
- //需要执行的业务代码
- } finally {
- //lua脚本
- String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
- //执行脚本
- jedis.eval(script, Collections.singletonList("lock_stock"),Collections.singletonList(uuid));
- }
- }
到此一个基本的redis实现分布式锁就完成了,但是考虑到实际的业务,未获得锁的线程可以做重入,等待一会儿,再次尝试获取锁。
- public void method(){
- String uuid = UUID.randomUUID().toString();
- if(jedis.set(lock_stock,uuid,"NX","EX",5) == 1){ //获取锁并设置超时
- try {
- //需要执行的业务代码
- } finally {
- //lua脚本
- String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
- //执行脚本
- jedis.eval(script, Collections.singletonList("lock_stock"),Collections.singletonList(uuid));
- }
- }else{
- //休眠之后重入方法,尝试获取锁,休眠的时间可根据自己的时间调整
- Thread.sleep(100);
- //自旋,重新进入方法
- method();
- }
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。