Zookeeper实现分布式锁

文章目录

  • 什么是Zookeeper
  • Zookeeper集群机制
  • Zookeeper特性
  • Zookeeper数据结构
  • Zookeeper应用场景
  • Zookeeper的环境搭建(linux)
  • Zookeeper客户端
  • Zookeeper配置文件介绍
  • Java操作Zookeeper
  • Zookeeper的事件通知
  • 使用Zookeeper实现分布式锁的思路
  • Redis实现分布式锁代码实现

什么是Zookeeper

Zookeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集群维护(Group Maintenance)等,简化分布式应用协调及其管理的难度,提供高性能的分布式服务。ZooKeeper本身可以以单机模式安装运行,不过它的长处在于通过分布式ZooKeeper集群(一个Leader,多个Follower),基于一定的策略来保证ZooKeeper集群的稳定性和可用性,从而实现分布式应用的可靠性。
1、zookeeper是为别的分布式程序服务的
2、Zookeeper本身就是一个分布式程序(只要有半数以上节点存活,zk就能正常服务)
3、Zookeeper所提供的服务涵盖:主从协调、服务器节点动态上下线、统一配置管理、分布式共享锁、统> 一名称服务等
4、虽然说可以提供各种服务,但是zookeeper在底层其实只提供了两个功能:
管理(存储,读取)用户程序提交的数据(类似namenode中存放的metadata); 
并为用户程序提供数据节点监听服务;

Zookeeper集群机制

Zookeeper集群的角色: Leader 和 follower 
只要集群中有半数以上节点存活,集群就能提供服务

Zookeeper特性

1、Zookeeper:一个leader,多个follower组成的集群
2、全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的
3、分布式读写,更新请求转发,由leader实施
4、更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行
5、数据更新原子性,一次数据更新要么成功,要么失败
6、实时性,在一定时间范围内,client能读到最新数据

Zookeeper数据结构

1、层次化的目录结构,命名符合常规文件系统规范(类似文件系统),如下图所示
2、每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识 
3、节点Znode可以包含数据和子节点(但是EPHEMERAL类型的节点不能有子节点)
节点类型 
a、Znode有两种类型:
短暂(ephemeral)(create -e /app1/test1 “test1” 客户端断开连接zk删除ephemeral类型节点) 
持久(persistent) (create -s /app1/test2 “test2” 客户端断开连接zk不删除persistent类型节点)
b、Znode有四种形式的目录节点(默认是persistent )
PERSISTENT 
PERSISTENT_SEQUENTIAL(持久序列/test0000000019 ) 
EPHEMERAL 
EPHEMERAL_SEQUENTIAL

Zookeeper实现分布式锁_第1张图片

Zookeeper应用场景

统一命名服务
       分布式环境下,经常需要对应用/服务进行统一命名,便于识别不同服务。类似于域名与ip之间对应关系,域名容易记住。通过名称来获取资源或服务的地址,提供者等信息按照层次结构组织服务/应用名称可将服务名称以及地址信息写到Zookeeper上,客户端通过Zookeeper获取可用服务列表类。
配置管理
       分布式环境下,配置文件管理和同步是一个常见问题。一个集群中,所有节点的配置信息是一致的,比如Hadoop。对配置文件修改后,希望能够快速同步到各个节点上配置管理可交由Zookeeper实现。可将配置信息写入Zookeeper的一个znode上。各个节点监听这个znode。一旦znode中的数据被修改,zookeeper将通知各个节点。
集群管理
       分布式环境中,实时掌握每个节点的状态是必要的。可根据节点实时状态作出一些调整。Zookeeper可将节点信息写入Zookeeper的一个znode上。监听这个znode可获取它的实时状态变化。典型应用比如Hbase中Master状态监控与选举。
分布式通知/协调
       分布式环境中,经常存在一个服务需要知道它所管理的子服务的状态。例如,NameNode须知道各DataNode的状态,JobTracker须知道各TaskTracker的状态。心跳检测机制和信息推送也是可通过Zookeeper实现。
分布式锁
       Zookeeper是强一致的。多个客户端同时在Zookeeper上创建相同znode,只有一个创建成功。Zookeeper实现锁的独占性。多个客户端同时在Zookeeper上创建相同znode ,创建成功的那个客户端得到锁,其他客户端等待。Zookeeper 控制锁的时序。各个客户端在某个znode下创建临时znode (类型为CreateMode. EPHEMERAL _SEQUENTIAL),这样,该znode可掌握全局访问时序。
分布式队列
       两种队列。当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。(可通过分布式锁实现)
       同步队列。一个job由多个task组成,只有所有任务完成后,job才运行完成。可为job创建一个/job目录,然后在该目录下,为每个完成的task创建一个临时znode,一旦临时节点数目达到task总数,则job运行完成。

Zookeeper的环境搭建(linux)

加入说目前我们有三台机器。IP分别为
192.168.110.154
192.168.110.158
192.168.110.159
环境要求

必须要有jdk环境,关于linux的jdk的安装,麻烦大家动动可爱的小手,百度下
# 关闭防火墙 
service iptable stop

结构

一共三个节点
(zk服务器集群规模不小于3个节点),要求服务器之间系统时间保持一致。

上传zk并且解压

进行解压: tar -zxvf zookeeper-3.4.6.tar.gz
重命名: mv zookeeper-3.4.6 zookeeper

修改zookeeper环境变量

# 编辑文件
vi /etc/profile
export JAVA_HOME=/opt/jdk1.8.0_71
export ZOOKEEPER_HOME=/usr/local/zookeeper
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$JAVA_HOME/bin:$ZOOKEEPER_HOME/bin:$PATH
# 检测环境变成是否报错
source /etc/profile

修改zoo_sample.cfg文件

cd /usr/local/zookeeper/conf
vim zoo_sample.cfg
(1) dataDir=/usr/local/zookeeper/data(注意同时在zookeeper创建data目录)
(2)最后面添加
server.0=192.168.110.154:2888:3888 # 2888 底层连接的端口号
server.1=192.168.110.158:2888:3888
server.2=192.168.110.159:2888:3888

创建服务器标识

# 进入到文件目录
cd /usr/local/zookeeper/data
# 创建一个文件并且编辑
vim myid 
# 填写内容
0

# 其他服务也进行相同的操作,myid的编号分别为 Ip对象的server编号
server.0=192.168.110.154:2888:3888 # 2888 底层连接的端口号
server.1=192.168.110.158:2888:3888
server.2=192.168.110.159:2888:3888

启动Zookeeper

# 进入路径
cd /usr/local/zookeeper/bin
# 启动
zkServer.sh start
(注意这里3台机器都要进行启动)
# 检查状态
zkServer.sh status(在三个节点上检验zk的mode,一个leader和俩个follower),具体如下图,可以看见每个服务器的状态

在这里插入图片描述

Zookeeper客户端

ZooKeeper命令行工具类似于Linux的shell环境,不过功能肯定不及shell啦,但是使用它我们可以简单的对ZooKeeper进行访问,数据创建,数据修改等操作.  使用 zkCli.sh -server 127.0.0.1:2181 连接到 ZooKeeper 服务,连接成功后,系统会输出 ZooKeeper 的相关环境以及配置信息。
命令行工具的一些简单操作如下:
•	1. 显示根目录下、文件: ls / 使用 ls 命令来查看当前 ZooKeeper 中所包含的内容
•	2. 显示根目录下、文件: ls2 / 查看当前节点数据并能看到更新次数等数据
•	3. 创建文件,并设置初始内容: create /zk "test" 创建一个新的 znode节点“ zk ”以及与它关联的字符串
•	4. 获取文件内容: get /zk 确认 znode 是否包含我们所创建的字符串
•	5. 修改文件内容: set /zk "zkbak" 对 zk 所关联的字符串进行设置
•	6. 删除文件: delete /zk 将刚才创建的 znode 删除
•	7. 退出客户端: quit
•	8. 帮助命令: help

Zookeeper配置文件介绍


# 心跳时间,为了确保连接存在的,以毫秒为单位,最小超时时间为两个心跳时间
tickTime=2000 
 

# 多少个心跳时间内,允许其他server连接并初始化数据,如果ZooKeeper管理的数据较大,则应相应增大这个值
initLimit=10 
 
 
# 多少个tickTime内,允许follower同步,如果follower落后太多,则会被丢弃。
syncLimit=5 
 

# 用于存放内存数据库快照的文件夹,同时用于集群的myid文件也存在这个文件夹里(注意:一个配置文件只能包含一个dataDir字样,即使它被注释掉了。)
dataDir=/home/myuser/zooA/data 
 

# 服务的监听端口
clientPort=2181 
 

# server.A=B:C:D:
#A是一个数字,表示这个是第几号服务器,B是这个服务器的ip地址
#C第一个端口用来集群成员的信息交换,表示的是这个服务器与集群中的Leader服务器交换信息的端口
#D是在leader挂掉时专门用来进行选举leader所用

server.1=127.0.0.1:2888:3888 
server.2=127.0.0.1:2988:3988  
server.3=127.0.0.1:2088:3088 
 
# 用于单独设置transaction log的目录,transaction log分离可以避免和普通log还有快照的竞争
dataLogDir=/home/myuser/zooA/log

Java操作Zookeeper

依赖

<dependency>
			<groupId>org.apache.zookeepergroupId>
			<artifactId>zookeeperartifactId>
			<version>3.4.6version>
		dependency>

客户端

public class ZookeeperDemo {
	/**
	 * 集群连接地址
	 */
	private static final String CONNECT_ADDR = "192.168.110.138:2181,192.168.110.147:2181,192.168.110.148:2181";
	/**
	 * session超时时间
	 */
	private static final int SESSION_OUTTIME = 2000;
	/**
	 * 信号量,阻塞程序执行,用户等待zookeeper连接成功,发送成功信号,
	 */
	private static final CountDownLatch countDownLatch = new CountDownLatch(1);

	public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
		ZooKeeper zk = new ZooKeeper(CONNECT_ADDR, SESSION_OUTTIME, new Watcher() {

			public void process(WatchedEvent event) {
				// 获取时间的状态
				KeeperState keeperState = event.getState();
				EventType tventType = event.getType();
				// 如果是建立连接
				if (KeeperState.SyncConnected == keeperState) {
					if (EventType.None == tventType) {
						// 如果建立连接成功,则发送信号量,让后阻塞程序向下执行  类似notify
						countDownLatch.countDown();
						System.out.println("zk 建立连接");
					}
				}
			}

		});
		// 进行阻塞 类似join
		countDownLatch.await();
		//创建父节点
//		String result = zk.create("/testRott", "12245465".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//		System.out.println("result:" + result);
		//创建子节点
		String result = zk.create("/testRott/children", "children 12245465".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
		System.out.println("result:"+result);
		zk.close();
	}

}

Zookeeper的事件通知

Watcher

在ZooKeeper中,接口类Watcher用于表示一个标准的事件处理器,其定义了事件通知相关的逻辑,
包含KeeperState和EventType两个枚举类,分别代表了通知状态和事件类型,对节点的CRUD进行监听
同时定义了事件的回调方法:process(WatchedEvent event)。

代码

public class ZkClientWatcher implements Watcher {
	// 集群连接地址
	private static final String CONNECT_ADDRES = "192.168.110.159:2181,192.168.110.160:2181,192.168.110.162:2181";
	// 会话超时时间
	private static final int SESSIONTIME = 2000;
	// 信号量,让zk在连接之前等待,连接成功后才能往下走.
	private static final CountDownLatch countDownLatch = new CountDownLatch(1);
	private static String LOG_MAIN = "【main】 ";
	private ZooKeeper zk;

	public void createConnection(String connectAddres, int sessionTimeOut) {
		try {
			zk = new ZooKeeper(connectAddres, sessionTimeOut, this);
			System.out.println(LOG_MAIN + "zk 开始启动连接服务器....");
			countDownLatch.await();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public boolean createPath(String path, String data) {
		try {
			this.exists(path, true);
			this.zk.create(path, data.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
			System.out.println(LOG_MAIN + "节点创建成功, Path:" + path + ",data:" + data);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * 判断指定节点是否存在
	 * 
	 * @param path
	 *            节点路径
	 */
	public Stat exists(String path, boolean needWatch) {
		try {
			return this.zk.exists(path, needWatch);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public boolean updateNode(String path,String data) throws KeeperException, InterruptedException {
		exists(path, true);
		this.zk.setData(path, data.getBytes(), -1);
		return false;
	}

	public void process(WatchedEvent watchedEvent) {

		// 获取事件状态
		KeeperState keeperState = watchedEvent.getState();
		// 获取事件类型
		EventType eventType = watchedEvent.getType();
		// zk 路径
		String path = watchedEvent.getPath();
		System.out.println("进入到 process() keeperState:" + keeperState + ", eventType:" + eventType + ", path:" + path);
		// 判断是否建立连接
		if (KeeperState.SyncConnected == keeperState) {
			if (EventType.None == eventType) {
				// 如果建立建立成功,让后程序往下走
				System.out.println(LOG_MAIN + "zk 建立连接成功!");
				countDownLatch.countDown();
			} else if (EventType.NodeCreated == eventType) {
				System.out.println(LOG_MAIN + "事件通知,新增node节点" + path);
			} else if (EventType.NodeDataChanged == eventType) {
				System.out.println(LOG_MAIN + "事件通知,当前node节点" + path + "被修改....");
			}
			else if (EventType.NodeDeleted == eventType) {
				System.out.println(LOG_MAIN + "事件通知,当前node节点" + path + "被删除....");
			}

		}
		System.out.println("--------------------------------------------------------");
	}

	public static void main(String[] args) throws KeeperException, InterruptedException {
		ZkClientWatcher zkClientWatcher = new ZkClientWatcher();
		zkClientWatcher.createConnection(CONNECT_ADDRES, SESSIONTIME);
//		boolean createResult = zkClientWatcher.createPath("/p15", "pa-644064");
		zkClientWatcher.updateNode("/pa2","7894561");
	}

}

使用Zookeeper实现分布式锁的思路

分布式锁使用Zookeeper,会先zk上创建一个临时节点(有效期),使用临时节点资源,因为节点不允许重复,如果能创建节点成功,则生成订单号,如果失败,则创建是订单号失败,等待。在释放锁的时候,可以通过他给他的设置的有效期自动释放,或者自己关闭,然后其他节点就可以创建订单号啦。
核心思想:zk节点唯一,创建就能获取到锁,获取不到就等待

Redis实现分布式锁代码实现


#####生成订单号######
import java.text.SimpleDateFormat;
import java.util.Date;

//生成订单号
public class OrderNumGenerator {
	private static int count = 0;
    //生成订单号
	public String getOrderNumber() {
		SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
		return smt.format(new Date()) + "-" + ++count;
	}

}
#####订单业务逻辑######
public class OrderService implements Runnable {
	private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
	private static Object oj = new Object();
	private Lock lock = new ZookeeperDistrbuteLock();

	public void run() {
		getNumber();
	}

	public void getNumber() {
		// synchronized (oj) {
		lock.getLock();
		String orderNumber = orderNumGenerator.getOrderNumber();
		System.out.println("获取订单号:" + orderNumber);
		lock.unLock();
		// }

	}

	public static void main(String[] args) {
		for (int i = 0; i < 100; i++) {
			new Thread(new OrderService()).start();
		}
	}

}

#####lock接口 ######
public interface Lock {
	// 获取锁
	public void getLock();
    // 释放锁
	public void unLock();
}

#####ZookeeperAbstractLock抽象类接口 ######
public abstract class ZookeeperAbstractLock implements Lock {
	private static final String CONNECT_ADDRES = "192.168.110.159:2181,192.168.110.160:2181,192.168.110.162:2181";

	protected ZkClient zkClient = new ZkClient(CONNECT_ADDRES);
	protected String PATH = "/lock";

	public void getLock() {
		// 如果当前节点已经存在,则等待
		if (tryLock()) {
			System.out.println("获取到锁 get");
		} else {
			// 等待
			waitLock();
			// 重新获取锁
			getLock();
		}
	}

	protected abstract void waitLock();

	protected abstract boolean tryLock();

	public void unLock() {
		if (zkClient != null) {
			zkClient.close();
		}
		System.out.println("已经释放锁...");
	}
#####ZookeeperAbstractLock抽象类接口 ######
//实现锁
public class ZookeeperDistrbuteLock extends ZookeeperAbstractLock {
	private CountDownLatch countDownLatch = new CountDownLatch(1);

	@Override
	protected boolean tryLock() {
		try {
			zkClient.createEphemeral(PATH);
			// 创建成功
			return true;
		} catch (Exception e) {
			// 创建失败
			return false;
		}

	}

	@Override
	protected void waitLock() {
		try {
			IZkDataListener iZkDataListener = new IZkDataListener() {

				public void handleDataDeleted(String path) throws Exception {
					// 唤醒等待线程, 继续往下走.
					if (countDownLatch != null) {
						countDownLatch.countDown();
					}
				}

				public void handleDataChange(String path, Object data) throws Exception {

				}
			};
			// 注册到zk监听中
			zkClient.subscribeDataChanges(PATH, iZkDataListener);
			if (zkClient.exists(PATH)) {
				countDownLatch = new CountDownLatch(1);

				// 等待
				countDownLatch.await();

			}
			// 删除事件通知
			zkClient.unsubscribeDataChanges(PATH, iZkDataListener);
		} catch (Exception e) {
			// TODO: handle exception
		}
	}

}

你可能感兴趣的:(#,分布式区,分布式,java-zookeeper,zookeeper)