1.分布式共享锁的简单实现
在分布式系统中如何对进程进行调度,假设在第一台机器上挂载了一个资源,然后这三个物理分布的进程都要竞争这个资源,但我们又不希望他们同时
进行访问,这时候我们就需要一个协调器,来让他们有序的来访问这个资源。这个协调器就是我们经常提到锁,比如说"进程-1"在使用该资源的时候,会先去
获得锁,"进程1"获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,"进程1"用完该资源以后就将锁释放掉,让其他进程来获得锁,那么通过这
个锁机制,我们就能保证了分布式系统中多个进程能够有序的访问该临界资源。那么我们把这个分布式环境下的这个锁叫作分布式锁。这个分布式锁也就是我们
分布式协调技术实现的核心内容,那么如何实现这个分布式呢?
分布式协调技术主要用来解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某种临界资源,防止造成"脏数据"的后果
代码实现:分布式多进程模式实现
import java.util.Collections; import java.util.List; import java.util.Random; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooKeeper; public class DistributedClientLock { // 会话超时 private static final int SESSION_TIMEOUT = 2000; // zookeeper集群地址 private String hosts = "shizhan2:2183,shizhan3:2183,shizhan5:2183"; private String groupNode = "locks"; private String subNode = "sub"; private boolean haveLock = false; private ZooKeeper zk; // 记录自己创建的子节点路径 private volatile String thisPath; /** * 连接zookeeper */ public void connectZookeeper() throws Exception { zk = new ZooKeeper(hosts, SESSION_TIMEOUT, new Watcher() { public void process(WatchedEvent event) { try { System.out.println("=============事件监听============="); // 判断事件类型,此处只处理子节点变化事件 if (event.getType() == EventType.NodeChildrenChanged && event.getPath().equals("/" + groupNode)) { //获取子节点,并对父节点进行监听 List<String> childrenNodes = zk.getChildren("/" + groupNode, true); String thisNode = thisPath.substring(("/" + groupNode + "/").length()); System.out.println("thisNode------"+thisNode); // 去比较是否自己是最小id Collections.sort(childrenNodes); System.out.println("childrenNodes.indexOf(thisNode)"+childrenNodes.indexOf(thisNode)); if (childrenNodes.indexOf(thisNode) == 0) { //访问共享资源处理业务,并且在处理完成之后删除锁 doSomething(); //重新注册一把新的锁 thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL); } } } catch (Exception e) { e.printStackTrace(); } } }); // 1、程序一进来就先注册一把锁到zk上 thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL); // wait一小会,便于观察 Thread.sleep(new Random().nextInt(1000)); // 从zk的锁父目录下,获取所有子节点,并且注册对父节点的监听 List<String> childrenNodes = zk.getChildren("/" + groupNode, true); //如果争抢资源的程序就只有自己,则可以直接去访问共享资源 if (childrenNodes.size() == 1) { System.out.println("当前服务器数目:"+childrenNodes.size()); doSomething(); thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); } } /** * 处理业务逻辑,并且在最后释放锁 */ private void doSomething() throws Exception { try { System.out.println("gain lock: " + thisPath); Thread.sleep(2000); // do something } finally { System.out.println("finished: " + thisPath); zk.delete(this.thisPath, -1); } } public static void main(String[] args) throws Exception { DistributedClientLock dl = new DistributedClientLock(); dl.connectZookeeper(); Thread.sleep(Long.MAX_VALUE); } }
代码实现2:
import java.util.Collections; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; public class DistributedClientLock { // 超时时间 private static final int SESSION_TIMEOUT = 5000; // zookeeper server列表 private String hosts = "shizhan2:2183,shizhan3:2183,shizhan5:2183"; private String groupNode = "locks"; private String subNode = "sub"; private ZooKeeper zk; // 当前client创建的子节点 private String thisPath; // 当前client等待的子节点 private String waitPath; private CountDownLatch latch = new CountDownLatch(1); /** * 连接zookeeper */ public void connectZookeeper() throws Exception { zk = new ZooKeeper(hosts, SESSION_TIMEOUT, new Watcher() { public void process(WatchedEvent event) { try { // 连接建立时, 打开latch, 唤醒wait在该latch上的线程 if (event.getState() == KeeperState.SyncConnected) { latch.countDown(); } // 发生了waitPath的删除事件 if (event.getType() == EventType.NodeDeleted && event.getPath().equals(waitPath)) { doSomething(); } } catch (Exception e) { e.printStackTrace(); } } }); // 等待连接建立 latch.await(); // 创建子节点 thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); // wait一小会, 让结果更清晰一些 Thread.sleep(10); // 注意, 没有必要监听"/locks"的子节点的变化情况 List<String> childrenNodes = zk.getChildren("/" + groupNode, false); // 列表中只有一个子节点, 那肯定就是thisPath, 说明client获得锁 if (childrenNodes.size() == 1) { doSomething(); } else { String thisNode = thisPath.substring(("/" + groupNode + "/").length()); // 排序 Collections.sort(childrenNodes); int index = childrenNodes.indexOf(thisNode); if (index == -1) { // never happened } else if (index == 0) { // inddx == 0, 说明thisNode在列表中最小, 当前client获得锁 doSomething(); } else { // 获得排名比thisPath前1位的节点 this.waitPath = "/" + groupNode + "/" + childrenNodes.get(index - 1); // 在waitPath上注册监听器, 当waitPath被删除时, zookeeper会回调监听器的process方法 zk.getData(waitPath, true, new Stat()); } } } private void doSomething() throws Exception { try { System.out.println("gain lock: " + thisPath); Thread.sleep(2000); // do something } finally { System.out.println("finished: " + thisPath); // 将thisPath删除, 监听thisPath的client将获得通知 // 相当于释放锁 zk.delete(this.thisPath, -1); } } public static void main(String[] args) throws Exception { for (int i = 0; i < 10; i++) { new Thread() { public void run() { try { DistributedClientLock dl = new DistributedClientLock(); dl.connectZookeeper(); } catch (Exception e) { e.printStackTrace(); } } }.start(); } Thread.sleep(Long.MAX_VALUE); }}
时序图: