**
本章介绍用zookeeper实现分布式无序锁
**
1. 实现流程
2. 流程描述
- 第一步:客户端(分布式系统的节点)收到请求后。
- 第二步:首先会进行tryLock(尝试创建zk节点) 获取锁。
- 第三步:如果获取成功,则进行业务操作。业务操作完成后,则会删除这一zk节点,程序结束。
- 第四步:如果创建失败则会抛出异常,说明没有获得到锁。则会启动IZkDataListener进行zk监听。
- 第五步:收到监听节点被删除后,再从第二步开始执行。
3. 此种实现方式优缺点
优点:
- 实现简单
- 任何客户端挂掉之后都不会影响其它客户端执行
缺点: - 所有请求都是无序的
- 可能网络条件好的客户端会优先得到锁
4.zookeeper下载安装
- 下载地址:http://mirrors.hust.edu.cn/apache/zookeeper/
- 解压,进入conf目录下,复制zoo_sample.cfg并且改名为zoo.cfg
- 进入bin目录,点击zkServer.cmd进行启动,linux则运行脚本zkServer.sh,这样zookeeper服务端就启动好了。
- 进入bin目录,点击zkCli.cmd进行启动客户端,linux则运行脚本zkCli.sh,这样zookeeper客户端就启动好了。可以直接输入zk命令。
5.代码实现
pom:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.4-beta</version>
<type>pom</type>
</dependency>
</dependencies>
要引入的配置文件配置有:
zklock:
cliPath: 127.0.0.1:2181
rootpath: /locks
spring ioc容器初始化对象:
package com.weixiaoyi.distribute.lock1.util;
import lombok.extern.slf4j.Slf4j;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* @author :yuanLong Wei
* @date :Created in 2019/5/7 18:13
* @description:管理自创建bean对象
* @modified By:
* @version: 1.0
*/
@Component
@Configuration
@Slf4j
public class Beans {
@Value(value = "${zklock.cliPath}")
private String zklockPath;
/**
* 实例化zookeeper客户端连接对象
*
* @return
*/
@Bean
public ZkClient configZkLock() {
ZkClient zkClient = new ZkClient(zklockPath, 5 * 1000);
return zkClient;
}
}
自定义锁:
package com.weixiaoyi.distribute.lock1.util;
import lombok.extern.slf4j.Slf4j;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* @author :yuanLong Wei
* @date :Created in 2019/5/7 15:22
* @description:zookeeper锁对象
* @modified By:
* @version: 1.0
*/
@Component
@Slf4j
public class ZookeeperLock implements Lock {
/** zookeeper创建节点的根节点 */
@Value("${zklock.rootpath}")
private String LOCK_ROOT_PATH;
/** 即将创建节点的名称 */
private static final String createNodeName = "/zklock";
/** 线程阻塞器 */
private CountDownLatch countDownLatch = null;
/** zk客户端连接对象 */
@Resource
private ZkClient zkClient;
/**
* 获取锁
*
*/
@Override
public void lock() {
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
/**
* 获取锁 返回是否获取成功
* zookeeper 节点类型:
* PERSISTENT : 永久节点
* EPHEMERAL : 临时节点
* PERSISTENT_SEQUENTIAL : 永久有序节点
* EPHEMERAL_SEQUENTIAL : 临时有序节点
*
* 如果只是防止业务并发访问 可以使用临时节点 每次删除节点后让其他等待线程一起去创建节点 创建节点成功的 即为拿到所
* 如果类似秒杀活动的 要按照访问顺序进行顺序处理业务 这时 就要用临时有序节点 根据创建节点的序号去监听前一个节点的状态 前一个节点被删除 则可以拿到所
* 临时节点是为了防止某个客户端挂掉或者zookeeper挂掉 出现死锁情况
*
* 这里模拟无序请求访问服务的情况
*
* @return
*/
@Override
public boolean tryLock() {
try {
// 创建节点 创建成功得到锁则返回true 否则返回false
zkClient.createEphemeral(LOCK_ROOT_PATH + createNodeName);
log.info("创建临时节点 拿到锁");
return true;
}catch (Exception e) {
log.info("获取锁失败");
return false;
}
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
/**
* 释放锁
*
*/
@Override
public void unlock() {
zkClient.delete(LOCK_ROOT_PATH + createNodeName);
log.info("释放锁------删除节点,节点名称:{}",LOCK_ROOT_PATH + createNodeName);
}
@Override
public Condition newCondition() {
return null;
}
public boolean waitLock(IZkDataListener iZkDataListener) throws InterruptedException {
log.info("开始监听");
// zk启动监听
zkClient.subscribeDataChanges(LOCK_ROOT_PATH + createNodeName,iZkDataListener);
countDownLatch = new CountDownLatch(1);
countDownLatch.await();
log.info("监听到节点被删除");
if(!tryLock()) {
log.info("没有获取到锁");
waitLock(iZkDataListener);
}
return true;
}
/**
* 初始化zk监听器
* zk有节点变化立即会得到通知
* 监听节点删除事件 一旦目标节点被删除 则立即创建节点
*
* @return
*/
public IZkDataListener configZkListener() {
IZkDataListener iZkDataListener = new IZkDataListener() {
@Override
public void handleDataChange(String path, Object data) throws Exception {
log.info("path == {},data == {}", path, data);
}
@Override
public void handleDataDeleted(String path) throws Exception {
log.info("节点 {} 被删除", path);
if((LOCK_ROOT_PATH + createNodeName).equals(path) && countDownLatch != null)
countDownLatch.countDown();
else
log.info("没有进行计数减法");
}
};
return iZkDataListener;
}
}
service层实现类:
package com.weixiaoyi.distribute.lock1.service.impl;
import com.weixiaoyi.distribute.lock1.service.DistributeService;
import com.weixiaoyi.distribute.lock1.util.ZookeeperLock;
import lombok.extern.slf4j.Slf4j;
import org.I0Itec.zkclient.IZkDataListener;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author :yuanLong Wei
* @date :Created in 2019/5/7 15:19
* @description:zklick服务层实现类
* @modified By:
* @version: 1.0
*/
@Service
@Slf4j
public class DistributeServiceImpl implements DistributeService {
/** 自定义锁对象 */
@Resource
private ZookeeperLock zookeeperLock;
/**
* @see DistributeService#distributeServer()
*
* @return
*/
@Override
public String distributeServer() throws InterruptedException {
if(zookeeperLock.tryLock()) {
servering();
}else {
log.info("调用lock1服务");
IZkDataListener iZkDataListener = zookeeperLock.configZkListener();
boolean lockResult = zookeeperLock.waitLock(iZkDataListener);
if(lockResult) {
// 得到了锁 进行业务处理
servering();
}
}
return "lock1";
}
/**
* 模拟业务处理 这里进行延时等待操作
*
* @throws InterruptedException
*/
private void servering() throws InterruptedException {
log.info("业务处理中 》》》》》》》》》》》》》》");
Thread.sleep(5 * 1000);
zookeeperLock.unlock();
}
}
controller这里不进行介绍,直接调用service接口方法。
后面会介绍请求有序的分布式锁。
代码git地址:https://github.com/distribute-program/zookeeper-lock.git
项目名称:distribute-zklock1