拜托,别再问我Zookeeper如何实现分布式锁了!

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#ffffff","name":"user"}},{"type":"bgcolor","attrs":{"color":"#FC8F99","name":"red"}}],"text":"导读"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"真是有人("},{"type":"codeinline","content":[{"type":"text","text":"锁"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":")的地方就有江湖("},{"type":"codeinline","content":[{"type":"text","text":"事务"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"),今天不谈江湖,来撩撩人。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"分布式锁的概念、为什么使用分布式锁,想必大家已经很清楚了。前段时间作者写过Redis是如何实现分布式锁,今天这篇文章来谈谈Zookeeper是如何实现分布式锁的。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"陈某今天分别从如下几个方面来详细讲讲ZK如何实现分布式锁:ZK的四种节点排它锁的实现读写锁的实现Curator实现分步式锁"}]}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#ffffff","name":"user"}},{"type":"bgcolor","attrs":{"color":"#FC8F99","name":"red"}}],"text":"ZK的四种节点"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"持久性节点:节点创建后将会一直存在"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"临时节点:临时节点的生命周期和当前会话绑定,一旦当前会话断开临时节点也会删除,当然可以主动删除。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"持久有序节点:节点创建一直存在,并且zk会自动为节点加上一个自增的后缀作为新的节点名称。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"临时有序节点:保留临时节点的特性,并且zk会自动为节点加上一个自增的后缀作为新的节点名称。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#ffffff","name":"user"}},{"type":"bgcolor","attrs":{"color":"#FC8F99","name":"red"}}],"text":"排它锁的实现"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"排他锁的实现相对简单一点,利用了zk的创建节点不能重名的特性"},{"type":"text","text":"。如下图:"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f2/f2df6ff09cf380622883315a338ba689.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"根据上图分析大致分为如下步骤:尝试获取锁:创建"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"临时节点"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":",zk会保证只有一个客户端创建成功。创建临时节点成功,获取锁成功,执行业务逻辑,业务执行完成后删除锁。创建临时节点失败,阻塞等待。监听删除事件,一旦临时节点删除了,表示互斥操作完成了,可以再次尝试获取锁。递归:获取锁的过程是一个递归的操作,"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"获取锁->监听->获取锁"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如何避免死锁"},{"type":"text","text":":创建的是临时节点,当服务宕机会话关闭后临时节点将会被删除,锁自动释放。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"代码实现"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"作者参照JDK锁的实现方式加上模板方法模式的封装,封装接口如下:"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * @Description ZK分布式锁的接口\n * @Author 陈某\n * @Date 2020/4/7 22:52\n */\npublic interface ZKLock {\n /**\n * 获取锁\n */\n void lock() throws Exception;\n\n /**\n * 解锁\n */\n void unlock() throws Exception;\n}\n"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"模板抽象类如下:"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * @Description 排他锁,模板类\n * @Author 陈某\n * @Date 2020/4/7 22:55\n */\npublic abstract class AbstractZKLockMutex implements ZKLock {\n\n /**\n * 节点路径\n */\n protected String lockPath;\n\n /**\n * zk客户端\n */\n protected CuratorFramework zkClient;\n\n private AbstractZKLockMutex(){}\n\n public AbstractZKLockMutex(String lockPath,CuratorFramework client){\n this.lockPath=lockPath;\n this.zkClient=client;\n }\n\n /**\n * 模板方法,搭建的获取锁的框架,具体逻辑交于子类实现\n * @throws Exception\n */\n @Override\n public final void lock() throws Exception {\n //获取锁成功\n if (tryLock()){\n System.out.println(Thread.currentThread().getName()+\"获取锁成功\");\n }else{ //获取锁失败\n //阻塞一直等待\n waitLock();\n //递归,再次获取锁\n lock();\n }\n }\n\n /**\n * 尝试获取锁,子类实现\n */\n protected abstract boolean tryLock() ;\n\n /**\n * 等待获取锁,子类实现\n */\n protected abstract void waitLock() throws Exception;\n\n /**\n * 解锁:删除节点或者直接断开连接\n */\n @Override\n public abstract void unlock() throws Exception;\n}\n"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"排他锁的具体实现类如下:"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * @Description 排他锁的实现类,继承模板类 AbstractZKLockMutex\n * @Author 陈某\n * @Date 2020/4/7 23:23\n */\n@Data\npublic class ZKLockMutex extends AbstractZKLockMutex {\n\n /**\n * 用于实现线程阻塞\n */\n private CountDownLatch countDownLatch;\n\n public ZKLockMutex(String lockPath,CuratorFramework zkClient){\n super(lockPath,zkClient);\n }\n\n /**\n * 尝试获取锁:直接创建一个临时节点,如果这个节点存在创建失败抛出异常,表示已经互斥了,\n * 反之创建成功\n * @throws Exception\n */\n @Override\n protected boolean tryLock() {\n try {\n zkClient.create()\n //临时节点\n .withMode(CreateMode.EPHEMERAL)\n //权限列表 world:anyone:crdwa\n .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)\n .forPath(lockPath,\"lock\".getBytes());\n return true;\n }catch (Exception ex){\n return false;\n }\n }\n\n /**\n * 等待锁,一直阻塞监听\n * @return 成功获取锁返回true,反之返回false\n */\n @Override\n protected void waitLock() throws Exception {\n //监听节点的新增、更新、删除\n final NodeCache nodeCache = new NodeCache(zkClient, lockPath);\n //启动监听\n nodeCache.start();\n ListenerContainer listenable = nodeCache.getListenable();\n\n //监听器\n NodeCacheListener listener=()-> {\n //节点被删除,此时获取锁\n if (nodeCache.getCurrentData() == null) {\n //countDownLatch不为null,表示节点存在,此时监听到节点删除了,因此-1\n if (countDownLatch != null)\n countDownLatch.countDown();\n }\n };\n //添加监听器\n listenable.addListener(listener);\n\n //判断节点是否存在\n Stat stat = zkClient.checkExists().forPath(lockPath);\n //节点存在\n if (stat!=null){\n countDownLatch=new CountDownLatch(1);\n //阻塞主线程,监听\n countDownLatch.await();\n }\n //移除监听器\n listenable.removeListener(listener);\n }\n\n /**\n * 解锁,直接删除节点\n * @throws Exception\n */\n @Override\n public void unlock() throws Exception {\n zkClient.delete().forPath(lockPath);\n }\n}\n"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"可重入性排他锁如何设计"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"可重入的逻辑很简单,在本地保存一个"},{"type":"codeinline","content":[{"type":"text","text":"ConcurrentMap"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":","},{"type":"codeinline","content":[{"type":"text","text":"key"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"是当前线程,"},{"type":"codeinline","content":[{"type":"text","text":"value"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"是定义的数据,结构如下:"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" private final ConcurrentMap threadData = Maps.newConcurrentMap();\n"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"重入的伪代码如下:"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public boolean tryLock(){\n //判断当前线程是否在threadData保存过\n //存在,直接return true\n //不存在执行获取锁的逻辑\n //获取成功保存在threadData中\n}\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#ffffff","name":"user"}},{"type":"bgcolor","attrs":{"color":"#FC8F99","name":"red"}}],"text":"读写锁的实现"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"读写锁分为读锁和写锁,区别如下:读锁允许多个线程同时读数据,但是在读的同时不允许写线程修改。写锁在获取后,不允许多个线程同时写或者读。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如何实现读写锁?ZK中有一类节点叫临时有序节点,上文有介绍。下面我们来利用临时有序节点来实现读写锁的功能。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"读锁的设计"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"读锁允许多个线程同时进行读,并且在读的同时不允许线程进行写操作,实现原理如下图:"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/24/24e9c3e5f501e29d6682e30b9a69b8d0.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"根据上图,获取一个读锁分为以下步骤:创建临时有序节点(当前线程拥有的"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"读锁"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"或称作"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"读节点"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":")。获取路径下所有的子节点,并进行"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"从小到大"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"排序获取当前节点前的临近写节点(写锁)。如果不存在的临近写节点,则成功获取读锁。如果存在临近写节点,对其监听删除事件。一旦监听到删除事件,重复2,3,4,5的步骤(递归)。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"写锁的设计"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"线程一旦获取了写锁,不允许其他线程读和写。实现原理如下:"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a9/a95d774f487309309cf0494074765afb.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"从上图可以看出唯一和写锁不同的就是监听的节点,这里是监听临近节点(读节点或者写节点),读锁只需要监听写节点,步骤如下:创建临时有序节点(当前线程拥有的"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"写锁"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"或称作"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"写节点"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":")。获取路径下的所有子节点,并进行"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"从小到大"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"排序。获取当前节点的临近节点(读节点和写节点)。如果不存在临近节点,则成功获取锁。如果存在临近节点,对其进行监听删除事件。一旦监听到删除事件,重复2,3,4,5的步骤(递归)。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"如何监听"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"无论是写锁还是读锁都需要监听前面的节点,不同的是读锁只监听临近的写节点,写锁是监听临近的所有节点,抽象出来看其实是一种链式的监听,如下图:"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c1/c130a5ac212cd27922b3bcb7e169731f.png","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"每一个节点都在监听前面的临近节点,一旦前面一个节点删除了,再从新排序后监听前面的节点,这样递归下去。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"代码实现"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"作者简单的写了读写锁的实现,先造出来再优化,不建议用在生产环境。代码如下:"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class ZKLockRW {\n\n /**\n * 节点路径\n */\n protected String lockPath;\n\n /**\n * zk客户端\n */\n protected CuratorFramework zkClient;\n\n /**\n * 用于阻塞线程\n */\n private CountDownLatch countDownLatch=new CountDownLatch(1);\n\n private final static String WRITE_NAME=\"_W_LOCK\";\n\n private final static String READ_NAME=\"_R_LOCK\";\n\n public ZKLockRW(String lockPath, CuratorFramework client) {\n this.lockPath=lockPath;\n this.zkClient=client;\n }\n\n /**\n * 获取锁,如果获取失败一直阻塞\n * @throws Exception\n */\n public void lock() throws Exception {\n //创建节点\n String node = createNode();\n //阻塞等待获取锁\n tryLock(node);\n countDownLatch.await();\n }\n\n /**\n * 创建临时有序节点\n * @return\n * @throws Exception\n */\n private String createNode() throws Exception {\n //创建临时有序节点\n return zkClient.create()\n .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)\n .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)\n .forPath(lockPath);\n }\n\n /**\n * 获取写锁\n * @return\n */\n public ZKLockRW writeLock(){\n return new ZKLockRW(lockPath+WRITE_NAME,zkClient);\n }\n\n /**\n * 获取读锁\n * @return\n */\n public ZKLockRW readLock(){\n return new ZKLockRW(lockPath+READ_NAME,zkClient);\n }\n\n private void tryLock(String nodePath) throws Exception {\n //获取所有的子节点\n List childPaths = zkClient.getChildren()\n .forPath(\"/\")\n .stream().sorted().map(o->\"/\"+o).collect(Collectors.toList());\n\n //第一个节点就是当前的锁,直接获取锁。递归结束的条件\n if (nodePath.equals(childPaths.get(0))){\n countDownLatch.countDown();\n return;\n }\n\n //1. 读锁:监听最前面的写锁,写锁释放了,自然能够读了\n if (nodePath.contains(READ_NAME)){\n //查找临近的写锁\n String preNode = getNearWriteNode(childPaths, childPaths.indexOf(nodePath));\n if (preNode==null){\n countDownLatch.countDown();\n return;\n }\n NodeCache nodeCache=new NodeCache(zkClient,preNode);\n nodeCache.start();\n ListenerContainer listenable = nodeCache.getListenable();\n listenable.addListener(() -> {\n //节点删除事件\n if (nodeCache.getCurrentData()==null){\n //继续监听前一个节点\n String nearWriteNode = getNearWriteNode(childPaths, childPaths.indexOf(preNode));\n if (nearWriteNode==null){\n countDownLatch.countDown();\n return;\n }\n tryLock(nearWriteNode);\n }\n });\n }\n\n //如果是写锁,前面无论是什么锁都不能读,直接循环监听上一个节点即可,直到前面无锁\n if (nodePath.contains(WRITE_NAME)){\n String preNode = childPaths.get(childPaths.indexOf(nodePath) - 1);\n NodeCache nodeCache=new NodeCache(zkClient,preNode);\n nodeCache.start();\n ListenerContainer listenable = nodeCache.getListenable();\n listenable.addListener(() -> {\n //节点删除事件\n if (nodeCache.getCurrentData()==null){\n //继续监听前一个节点\n tryLock(childPaths.get(childPaths.indexOf(preNode) - 1<0?0:childPaths.indexOf(preNode) - 1));\n }\n });\n }\n }\n\n /**\n * 查找临近的写节点\n * @param childPath 全部的子节点\n * @param index 右边界\n * @return\n */\n private String getNearWriteNode(List childPath,Integer index){\n for (int i = 0; i < index; i++) {\n String node = childPath.get(i);\n if (node.contains(WRITE_NAME))\n return node;\n\n }\n return null;\n }\n\n}\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#ffffff","name":"user"}},{"type":"bgcolor","attrs":{"color":"#FC8F99","name":"red"}}],"text":"Curator实现分步式锁"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Curator是Netflix公司开源的一个Zookeeper客户端,与Zookeeper提供的原生客户端相比,Curator的抽象层次更高,简化了Zookeeper客户端的开发量。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Curator在分布式锁方面已经为我们封装好了,大致实现的思路就是按照作者上述的思路实现的。中小型互联网公司还是建议直接使用框架封装好的,毕竟稳定,有些大型的互联公司都是手写的,牛逼啊。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"创建一个排他锁很简单,如下:"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//arg1:CuratorFramework连接对象,arg2:节点路径\nlock=new InterProcessMutex(client,path);\n//获取锁\nlock.acquire();\n//释放锁\nlock.release();\n"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"更多的API请参照官方文档,不是此篇文章重点。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"至此ZK实现分布式锁就介绍完了,如有想要源码的朋友,老规矩,回复关键词"},{"type":"codeinline","content":[{"type":"text","text":"分布式锁"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"获取。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#ffffff","name":"user"}},{"type":"bgcolor","attrs":{"color":"#FC8F99","name":"red"}}],"text":"一点小福利"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"对于Zookeeper不太熟悉的朋友,陈某特地花费两天时间总结了ZK的常用知识点,包括ZK常用shell命令、ZK权限控制、Curator的基本操作API。目录如下:"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e0/e033980a42cd68fb818f0a6ae2f9eeaf.png","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"需要上面PDF文件的朋友,老规矩,回复关键词"},{"type":"codeinline","content":[{"type":"text","text":"ZK总结"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章