zookeeper实现分布式锁

zookeeper实现分布式锁

上一章通过使用临时有序节点保证有序的资源竞争,今天我们就完整的实现一个使用zookeepr的分布式锁。

在多并发的情况下,在单机服务上,通过锁来保证在同一时间时,只有一个线程可以执行,用于保证临界资源的最终的结果准确性
那么在分布式服务时,要解决只有同一台机器执行一段有对临界资源同时访问的问题,就是通过分布式锁来保证其实现

实现分布式服务的框架有 Redis zookeeper 以及通过数据库表的形式来实现分布式锁
对于分布式锁 我们同认为需要和单机并发锁所具备的功能相似。
zookeeper有两种实现方式

  1. 通过对于临时有序节点的顺序监听实现
  2. 根据Zookeeper的开源客户端Curator实现分布式锁

现在就来实现一个比较简单一些的分布式锁吧。(通过对于临时有序节点的顺序监听实现)

import org.I0Itec.zkclient.ZkClient;
 
public abstract class AbstractLock {
 
	//zk地址和端口
	public static final String ZK_ADDR = "192.168.1.11:2181";
	//超时时间
	public static final int SESSION_TIMEOUT = 10000;
	//创建zk
	protected ZkClient zkClient = new ZkClient(ZK_ADDR, SESSION_TIMEOUT);
	
	
	/**
	 * 可以认为是模板模式,两个子类分别实现它的抽象方法
	 * 1,简单的分布式锁
	 * 2,高性能分布式锁
	 */
	public void getLock() {
		String threadName = Thread.currentThread().getName();
		if (tryLock()) {
			System.out.println(threadName+"-获取锁成功");
		}else {
			System.out.println(threadName+"-获取锁失败,进行等待...");
			waitLock();
			//递归重新获取锁
			getLock();
		}
	}
	
	public abstract void releaseLock();
	
	public abstract boolean tryLock();
	
	public abstract void waitLock();
}
import java.util.concurrent.CountDownLatch;
 
import org.I0Itec.zkclient.IZkDataListener;
 
/**
 * @author hongtaolong
 * 简单的分布式锁的实现
 */
public class SimpleZkLock extends AbstractLock {
 
	private static final String NODE_NAME = "/zk_lock";
	
	private CountDownLatch countDownLatch;
	
	@Override
	public void releaseLock() {
		if (null != zkClient) {
			//删除节点
			zkClient.delete(NODE_NAME);
			zkClient.close();
			System.out.println(Thread.currentThread().getName()+"-释放锁成功");
		}
		
	}
 
	//直接创建临时节点,如果创建成功,则表示获取了锁,创建不成功则处理异常
	@Override
	public boolean tryLock() {
		if (null == zkClient) return false;
		try {
			zkClient.createEphemeral(NODE_NAME);
			return true;
		} catch (Exception e) {
			return false;
		}
	}
 
	@Override
	public void waitLock() {
		//监听器
		IZkDataListener iZkDataListener = new IZkDataListener() {
			//节点被删除回调
			@Override
			public void handleDataDeleted(String dataPath) throws Exception {
				if (countDownLatch != null) {
					countDownLatch.countDown();
				}
			}
			//节点改变被回调
			@Override
			public void handleDataChange(String dataPath, Object data) throws Exception {
				// TODO Auto-generated method stub
				
			}
		};
		zkClient.subscribeDataChanges(NODE_NAME, iZkDataListener);
		//如果存在则阻塞
		if (zkClient.exists(NODE_NAME)) {
			countDownLatch = new CountDownLatch(1);
			try {
				countDownLatch.await();
				System.out.println(Thread.currentThread().getName()+" 等待获取锁...");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//删除监听
		zkClient.unsubscribeDataChanges(NODE_NAME, iZkDataListener);
	}
 
}

这里大家可以根据我上一个文章进行比较 zookeeper事件监听,其本质还是对于对于zookeeper在创建临时有序的节点时,会根据创建的顺序来保证获取锁的顺序,并在对应的节点上有相应的事件监听来保证获取最新的执行节点

下边时根据Zookeeper的开源客户端Curator实现分布式锁

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class CuratorDistributeLock {

    public static void main(String[] args) {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.11:2181",retryPolicy);
        client.start();
        CuratorFramework client2 = CuratorFrameworkFactory.newClient("192.168.1.11:2181",retryPolicy);
        client2.start();
        //创建分布式锁, 锁空间的根节点路径为/zk_lock/lock
        InterProcessMutex mutex  = new InterProcessMutex(client,"/zk_lock/lock");
        final InterProcessMutex mutex2  = new InterProcessMutex(client2,"/zk_lock/lock");
        try {
            mutex.acquire();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //获得了锁, 进行业务流程
        System.out.println("clent Enter mutex");
         Thread client2Th = new Thread(new Runnable() {
             @Override
             public void run() {
                 try {
                 	//加锁方法  下边会有源码展示
                     mutex2.acquire();
                     System.out.println("client2 Enter mutex");
                     //释放锁  
                     mutex2.release();
                     System.out.println("client2 release lock");

                 }catch (Exception e){
                     e.printStackTrace();
                 }

             }
         });
        client2Th.start();
        //完成业务流程, 释放锁
        try {
            Thread.sleep(5000);
            mutex.release();
            System.out.println("client release lock");
            client2Th.join();
        } catch (Exception e) {
            e.printStackTrace();
        }

        //关闭客户端
        client.close();
    }
}

追看 acquire 方法会注意到以下方法

String attemptLock(long time, TimeUnit unit, byte[] lockNodeBytes) throws Exception
    {
        .....
        while ( !isDone )
        {
            isDone = true;

            try
            {
                //创建临时有序节点
                ourPath = driver.createsTheLock(client, path, localLockNodeBytes);
                //判断自己是否最小序号的节点,如果不是添加监听前面节点被删的通知
                hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);
            }
        }
        //如果获取锁返回节点路径
        if ( hasTheLock )
        {
            return ourPath;
        }
        ....
    }

由此来看 开源框架的实现也是来自与对于 zookeeper 临时有序节点的封装。

参考文章:
https://www.jianshu.com/p/91976b27a188
https://www.pianshen.com/article/4687316303/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值