ZooKeeper 笔记(6) 分布式锁
目前分布式锁,比较成熟、主流的方案有基于redis及基于zookeeper的二种方案。
大体来讲,基于redis的分布式锁核心指令为SETNX,即如果目标key存在,写入缓存失败返回0,反之如果目标key不存在,写入缓存成功返回1,通过区分这二个不同的返回值,可以认为SETNX成功即为获得了锁。
redis分布式锁,看上去很简单,但其实要考虑周全,并不容易,网上有一篇文章讨论得很详细:http://blog.csdn.net/ugg/article/details/41894947/,有兴趣的可以阅读一下。
其主要问题在于某些异常情况下,锁的释放会有问题,比如SETNX成功,应用获得锁,这时出于某种原因,比如网络中断,或程序出异常退出,会导致锁无法及时释放,只能依赖于缓存的过期时间,但是过期时间这个值设置多大,也是一个纠结的问题,设置小了,应用处理逻辑很复杂的话,可能会导致锁提前释放,如果设置大了,又会导致锁不能及时释放,所以那篇文章中针对这些细节讨论了很多。
而基于zk的分布式锁,在锁的释放问题上处理起来要容易一些,其大体思路是利用zk的“临时顺序”节点,需要获取锁时,在某个约定节点下注册一个临时顺序节点,然后将所有临时节点按小从到大排序,如果自己注册的临时节点正好是最小的,表示获得了锁。(zk能保证临时节点序号始终递增,所以如果后面有其它应用也注册了临时节点,序号肯定比获取锁的应用更大)
当应用处理完成,或者处理过程中出现某种原因,导致与zk断开,超过时间阈值(可配置)后,zk server端会自动删除该临时节点,即:锁被释放。所有参与锁竞争的应用,只要监听父路径的子节点变化即可,有变化时(即:有应用断开或注册时),开始抢锁,抢完了大家都在一边等着,直到有新变化时,开始新一轮抢锁。
关于zk的分布式锁,网上也有一篇文章写得不错,见http://blog.csdn.net/desilting/article/details/41280869
个人感觉:zk做分布式锁机制更完善,但zk抗并发的能力弱于redis,性能上略差,建议如果并发要求高,锁竞争激烈,可考虑用redis,如果抢锁的频度不高,用zk更适合。
最后送福利时间到:
文中提到的基于zk分布式锁的那篇文章,逻辑上虽然没有问题,但是有些场景下,锁的数量限制可能要求不止1个,比如:某些应用,我希望同时启动2个实例来处理,但是出于HA的考虑,又担心这二个实例会挂掉,这时可以启动4个(或者更多),这些实例中,只允许2个抢到锁的实例可以进行业务处理,其它实例处于standby状态(即:备胎),如果这二个抢到锁的实例挂了(比如异常退出),那么standby的实例会得到锁,即:备胎转正,开始正常业务处理,从而保证了系统的HA。
对于这些场景,我封装了一个抽象类,大家可在此基础上自行修改:(主要看明白思路就行,代码细节并不重要)
package cn.cnblogs.yjmyzz.zookeeper; import org.I0Itec.zkclient.ZkClient;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; /**
* Created by yangjunming on 5/27/16.
* 基于Zookeeper的分布式锁
*/
public abstract class AbstractLock { private int lockNumber = 1; //允许获取的锁数量(默认为1,即最小节点=自身时,认为获得锁)
private ZkClient zk = null;
private String rootNode = "/lock"; //根节点名称
private String selfNode;
private final String className = this.getClass().getSimpleName(); //当前实例的className
private String selfNodeName;//自身注册的临时节点名
private boolean handling = false;
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private static final JsonUtil jsonUtil = new FastJsonUtil();
private static final String SPLIT = "/";
private String selfNodeFullName; /**
* 通过Zk获取分布式锁
*/
protected void getLock(int lockNumber) {
setLockNumber(lockNumber);
initBean();
initNode();
subscribe();
register();
heartBeat();
remainRunning();
} protected void getLock() {
getLock(1);
} /**
* 初始化结点
*/
private void initNode() { String error;
if (!rootNode.startsWith(SPLIT)) {
error = "rootNode必须以" + SPLIT + "开头";
logger.error(error);
throw new RuntimeException(error);
} if (rootNode.endsWith(SPLIT)) {
error = "不能以" + SPLIT + "结尾";
logger.error(error);
throw new RuntimeException(error);
} int start = 1;
int index = rootNode.indexOf(SPLIT, start);
String path;
while (index != -1) {
path = rootNode.substring(0, index);
if (!zk.exists(path)) {
zk.createPersistent(path);
}
start = index + 1;
if (start >= rootNode.length()) {
break;
}
index = rootNode.indexOf(SPLIT, start);
} if (start < rootNode.length()) {
if (!zk.exists(rootNode)) {
zk.createPersistent(rootNode);
}
} selfNode = rootNode + SPLIT + className; if (!zk.exists(selfNode)) {
zk.createPersistent(selfNode);
}
} /**
* 向zk注册自身节点
*/
private void register() {
selfNodeName = zk.createEphemeralSequential(selfNode + SPLIT, StringUtils.EMPTY);
if (!StringUtils.isEmpty(selfNodeName)) {
selfNodeFullName = selfNodeName;
logger.info("自身节点:" + selfNodeName + ",注册成功!");
selfNodeName = selfNodeName.substring(selfNode.length() + 1);
}
checkMin();
} /**
* 订阅zk的节点变化
*/
private void subscribe() {
zk.subscribeChildChanges(selfNode, (parentPath, currentChilds) -> {
checkMin();
});
} /**
* 检测是否获得锁
*/
private void checkMin() {
List<String> list = zk.getChildren(selfNode);
if (CollectionUtils.isEmpty(list)) {
logger.error(selfNode + " 无任何子节点!");
lockFail();
handling = false;
return;
}
//按序号从小到大排
Collections.sort(list); //如果自身ID在前N个锁中,则认为获取成功
int max = Math.min(getLockNumber(), list.size());
for (int i = 0; i < max; i++) {
if (list.get(i).equals(selfNodeName)) {
if (!handling) {
lockSuccess();
handling = true;
logger.info("获得锁成功!");
}
return;
}
} int selfIndex = list.indexOf(selfNodeName);
if (selfIndex > 0) {
logger.info("前面还有节点" + list.get(selfIndex - 1) + ",获取锁失败!");
} else {
logger.info("获取锁失败!");
}
lockFail(); handling = false;
} /**
* 获得锁成功的处理回调
*/
protected abstract void lockSuccess(); /**
* 获得锁失败的处理回调
*/
protected abstract void lockFail(); /**
* 初始化相关的Bean对象
*/
protected abstract void initBean(); protected void setZkClient(ZkClient zk) {
this.zk = zk;
} protected int getLockNumber() {
return lockNumber;
} protected void setLockNumber(int lockNumber) {
this.lockNumber = lockNumber;
} protected void setRootNode(String value) {
this.rootNode = value;
} /**
* 防程序退出
*/
private void remainRunning() {
byte[] lock = new byte[0];
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("remainRunning出错:", e);
}
}
} /**
* 定时向zk发送心跳
*/
private void heartBeat() {
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate(() -> {
HeartBeat heartBeat = new HeartBeat();
heartBeat.setHostIp(NetworkUtil.getHostAddress());
heartBeat.setHostName(NetworkUtil.getHostName());
heartBeat.setLastTime(new Date());
heartBeat.setPid(RuntimeUtil.getPid());
zk.writeData(selfNodeFullName, jsonUtil.toJson(heartBeat));
}, 0, 15, TimeUnit.SECONDS);
}
}
这个类中,提供了三个抽象方法:
/**
* 获得锁成功的处理回调
*/
protected abstract void lockSuccess(); /**
* 获得锁失败的处理回调
*/
protected abstract void lockFail(); /**
* 初始化相关的Bean对象
*/
protected abstract void initBean();
用于处理抢锁成功、抢锁失败、及开抢前的一些对象初始化处理,子类继承后,只要实现这3个具体的方法即可,同时该抽象类默认还提供了心跳机制,用于定时向zk汇报自身的健康状态。
ZooKeeper 笔记(6) 分布式锁的更多相关文章
- zookeeper 笔记--curator分布式锁
使用ZK实现分布式独占锁, 原理就是利用ZK同级节点的唯一性. Curator框架下的一些分布式锁工具InterProcessMutex:分布式可重入排它锁 InterProcessSemaphore ...
- 如何用Zookeeper来实现分布式锁?
什么是Zookeeper临时顺序节点? 例如 : / 动物 植物 猫 仓鼠 荷花 松树 Zookeeper的数据存储结构就像一棵树,这棵树由节点组成,这种节点叫做Zonde.# Znode分为四种类型 ...
- 基于zookeeper实现的分布式锁
基于zookeeper实现的分布式锁 2011-01-27 • 技术 • 7 条评论 • jiacheo •14,941 阅读 A distributed lock base on zookeeper ...
- java使用zookeeper实现的分布式锁示例
java使用zookeeper实现的分布式锁示例 作者: 字体:[增加 减小] 类型:转载 时间:2014-05-07我要评论 这篇文章主要介绍了java使用zookeeper实现的分布式锁示例,需要 ...
- 基于Zookeeper实现多进程分布式锁
一.zookeeper简介及基本操作 Zookeeper 并不是用来专门存储数据的,它的作用主要是用来维护和监控你存储的数据的状态变化.当对目录节点监控状态打开时,一旦目录节点的状态发生变化,Watc ...
- 利用ZooKeeper简单实现分布式锁
1.分布式锁的由来: 在程序开发过程中不得不考虑的就是并发问题.在java中对于同一个jvm而言,jdk已经提供了lock和同步等.但是在分布式情况下,往往存在多个进程对一些资源产生竞争关系,而这些进 ...
- 基于zookeeper简单实现分布式锁
https://blog.csdn.net/desilting/article/details/41280869 这里利用zookeeper的EPHEMERAL_SEQUENTIAL类型节点及watc ...
- 基于zookeeper实现高性能分布式锁
实现原理:利用zookeeper的持久性节点和Watcher机制 具体步骤: 1.创建持久性节点 zkLock 2.在此父节点下创建子节点列表,name按顺序定义 3.Java程序获取该节点下的所有顺 ...
- zookeeper实现的分布式锁
在分布式系统中,多个jvm对共享资源进行操作时候,要加上锁,这就是分布式锁 利用zookeeper的临时节点的特性,可以实现分布式锁 public class ZookeeperDistrbuteLo ...
随机推荐
- 分布式系统理论进阶 - Raft、Zab
引言 <分布式系统理论进阶 - Paxos>介绍了一致性协议Paxos,今天我们来学习另外两个常见的一致性协议——Raft和Zab.通过与Paxos对比,了解Raft和Zab的核心思想.加 ...
- Android面试一天一题(1Day)
写在前面 该博客思路源于在简书看到goeasyway博主写的Android面试一天一题系列,无copy之意,仅为让自己总结知识点,成长一点点.先感谢各位大神的无私分享~! 关于题目,大部分则出自And ...
- 细说Java主流日志工具库
概述 在项目开发中,为了跟踪代码的运行情况,常常要使用日志来记录信息. 在Java世界,有很多的日志工具库来实现日志功能,避免了我们重复造轮子. 我们先来逐一了解一下主流日志工具. java.util ...
- 微服务(Microservices)—Martin Fowler【翻译】
本文转载自:http://www.cnblogs.com/liuning8023/p/4493156.html -------------------------------------------- ...
- ASP.NET五步打包下载Zip文件
本文版权归博客园和作者吴双共同所有,转载和爬虫请注明原文地址:www.cnblogs.com/tdws 首先分享几个振奋人心的新闻: 1.谷歌已经宣布加入.NET基金会 2.微软加入Linux基金会, ...
- Redis命令拾遗一(字符串类型)
文章归博客园和作者“蜗牛”共同所有 .转载和爬虫请注明原文Redis系列链接 http://www.cnblogs.com/tdws/tag/NoSql/ Redis有五种基本数据类型.他们分别是字符 ...
- 选择目录,选择文件夹的COM组件问题。在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式。请确保您的 Main 函数带有 STAThreadAttribute 标记。 只有将调试器附加到该进程才会引发此异常。
异常: 在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式.请确保您的 Main 函数带有 STAThreadAttribute 标记. 只有将调试器附加到该进程才会引发此异常. ...
- js正则表达式校验非负整数:^\d+$ 或 ^[1-9]\d*|0$
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 在WPF按钮删除默认的鼠标悬停效果
如果你想在应用程序的所有按钮将此风格,那么这种风格可以插入Application.Resources部分的App.xaml页面. <Window.Resources> <Style ...
- Jedis的使用
Redis是常用的key-value存储服务器,Java使用Redis有很多方法,其中官方推荐的是Jedis. 使用Jedis,首先是下载jedis-x.x.x.jar文件并导入工程,然后运行Redi ...