最近一直在学习,偶然间翻看了一下博客,发现很多人问我关于基于zookeeper和quartz实现分布式定时调度文章的一些问题。由于本人最近几年都没怎么关注博客,因此也没有做出回复,心里感觉愧对大家。所以抽时间把之前写的东西重新翻了下版。顺便把代码也分享给大家。Quartz相关的知识和类之间的关系,可以参考我之前的一篇文章。
本文对zookeeper的分布式协作相关的代码做了改进。本文中使用临时顺序节点来控制多个定时任务同时执行时,保证只有一个定时任务执行成功,其他的定时任务都不执行。实现的原理就是每个定时任务的客户端都去创建节点,谁是节点的第一位,谁执行,就是多个客户端竞争资源的思想。话不多说,想了解具体实现的同事,自己去下载代码即可,代码很简单,大家可以自己研究一下。
下载地址:https://gitee.com/alex211/ets-schedule
使用zookeeper的临时顺序节点创建实现竞争资源,代码如下:
package com.ets.schedule.zklock;
import com.ets.schedule.quartz.contants.ETSConstant;
import org.I0Itec.zkclient.ZkClient;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.InitializingBean;
import java.util.Collections;
import java.util.List;
/**
* @author: alex
* @Date: 2019/4/8
* @Description: 利用zookeeper的临时顺序节点来实现协调多个quartz客户端在同一时刻保证只有一个实例去执行任务。
* 与分布式锁的思想类似但不是一个完整的分布式锁实现。
* 场景描述:
* 1、多个客户端,都去创建临时节点,谁是在节点的第一位,谁获得锁,可以执行定时任务。
* 2、一旦有客户端宕机,立即断开连接,删除该客户端创建的临时节点,让其他的客户端去执行任务。
*/
public class ZookeeperLock implements InitializingBean {
protected static Logger logger = Logger.getLogger(ZookeeperLock.class);
/**
* zookeeper服务地址
*/
private String hosts;
/**
* 会话超时时间(越短越好,因为服务器一断开,就删除临时节点,可以让其他存活的机器去执行任务)
*/
private int sessionTimeout = 1000;
/**
* 连接超时时间
*/
private int connectionTimeout = 1000*60;
/**
* zk客户端
*/
private ZkClient client;
/**
* 当前节点
*/
private ThreadLocal<String> currentPath;
/**
* spring 初始化后执行
* @throws Exception
*/
public void afterPropertiesSet() throws Exception {
this.currentPath = new ThreadLocal<>();
this.client = new ZkClient(hosts,sessionTimeout,connectionTimeout); //获得客户端
this.client.setZkSerializer(new MyZkSerializer()); //设置序列化类
//判断根节点是否存在,不存在则创建
if (!this.client.exists(ETSConstant.LOCKPATH)) {
try {
this.client.createPersistent(ETSConstant.LOCKPATH);
} catch (Exception e) {
logger.error("ZkClient create root node failed...");
logger.error(e);
}
}
}
/**
* 尝试获取锁
* @return true 拿到,false没拿到
*/
public boolean tryLock() {
//当前节点为空,说明还没有线程来创建节点
if(this.currentPath.get() == null) {
this.currentPath.set(this.client.createEphemeralSequential(ETSConstant.LOCKPATH + ETSConstant.SEPARATOR,"data"));
}
// 获取所有节点
List<String> children = this.client.getChildren(ETSConstant.LOCKPATH);
// 排序
Collections.sort(children);
//判断当前节点是否是最小节点
if(this.currentPath.get().equals(ETSConstant.LOCKPATH + ETSConstant.SEPARATOR + children.get(0))) {
return true;
}
return false;
}
/**
* 释放锁
*/
public void unlock() {
this.client.delete(this.currentPath.get());
this.currentPath.remove();
}
public String getHosts() {
return hosts;
}
public void setHosts(String hosts) {
this.hosts = hosts;
}
public long getSessionTimeout() {
return sessionTimeout;
}
public void setSessionTimeout(int sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
public int getConnectionTimeout() {
return connectionTimeout;
}
public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
}
zookeeper的序列化类
package com.ets.schedule.zklock;
import org.I0Itec.zkclient.exception.ZkMarshallingError;
import org.I0Itec.zkclient.serialize.ZkSerializer;
import org.apache.log4j.Logger;
import java.io.UnsupportedEncodingException;
/**
* @author: alex
* @Date: 2019/4/8
* @Description: 序列化
*/
public class MyZkSerializer implements ZkSerializer {
protected static Logger logger = Logger.getLogger(MyZkSerializer.class);
/**
* 反序列化
* @param bytes 字节数组
* @return 实体
* @throws ZkMarshallingError
*/
public Object deserialize(byte[] bytes) throws ZkMarshallingError {
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
logger.error("MyZkSerializer deserialize happened unsupportedEncodingException...");
logger.error(e);
throw new ZkMarshallingError(e);
}
}
/**
* 序列化
* @param obj 实体
* @return 字节数组
* @throws ZkMarshallingError
*/
public byte[] serialize(Object obj) throws ZkMarshallingError {
try {
return String.valueOf(obj).getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
logger.error("MyZkSerializer serialize happened unsupportedEncodingException...");
logger.error(e);
throw new ZkMarshallingError(e);
}
}
}