client 注册自己的id与关注的任务标识到zookeeper。server读取client的注册信息到本地,发布任务时,根据任务标识选取对应的client,且在任务节点产生子节点,子节点的名称即为客户端id+任务标识,子节点的内容为任务参数。client监控任务节点,如果发现自己需要执行的节点,则获取对应的信息,且删除节点。
Zkclient类
/**
* @author wenjs
*/
public class ZKClient {
/**
* zookeeper地址
*/
final String CONNECT_ADDR = "localhost:2181";
/**
* session超时时间
*/
final int SESSION_OUTTIME = 10000;//ms
/**
* zk 客户端
*/
final ZkClient zkClinet;
/**
* 客户端id
*/
final String clientId;
/**
* 客户端注册路径
*/
final String clientPath;
/**
* 服务端发布任务路径
*/
final String taskPath;
/**
* 心跳频率
*/
final int delay = 10;
/**
* 心跳
*/
final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
/**
* 接受的任务标识
*/
String[] tasks;
/**
* 任务处理类
*/
TaskHandler handler;
/**
*
* @param clientId 客户端id,唯一
* @param clientPath 客户端注册路径
* @param taskPath 检测任务路径
* @param handler 任务处理器
*/
public ZKClient(String clientId, String clientPath, String taskPath, TaskHandler handler) {
this.clientId = clientId;
this.clientPath = clientPath;
this.taskPath = taskPath;
this.handler = handler;
this.zkClinet = new ZkClient(CONNECT_ADDR, SESSION_OUTTIME);
this.zkClinet.setZkSerializer(new MyZkSerializer());
createPath(clientPath);
createPath(taskPath);
heart();
}
public void createPath(String path) {
String paths[] = path.split("/");
String currentPath = "";
for (int i = 0; i < paths.length; i++) {
if (!"".equals(paths[i])) {
currentPath += "/" + paths[i];
createPathPersisten(currentPath);
}
}
}
public void createPathPersisten(String path) {
if (!zkClinet.exists(path)) {
zkClinet.createPersistent(path);
}
}
public void createTask(String... task) {
tasks = task;
doCreateTask();
}
public void doCreateTask() {
List<String> taskIds = new ArrayList<>();
String currentClinetPath = clientPath + "/" + clientId;
if (!zkClinet.exists(currentClinetPath)) {
zkClinet.createEphemeral(currentClinetPath);
} else {
String jsonStr = zkClinet.readData(currentClinetPath);
taskIds.addAll(JSONObject.parseArray(jsonStr, String.class));
}
if (tasks != null) {
taskIds.addAll(Arrays.asList(tasks));
zkClinet.writeData(currentClinetPath, JSONObject.toJSONString(taskIds));
}
}
public void work() {
//对父节点添加监听子节点变化。
zkClinet.subscribeChildChanges(taskPath, new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
currentChilds.forEach(p -> {
String[] split = p.split("::");
if (split[0].equals(clientId)) {
handler.process("clientId:" + clientId + ":执行任务:" + split[1] + ";任务参数:" + zkClinet.readData(taskPath + "/" + p)
+ ";节点编号:" + split[2]);
zkClinet.delete(taskPath + "/" + p);
}
});
}
});
zkClinet.subscribeDataChanges(taskPath, new IZkDataListener() {
@Override
public void handleDataDeleted(String path) throws Exception {
System.out.println("删除的节点为:" + path);
}
@Override
public void handleDataChange(String path, Object data) throws Exception {
System.out.println("变更的节点为:" + path + ",变更内容为:" + data);
}
});
}
public void close() {
zkClinet.close();
}
public void heart() {
scheduledExecutorService.schedule(new HeartThread(), delay, TimeUnit.SECONDS);
}
public static class MyZkSerializer implements ZkSerializer {
/**
* 序列化,将对象转化为字节数组
*/
@Override
public byte[] serialize(Object obj) throws ZkMarshallingError {
return String.valueOf(obj).getBytes(Charsets.UTF_8);
}
/**
* 反序列化,将字节数组转化为UTF_8字符串
*/
@Override
public Object deserialize(byte[] bytes) throws ZkMarshallingError {
return new String(bytes, Charsets.UTF_8);
}
}
public class HeartThread implements Runnable {
@Override
public void run() {
System.out.println(ZKClient.this.clientId + "心跳++++++++++++++++++++++++++++++++++++++++++++++++++");
ZKClient.this.doCreateTask();
ZKClient.this.scheduledExecutorService.schedule(new HeartThread(), delay, TimeUnit.SECONDS);
}
}
public static void main(String[] args) throws IOException {
ZKClient clinet1 = new ZKClient("client1", WorkPath.clientPath, WorkPath.taskPath, new DefaultTaskHandler());
clinet1.createTask("task1", "task2");
clinet1.work();
ZKClient clinet2 = new ZKClient("client2", WorkPath.clientPath, WorkPath.taskPath, new DefaultTaskHandler());
clinet2.createTask("task2", "task3");
clinet2.work();
System.out.println("客户端启动成功");
System.in.read();
clinet1.close();
clinet2.close();
}
}
ZKService类
/**
* zk的服务端,实现单点任务发布功能。
* 1、检测 "/isky/client" 路径注册了多少个客户端(客户端id),
* 2、定时在 "/isky/service/task" 路劲下生成任务节点
*
* @author wenjs
*/
public class ZKService {
/**
* zookeeper地址
*/
final String CONNECT_ADDR = "localhost:2181";
/**
* session超时时间
*/
final int SESSION_OUTTIME = 10000;//ms
/**
* zk 客户端
*/
final ZkClient zkClinet;
/**
* 客户端id
*/
final String serviceId;
/**
* 客户端信息路径
*/
final String clientPath;
/**
* 任务发布路径
*/
final String taskPath;
/**
* 任务生产者
*/
ProductWorker productWorker;
/**
* 任务发布者
*/
ConsumerWorker consumerWorker;
/**
* 任务队列
*/
public LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
public ZKService(String serviceId, String clientPath, String taskPath) {
this.serviceId = serviceId;
this.clientPath = clientPath;
this.taskPath = taskPath;
this.zkClinet = new ZkClient(CONNECT_ADDR, SESSION_OUTTIME);
this.zkClinet.setZkSerializer(new ZKClient.MyZkSerializer());
productWorker = new ProductWorker(this);
consumerWorker = new ConsumerWorker(this);
createPath(clientPath);
createPath(taskPath);
}
public void createPath(String path) {
String paths[] = path.split("/");
String currentPath = "";
for (int i = 0; i < paths.length; i++) {
if (! "".equals(paths[i]) ) {
currentPath += "/" + paths[i];
createPathPersisten(currentPath);
}
}
}
public void createPathPersisten(String path) {
if (!zkClinet.exists(path)) {
zkClinet.createPersistent(path);
}
}
/**
* 指定某个客户端生成任务节点,让它消费
*
* @param clientId
* @param task
*/
public void createTask(String clientId, String task) {
String flag = clientId + "::" + task + "::";
String param = "this is param";
String currentClinetPath = taskPath + "/" + flag;
zkClinet.createEphemeralSequential(currentClinetPath, param);
}
public void work() {
//对父节点添加监听子节点变化。
zkClinet.subscribeChildChanges(taskPath, new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
System.out.println("parentPath:" + parentPath);
System.out.println("当前任务被领取:" + currentChilds);
}
});
start();
}
public void start() {
productWorker.start();
consumerWorker.start();
}
public void close() {
zkClinet.close();
}
/**
* 根据指定的任务,获取所有关注此任务的客户端
* @param task
* @return
*/
public List<String> getClinet(String task) {
List<String> candidateClients = new ArrayList<>();
List<String> clients = zkClinet.getChildren(clientPath);
if (!CollectionUtils.isEmpty(clients)) {
clients.forEach(client -> {
String data = zkClinet.readData(clientPath + "/" + client);
if (!StringUtils.isEmpty(data) && data.contains(task)) {
candidateClients.add(client);
}
});
}
return candidateClients;
}
public static class ProductWorker extends Thread {
final ZKService service;
public ProductWorker(ZKService service) {
this.service = service;
}
@Override
public void run() {
try {
for (; ; ) {
List<String> tasks = new ArrayList<>();
tasks.add("task1");
tasks.add("task2");
tasks.add("task3");
service.queue.add(tasks.get((new Random()).nextInt(tasks.size())));
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class ConsumerWorker extends Thread {
final ZKService service;
public ConsumerWorker(ZKService service) {
this.service = service;
}
@Override
public void run() {
try {
for (; ; ) {
String task = service.queue.take();
System.out.println("产生任务:" + task);
List<String> clients = service.getClinet(task);
if (!CollectionUtils.isEmpty(clients)) {
// 在所有关注此任务的客户端中随机选取一个执行此任务
service.createTask(clients.get((new Random()).nextInt(clients.size())), task);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class MyZkSerializer implements ZkSerializer {
/**
* 序列化,将对象转化为字节数组
*/
@Override
public byte[] serialize(Object obj) throws ZkMarshallingError {
return String.valueOf(obj).getBytes(Charsets.UTF_8);
}
/**
* 反序列化,将字节数组转化为UTF_8字符串
*/
@Override
public Object deserialize(byte[] bytes) throws ZkMarshallingError {
return new String(bytes, Charsets.UTF_8);
}
}
public static void main(String[] args) throws IOException {
ZKService server = new ZKService(UUID.randomUUID().toString(), WorkPath.clientPath, WorkPath.taskPath);
server.work();
System.out.println("服务端启动成功");
System.in.read();
server.close();
System.out.println("服务端关闭");
}
}
WorkPath类
/**
* @author wenjs
*/
public interface WorkPath {
String bathPath = "/isky";
String clientPath = bathPath +"/client";
String serverPath = bathPath +"/service";
String taskPath = serverPath +"/task";
}
TaskHandler类
/**
* @author wenjs
*/
public interface TaskHandler {
/**
* 任务处理
* @param param
*/
void process(String param);
}
DefaultTaskHandler类
/**
* @author wenjs
*/
public class DefaultTaskHandler implements TaskHandler{
@Override
public void process(String param) {
System.out.println(param);
}
}
依赖引用
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.38</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.13</version> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.10</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>25.1-jre</version> </dependency>