https://blog.csdn.net/qq_19568599/article/details/52612421
1、Redis分布式锁实现
a、原理
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。其次Redis提供一些命令SETNX,GETSET,可以方便实现分布式锁机制。
public class RedisKeyLock {
private static Logger logger = Logger.getLogger(RedisKeyLock.class);
private final static long ACCQUIRE_LOCK_TIMEOUT_IN_MS = 10 * 1000;
private final static int EXPIRE_IN_SECOND = 5;//锁失效时间
private final static long WAIT_INTERVAL_IN_MS = 100;
private static RedisKeyLock lock;
private JedisPool jedisPool;
private RedisKeyLock(JedisPool pool){
this.jedisPool = pool;
}
public static RedisKeyLock getInstance(JedisPool pool){
if(lock == null){
lock = new RedisKeyLock(pool);
}
return lock;
}
public void lock(final String redisKey) {
Jedis resource = null;
try {
long now = System.currentTimeMillis();
resource = jedisPool.getResource();
long timeoutAt = now + ACCQUIRE_LOCK_TIMEOUT_IN_MS;
boolean flag = false;
while (true) {
String expireAt = String.valueOf(now + EXPIRE_IN_SECOND * 1000);
long ret = resource.setnx(redisKey, expireAt);
if (ret == 1) {//已获取锁
flag = true;
break;
} else {//未获取锁,重试获取锁
String oldExpireAt = resource.get(redisKey);
if (oldExpireAt != null && Long.parseLong(oldExpireAt) < now) {
oldExpireAt = resource.getSet(redisKey, expireAt);
if (Long.parseLong(oldExpireAt) < now) {
flag = true;
break;
}
}
}
if (timeoutAt < now) {
break;
}
TimeUnit.NANOSECONDS.sleep(WAIT_INTERVAL_IN_MS);
}
if (!flag) {
throw new RuntimeException("canot acquire lock now ...");
}
} catch (JedisException je) {
logger.error("lock", je);
je.printStackTrace();
if (resource != null) {
jedisPool.returnBrokenResource(resource);
}
} catch (Exception e) {
e.printStackTrace();
logger.error("lock", e);
} finally {
if (resource != null) {
jedisPool.returnResource(resource);
}
}
}
public boolean unlock(final String redisKey) {
Jedis resource = null;
try {
resource = jedisPool.getResource();
resource.del(redisKey);
return true;
} catch (JedisException je) {
je.printStackTrace();
if (resource != null) {
jedisPool.returnBrokenResource(resource);
}
return false;
} catch (Exception e) {
logger.error("lock", e);
return false;
} finally {
if (resource != null) {
jedisPool.returnResource(resource);
}
}
}
}
c、代码分析
lock:通过间隔时间段去请求Redis,来实现阻塞占用,一直到获取锁,或者超时。
unlock:删除redis中key。
2、Zookeeper分布式锁实现
a、原理
ZooKeeper核心是一个精简的文件系统,它提供了一些简单的文件操作以及附加的功能 ,它的数据结构原型是一棵znode树(类似Linux的文件系统),并且它们是一些已经被构建好的块,可以用来构建大型的协作数据结构和协议 。
每个锁都需要一个路径来指定(如:/geffzhang/lock)
1.根据指定的路径, 查找zookeeper集群下的这个节点是否存在.(说明已经有锁了)
2. 如果存在, 根据查询者的一些特征数据(如ip地址/hostname), 当前的锁是不是查询者的
3. 如果不是查询者的锁, 则返回null, 说明创建锁失败
4. 如果是查询者的锁, 则把这个锁返回给查询者
5. 如果这个节点不存在, 说明当前没有锁, 那么创建一个临时节点, 并将查询者的特征信息写入这个节点的数据中, 然后返回这个锁.
据以上5部, 一个分布式的锁就可以创建了.
创建的锁有三种状态:
1. 创建失败(null), 说明该锁被其他查询者使用了.’
2. 创建成功, 但当前没有锁住(unlocked), 可以使用
3. 创建成功, 但当前已经锁住(locked)了, 不能继续加锁.
b、代码样例
public class ZooKeeperLock implements Lock, Watcher {
private Logger logger = LoggerFactory.getLogger(getClass());
private static final String SPLITSTR = "_lock_";
private static final int SESSION_TIMEOUT = 60000;//等锁的毫秒数
private static final byte[] data = new byte[0];
private ZooKeeper zk = null;
private String root = "/locks";//根
private String lockName;//竞争资源的标志
private String waitNode;//等待前一个锁
private String myZnode;//当前锁
private CountDownLatch latch;//计数器
/**
* 创建分布式锁,使用前请确认config配置的zookeeper服务可用
* @param server 127.0.0.1:2181
* @param lockName 竞争资源标志,lockName中不能包含单词lock
*/
public ZooKeeperLock(String server, String lockName){
this.lockName = lockName;
// 创建一个与服务器的连接
try {
zk = initZk(server);
Stat stat = zk.exists(root, false);
if(stat == null){
// 创建根节点
zk.create(root, data, ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
} catch (Exception e) {
throw new LockException(e);
}
}
/**
* zookeeper节点的监视器
*/
@Override
public void process(WatchedEvent event) {
if(this.latch != null) {
this.latch.countDown();
}
}
@Override
public void lock() {
try {
if(!tryLock()){
boolean locked = waitForLock(waitNode, SESSION_TIMEOUT, TimeUnit.MILLISECONDS);//等待锁
if(!locked){
logger.error("can not lock...");
}
}
} catch (Exception e) {
throw new LockException(e);
}
}
public boolean tryLock() {
try {
if(lockName.contains(SPLITSTR)){
throw new LockException("lockName can not contains \\u000B");
}
//创建临时子节点
myZnode = zk.create(root + "/" + lockName + SPLITSTR, data, ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
//取出所有子节点
List<String> subNodes = zk.getChildren(root, false);
//取出所有lockName的锁
List<String> lockObjNodes = new ArrayList<>();
for (String node : subNodes) {
String _node = node.split(SPLITSTR)[0];
if(_node.equals(lockName)){
lockObjNodes.add(node);
}
}
Collections.sort(lockObjNodes);
if(myZnode.equals(root+"/"+lockObjNodes.get(0))){
//如果是最小的节点,则表示取得锁
return true;
}
//如果不是最小的节点,找到比自己小1的节点
String subMyZnode = myZnode.substring(myZnode.lastIndexOf("/") + 1);
waitNode = lockObjNodes.get(Collections.binarySearch(lockObjNodes, subMyZnode) - 1);
} catch (Exception e) {
throw new LockException(e);
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) {
try {
return tryLock() || waitForLock(waitNode, time, unit);
} catch (Exception e) {
throw new LockException(e);
}
}
private boolean waitForLock(String lower, long waitTime, TimeUnit unit) throws InterruptedException, KeeperException {
Stat stat = zk.exists(root + "/" + lower, true);
//判断比自己小一个数的节点是否存在,如果不存在则无需等待锁,同时注册监听
if(stat != null){
this.latch = new CountDownLatch(1);
this.latch.await(waitTime, unit);
this.latch = null;
}
return true;
}
@Override
public void unlock() {
try {
zk.delete(myZnode,-1);
myZnode = null;
} catch (Exception e) {
throw new LockException(e);
}
}
private synchronized ZooKeeper initZk(String server) {
try {
if(zk == null){
zk = new ZooKeeper(server, SESSION_TIMEOUT, this);
}
} catch (IOException e) {
throw new LockException("zk init connect fail" + e.getMessage());
}
return zk;
}
@Override
public void lockInterruptibly() throws InterruptedException {
this.lock();
}
@Override
public Condition newCondition() {
return null;
}
private class LockException extends RuntimeException {
private static final long serialVersionUID = 1L;
private LockException(String e){
super(e);
}
private LockException(Exception e){
super(e);
}
}
}
c、代码分析
lock:根据根创建锁节点,然后获取当前已经存在锁的节点,如果第一个节点为自己创建,说明没有锁,不是自己加锁,则给自己创建节点的上一个节点加监听,线程阻塞至上一个节点释放,并通知我,或者等待超时。
unlock:删除自己创建的节点,zookeeper会自动通知加在节点上的监听。
3、两者区别
Redis分布式锁,必须使用者自己间隔时间轮询去尝试加锁,当锁被释放后,存在多线程去争抢锁,并且可能每次间隔时间去尝试锁的时候,都不成功,对性能浪费很大。
Zookeeper分布锁,首先创建加锁标志文件,如果需要等待其他锁,则添加监听后等待通知或者超时,当有锁释放,无须争抢,按照节点顺序,依次通知使用者。