zookeeper实现分布式锁的两种方式

zookeeper实现分布式锁的两种方式

前言:

jdk提供的synchronized和ReentrantLock可以帮助我们在单进程中解决资源共享数据一致性,但是在分布式系统中是多进程多线程,这个时候仅仅使用jdk实现的锁解决不了资源共享的问题,比如某商城中数据库有10个商品,A用户想要买走6个,B用户想买走5个。如果系统运行在单台机器上,我们使用Jdk提供的锁,可以保证数据的一致性,但是当系统运行在多台机器中,JDK实现的锁就会失效,这个时候就应该使用分布式锁,每次只能保证一台机器在请求资源。分布式锁有三种不同的方式实现,分别是数据库提供的分布式锁、redis、zookeeper实现,今天主要讲zookeeper实现分布式锁

在学习zk实现分布所之前,我们应该需要了解一些zk的知识

1、持久节点:客户端断开连接zk不删除persistent类型节点

2、临时节点:客户端断开连接zk删除ephemeral类型节点

3、顺序节点:节点后面会自动生成类似0000001的数字表示顺序

4、节点变化的通知:客户端注册了监听节点变化的时候,会调用回调方法

源码地址:githut源码地址

一、zk实现的简单的分布式锁

1、zk实现简单的分布式锁的思路,主要是抓住一下三点

(1)、当一个客户端成功创建一个节点,另外一个客户端是无法创建同名的节点(达到互斥的效果)

(2)、我们注册该节点的监听时间,当节点删除,会通知其他的客户端,这个时候其他的客户端可以重新去创建该节点(可以认为时拿到锁的客户端释放锁,其他的客户端可以抢锁)

(3)、创建的节点应该时临时节点,这样保证我们在已经拿到锁的客户端挂掉了会自动释放锁

2、图解

zookeeper实现分布式锁的两种方式

3、代码实现

AbstractLock.java

package zklock;

import org.I0Itec.zkclient.ZkClient;

public abstract class AbstractLock {

	//zk地址和端口
	public static final String ZK_ADDR = "192.168.0.230: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();
}

 AbstractLock类是个抽象类,里面getLock使用模板模式,子类分别是简单的zk锁和高性能的zk锁

SimpleZkLock.java

package zklock;

import java.util.concurrent.CountDownLatch;

import org.I0Itec.zkclient.IZkDataListener;

/**
 * @author hongtaolong
 * 简单的分布式锁的实现
 */
public class SimpleZkLock extends AbstractLock {

	private static final String NODE_NAME = "/test_simple_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) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		//删除监听
		zkClient.unsubscribeDataChanges(NODE_NAME, iZkDataListener);
	}

}

SimpleZkLock 表示简单的zk分布式锁,逻辑还是相对比较简单,下面看下测试

LockTest.java

package zklock;

public class LockTest {
	public static void main(String[] args) {
		//模拟多个10个客户端
		for (int i=0;i<10;i++) {
			Thread thread = new Thread(new LockRunnable());
			thread.start();
		}
		
	}
	
	static class LockRunnable implements Runnable{

		@Override
		public void run() {
			AbstractLock zkLock = new SimpleZkLock();
			//AbstractLock zkLock = new HighPerformanceZkLock();
			zkLock.getLock();
			//模拟业务操作
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			zkLock.releaseLock();
		}
		
	}
}

测试结果:

 zookeeper实现分布式锁的两种方式

 打印比较多,还没有全部截完…这个时候我们也能看出使用zk实现的简单的分布式锁存在的性能问题

二、高性能分布式锁

上面使用zk实现的简单的分布式锁,实现比较简单,但是存在性能问题,从上面的打印的结果可以看出、每一次客户端释放锁的时候,其他的客户端都会去抢锁,这就造成了不必要的浪费。那么如果提升性能呢?

1、思路:客户端在抢锁的时候进行排队,客户端只要监听它前一个节点的变化就行,如果前一个节点释放了锁,客户端才去进行抢锁操作,这个时候我们就需要创建顺序节点了

2、图解

(1)客户端排队

zookeeper实现分布式锁的两种方式

 (2)获取锁的逻辑

zookeeper实现分布式锁的两种方式

3、代码实现

HighPerformanceZkLock .java

package zklock;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.I0Itec.zkclient.IZkDataListener;

/**
 * 高性能分布式锁
 * @author hongtaolong
 *
 */
public class HighPerformanceZkLock extends AbstractLock {

	private static final String PATH = "/highPerformance_zklock";
	//当前节点路径
	private String currentPath;
	//前一个节点的路径
	private String beforePath;
	
	private CountDownLatch countDownLatch = null;
	
	public HighPerformanceZkLock() {
		//如果不存在这个节点,则创建持久节点
		if (!zkClient.exists(PATH)) {		
			zkClient.createPersistent(PATH);
		}
	}
	
	@Override
	public void releaseLock() {
		if (null != zkClient) {
			zkClient.delete(currentPath);
	        zkClient.close();
		}

	}

	@Override
	public boolean tryLock() {
		//如果currentPath为空则为第一次尝试加锁,第一次加锁赋值currentPath
		if (null == currentPath || "".equals(currentPath)) {
			//在path下创建一个临时的顺序节点
			currentPath = zkClient.createEphemeralSequential(PATH+"/", "lock");
		}
		//获取所有的临时节点,并排序
		List<String> childrens = zkClient.getChildren(PATH);
		Collections.sort(childrens);
		if (currentPath.equals(PATH+"/"+childrens.get(0))) {
			return true;
		}else {//如果当前节点不是排名第一,则获取它前面的节点名称,并赋值给beforePath
			int pathLength = PATH.length();
			int wz = Collections.binarySearch(childrens, currentPath.substring(pathLength+1));
			beforePath = PATH+"/"+childrens.get(wz-1);
		}
		return false;
	}

	@Override
	public void waitLock() {
		IZkDataListener lIZkDataListener = new IZkDataListener() {
			
			@Override
			public void handleDataDeleted(String dataPath) throws Exception {
				if (null != countDownLatch){
					countDownLatch.countDown();
				}
			}
			
			@Override
			public void handleDataChange(String dataPath, Object data) throws Exception {
				
			}
		};
		//监听前一个节点的变化
		zkClient.subscribeDataChanges(beforePath, lIZkDataListener);
		if (zkClient.exists(beforePath)) {
			countDownLatch = new CountDownLatch(1);
			try {
				countDownLatch.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		zkClient.unsubscribeDataChanges(beforePath, lIZkDataListener);
	}

}

这里只要帖高性能锁的代码了,AbstractLock没变化,LockTest中只要修改一行代码

//AbstractLock zkLock = new SimpleZkLock();

AbstractLock zkLock = new HighPerformanceZkLock();

 测试结果:

 zookeeper实现分布式锁的两种方式

上面是全部的打印结果,可以明显看出要比上面简单实现的分布式锁少很多,这说明性能比上面的好,因为它不会去做无用功嘛,好了zk分布式锁就介绍到这里了,如有错误欢迎指正,谢谢!

 

 

 

 

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/111227.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • python3.3使用tkinter实现猜数字游戏代码

    python3.3使用tkinter实现猜数字游戏代码发布时间:2014-06-18编辑:www.jbxue.com原文地址:http://www.jbxue.com/article/python/22152.htmlpython3.3使用tkint

    2022年7月6日
    18
  • MATLAB插值函数interp1

    MATLAB插值函数interp1插值法    插值法又称“内插法”,是利用函数f(x)在某区间中已知的若干点的函数值,作出适当的特定函数,在区间的其他点上用这特定函数的值作为函数f(x)的近似值,这种方法称为插值法。如果这特定函数是多项式,就称它为插值多项式。线性插值法    线性插值法是指使用连接两个已知量的直线来确定在这两个已知量之间的一个未知量的值的方法。    

    2022年6月13日
    116
  • Python中如何生成exe文件

    Python中如何生成exe文件pipinstallpyinstallerpyinstaller-F文件名.py/dist文件夹里拖出来

    2022年5月31日
    34
  • dubbo负载均衡策略解析

    dubbo负载均衡策略解析dubbo负载均衡策略前言:在上一篇博客中,介绍了zookeeper作为dubbo的注册中心是如何工作的,有一个很重要的点,我们的程序是分布式应用,服务部署在几个节点(服务器)上,当消费者调用服务时,zk返回给dubbo的是一个节点列表,但是dubbo只会选择一台服务器,那么它究竟会选择哪一台呢?这就是dubbo的负载均衡策略了,本篇博客就来聚焦dubbo的负载均衡策略。本篇博客的目录一:负载均衡介绍1.1:负载均衡简介以下是wikipedia对负载均衡的定义:负载均衡改善…

    2022年7月11日
    17
  • IntelliJ IDEA 快捷键说明大全(中英对照、带图示详解)

    IntelliJ IDEA 快捷键说明大全(中英对照、带图示详解)因为觉得网络上的idea快捷键不够详尽,所以特别编写了此篇文章,方便大家使用ideaO(∩_∩)O~其中的英文说明来自于idea的官网资料,中文说明主要来自于自己的领会和理解,英文说明只是作为参考。重要的快捷键会附带图示,进行详细的说明。每一部分会先列出所有的快捷键说明表,如果有不清楚的地方,再看后续的图示详解。1编辑【Editing】快捷键英文说明

    2022年5月14日
    175
  • php属于前端还是后端_php实时推送到前端

    php属于前端还是后端_php实时推送到前端功能说明使用第三方平台goeasy实现服务端向前端推送数据基本原理WebSocket使用准备申请goeasy账号并创建应用官网http://www.goeasy.io安装并开启goeasy插件(注意清除缓存)在插件配置中填写应用的Appkeys等配置项使用说明使用插件集成的事件插件在前台(index模块)和后台(admin模块)各集成了两个默认的事件订阅,可以在js中通过监听top来处理,例:也…

    2025年5月27日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号