赞
踩
Redission是Redis官方推荐的客户端,提供了一个RLock的锁,RLock继承自juc的Lock接口,提供了中断,超时,尝试获取锁等操作,支持可重入,互斥等特性。
RLock底层使用Redis的Hash作为存储结构,其中Hash的key用于存储锁的名字,Hash的filed用于存储客户端id,filed对应的value是线程重入次数。
客户端id是用于区分每个加锁的线程的,由两部分组成: RedissonLock的成员变量id + 当前线程id Thread.currentThread().getId()。 其中id是一个UUID,在每次实例化Redisson对象实例的时候都会创建一个ConnectionManager,该类会在实例化时生成一个UUID(UUID.randomUUID()), 所以对于每一个Redisson在其生命周期中该id都是相同的,区别在于threadId的不同;而不同Redisson对象之间id也不同,这样可以很好的对不同服务中的线程进行区分。
可以参考Java的重入锁,用于表示该锁当前被递归的次数,该值>=1,如果等于0时该锁会被删除。
为了实现加锁的原子性,Redisson使用Lua脚本的形式进行加锁。 该脚本位于RedissonLock#tryLockInnerAsync中
- if (redis.call('exists', KEYS[1]) == 0) then
- redis.call('hset', KEYS[1], ARGV[2], 1);
- redis.call('pexpire', KEYS[1], ARGV[1]);
- return nil;
- end;
- if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
- redis.call('hincrby', KEYS[1], ARGV[2], 1);
- redis.call('pexpire', KEYS[1], ARGV[1]);
- return nil;
- end;
- return redis.call('pttl', KEYS[1]);
- 复制代码
其中KEYS[1]是Hash的key也就是RLock的名字,ARGV[2]是客户端id,ARGV[1]是Hash的生存时间TTL。 首先判断KEYS[1]对应的Hash是否存在,如果不存在直接创建一个新的Hash,对应的filed值为1也就是说第一次重入次数为1,并且设置超时时间,最终返回null; 如果Hash已经存在且filed为ARGV[2]的项也存在,说明是该线程递归进入该锁,需要对该filed增加一次重入次数,并且更新超时时间,最终返回null; 如果当前锁已经存在,且不是当前线程持有的,就会返回当前锁的TTL。
每次进行加锁时会返回锁的TTL,如果TTL为null说明加锁成功,直接返回即可,否则说明已有其他线程持有该锁。后续该线程会进行循环去尝试获取锁直到加锁成功。如果使用tryLock则可以在超时时间结束后直接返回。
如果锁设置了持有锁的超时时间,在超时后会进行锁的释放,如果获取锁的时候不指定持有锁的时间,那么默认获取锁30s后超时。为了防止任务没有执行完就释放锁,Redisson使用一个守护线程(看门狗任务)定时刷新(超时时间的 1/3, 默认是10s,也就是每10s续约30s,直到线程自己释放)这个锁超时时间进行续约,也就是只要这个锁被获取了,则力保这个锁一直不超时,除非获取锁的线程主动释放。由于获取到锁和这个续命任务的守护线程是在同一个线程的,当获取锁的线程挂掉了,意味着刷新任务的线程也会停止执行,就不会再刷新锁的超时时间。
解锁同样使用Lua脚本执行,代码为与#unlockInnerAsync方法:
- if (redis.call('exists', KEYS[1]) == 0) then
- redis.call('publish', KEYS[2], ARGV[1]);
- return 1;
- end;
- if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
- return nil;
- end;
- local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
- if (counter > 0) then
- redis.call('pexpire', KEYS[1], ARGV[2]);
- return 0;
- else
- redis.call('del', KEYS[1]);
- redis.call('publish', KEYS[2], ARGV[1]);
- return 1;
- end;
- return nil;
- 复制代码

首先判断锁是否是存在的,如果不存在直接返回nil; 如果该线程持有锁,则对当前的重入值-1,如果计算完后大于0,重新设置超时持有实践返回0; 如果算完不大于0,删除这个Hash,并且进行广播,通知watch dog停止进行刷新,并且 返回1.
Redisson锁并没有解决主从节点切换可能导致重复加锁的问题,即某个客户端在Master节点加锁,此时主节点宕机,由于主从之间异步复制,从节点没有来得及复制,此时选举出新的Master后并没有之前的锁,另外一个客户端要对同一个锁进行操作时可以直接加锁,那么有两个客户端同时持有一把锁,这样锁住的共享资源会被重复读取,造成混乱。
Redisson提供了RedissonRedLock锁实现了RedLock,需要同时使用多个独立的Redis实例分别进行加锁,只有超过一半的锁加锁成功,则认为是成功加锁。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。