承接上个博文, 这次是方案2的实现, 本方案的特点:
1. 该方案能很好地从几台服务器中选出一个Master机器, 不仅仅可以用于定时任务场景, 还可以用在其他场景下.
2. 该方案能实现Master节点的自动 failover, 经我测试 failover 过程稍长, 接近1分钟.
综上所述, 本方案是推荐方案.

==============================
Curator 中 LeaderSelector 相关
==============================
会用到四个相关类, 分别是:
LeaderSelector - 选举Leader的底层类.
LeaderSelectorListener 接口 - 在本接口中, 引入了takeLeadership()虚拟函数, 可以用来监听获得领导权.
LeaderSelectorListenerAdapter - 官方提供一个LeaderSelectorListener实现类, 我们可以在这个标准的实现上增加业务逻辑, 一般只需要实现takeLeadership()方法即可.
CancelLeadershipException - 取消Leader权异常.

这几个接口和类的关系是:
ConnectionStateListener 接口(引入了 stateChanged 虚函数)
        |
        |
        v
LeaderSelectorListener 接口(引入了 takeLeadership 虚函数)
        |
        |
        v
LeaderSelectorListenerAdapter 类(实现了 stateChanged() 方法), 实现了一个标准的stateChanged()方法, 即当zk连接异常,
        |                      直接抛出 CancelLeadershipException, 该异常将触发leaderSelector.interruptLeadership, 即中断领导权.
        |
        |
        v
LeaderSelectorController 自定义类( 主要用来实现 takeLeadership 方法)

重要的方法说明:

leaderSelector.autoRequeue()
调用该方法, 可以确保相关zk client在释放领导权后能参与选举.

leaderSelector.start()
让zk 客户端立即参与选举

leaderSelector.close()
放弃竞选参与.

leaderSelector.hasLeadership()
检查是否是Leader, 返回值为boolean, 该函数调用会立即返回.

leaderSelectorListener.takeLeadership()
获取领导权被触发的函数, 可以在其中编写业务逻辑, 需要注意的是, 该函数一旦结束, 将自动释放领导权. 所以如果要hold住领导权的话, 该函数中要加一个死循环.

client.getConnectionStateListenable().addListener()
为zk 客户端增加一个监听器, 用来监听连接状态, 共有三种状态: RECONNECT/LOST/SUSPEND
当连接状态为 ConnectionState.LOST 时, 写代码强制客户端重连, 以便该客户端能继续参与Leader选举.
当连接状态为 ConnectionState.SUSPEND 时, 我们一般不用处理, 输出log即可.

=============================
环境准备
=============================
在 VM (192.168.1.11) 上启动一个 zookeeper 容器
docker run -d --name myzookeeper --net host zookeeper:latest

在Windows开发机上, 使用 zkCli.cmd 应该能连上虚机中的 zk server.
zkCli.cmd -server 192.168.1.11:2181

=============================
SpringBoot 服务程序
=============================
增加下面三个依赖项, 引入 actuator 仅仅是为了不写任何代码就能展现一个web UI.

<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<!--org.apache.curator 依赖 slf4j -->
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Java 代码:

package com.example.demo;

import java.io.Closeable;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; /*
* 主程序类
* */
@EnableScheduling
@SpringBootApplication
public class ZkServiceApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(ZkServiceApplication.class, args); //开始节点选举过程
ZkLeaderSelectorController leaderController = new ZkLeaderSelectorController();
leaderController.start();
Thread.sleep(Long.MAX_VALUE);
leaderController.close();
}
} /*
* 常量工具类
*/
class ZkConst {
public static final String SERVICE_NAME = "ServiceA";
public static final String LATCH_NAME = "node";
public static final String LEADER_NAME = "master";
public static final String SERVICE_SERVER = "Server1:8080";
public static final String ZK_URL = "localhost:2181"; public static String getZkLatchPath() {
return String.format("/%s/%s", SERVICE_NAME, LATCH_NAME);
} public static String getZkLeaderPath() {
return String.format("/%s/%s", SERVICE_NAME, LEADER_NAME);
} } /*
* Zk Connection 监听器 如果 zk client 长连接断开后, 需要重连以保证该客户端仍能参与 Leader 选举.
* 对于定时任务级的Leader选举, 这个监听器并不重要. 对于服务器级别的Leader选举, 这个监听器很重要.
*/
class ZkConnectionStateListener implements ConnectionStateListener {
private static final Logger log = LoggerFactory.getLogger(ZkConnectionStateListener.class); @Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
log.debug("Zk connection change to " + newState);
if (ConnectionState.CONNECTED != newState) {
while (true) {
try {
log.error("Disconnected to the Zk server. Try to reconnect Zk server");
client.getZookeeperClient().blockUntilConnectedOrTimedOut();
log.info("Succeed to reconnect Zk server");
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
}
}
} } /*
* Zk 工具类
*/
class ZkHelper {
private static final Logger log = LoggerFactory.getLogger(ZkHelper.class); public static CuratorFramework getClient(boolean autoStart) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient(ZkConst.ZK_URL, retryPolicy);
if (client.getState() != CuratorFrameworkState.STARTED && autoStart) {
client.start();
}
return client;
} public static boolean isMasterNode(CuratorFramework client) {
boolean result = false;
String nodeValue = "(nodeValue)";
String path = ZkConst.getZkLeaderPath();
Stat stat;
try {
stat = client.checkExists().forPath(path);
if (stat != null) {
nodeValue = new String(client.getData().forPath(path));
}
result = nodeValue.equals(ZkConst.SERVICE_SERVER);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return result;
} public static boolean isMasterNode() {
CuratorFramework client = getClient(false);
// client.getConnectionStateListenable().addListener(new
// ZkConnectionStateListener());
client.start();
boolean result = isMasterNode(client);
client.close();
return result;
} public static void setMasterName(CuratorFramework client, boolean unknownMasterName) {
String serverName;
if (unknownMasterName) {
serverName = "Master In electing";
} else {
serverName = ZkConst.SERVICE_SERVER;
}
String path = ZkConst.getZkLeaderPath();
Stat stat;
try {
stat = client.checkExists().forPath(path);
if (stat != null) {
client.setData().forPath(path, serverName.getBytes());
} else {
client.create().forPath(path, serverName.getBytes());
} } catch (Exception e) {
log.error(e.getMessage(), e);
}
} public static void setMasterName(boolean unknownMasterName) {
CuratorFramework client = getClient(false);
client.getConnectionStateListenable().addListener(new ZkConnectionStateListener());
client.start();
setMasterName(client, unknownMasterName);
client.close();
} } /*
* Master选举的控制类
*/
class ZkLeaderSelectorController extends LeaderSelectorListenerAdapter implements Closeable {
private static final Logger log = LoggerFactory.getLogger(LeaderSelectorListenerAdapter.class); private LeaderSelector leaderSelector;
private CuratorFramework client;
String path; public ZkLeaderSelectorController() {
client = ZkHelper.getClient(false);
path = ZkConst.getZkLatchPath();
leaderSelector = new LeaderSelector(client, path, this);
leaderSelector.autoRequeue(); // 自动重新排队
} /*
* 获得Leader之后的处理过程
*/
@Override
public void takeLeadership(CuratorFramework client) throws Exception {
log.info(String.format("The server (%s) become as master", ZkConst.SERVICE_SERVER));
boolean unknownMasterName = false;
ZkHelper.setMasterName(client, unknownMasterName); // 永远 hold 住领导权
Thread.sleep(Long.MAX_VALUE);
log.info(String.format("The server (%s) become as non-master", ZkConst.SERVICE_SERVER));
} @Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
if ((newState == ConnectionState.SUSPENDED) || (newState == ConnectionState.LOST)) {
log.error("Disconnected to the Zk server. Try to release leadership ");
} super.stateChanged(client, newState); if ((newState == ConnectionState.SUSPENDED) || (newState == ConnectionState.LOST)) {
boolean unknownMasterName = true;
ZkHelper.setMasterName(client, unknownMasterName);
}
} /*
* 开始竞争leader
*/
public void start() {
client.start();
leaderSelector.start();
} @Override
public void close() throws IOException {
leaderSelector.close();
client.close();
}
} /*
* 定时任务控制器类
*/
class ZkTaskController {
private static final Logger log = LoggerFactory.getLogger(ZkTaskController.class);
private String taskName; public ZkTaskController(String taskName) {
this.taskName = taskName;
} public void runTask(Runnable action) {
if (ZkHelper.isMasterNode()) {
log.info(String.format("The task %s will run on this master server", taskName));
action.run();
} else {
log.info(String.format("The task %s will not run on this non-master server", taskName));
}
}
} /*
* 定时任务类
*/
@Component
class MyTasks { /**
* 一个定时任务 reportCurrentTimeTask 方法 (每分钟运行)
*/
@Scheduled(cron = "0 * * * * *")
public void reportCurrentTimeTask() {
ZkTaskController zkTaskController = new ZkTaskController("reportCurrentTime");
zkTaskController.runTask(new ReportCurrentTimeTaskInternal());
} /**
* 定时任务 reportCurrentTimeTask 真正执行的内容
*/
class ReportCurrentTimeTaskInternal implements Runnable {
private final Logger log = LoggerFactory.getLogger(ReportCurrentTimeTaskInternal.class);
private final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); @Override
public void run() {
log.info(String.format("The server (%s) is now %s", ZkConst.SERVICE_SERVER, dateFormat.format(new Date())));
}
} }

使用ZooKeeper协调多台Web Server的定时任务处理(方案2)的更多相关文章

  1. 使用ZooKeeper协调多台Web Server的定时任务处理(方案1)

    背景说明: 有一套Web服务程序, 为了保证HA, 需要在多台服务器上部署, 该服务程序有一些定时任务要执行, 现在要保证的是, 同一定时任务不会在多台机器上被同时执行. 方案1 --- 任务级的主备 ...

  2. [SDK2.2]Windows Azure Virtual Network (4) 创建Web Server 001并添加至Virtual Network

    <Windows Azure Platform 系列文章目录> 在上一章内容中,笔者已经介绍了以下两个内容: 1.创建Virtual Network,并且设置了IP range 2.创建A ...

  3. Nginx负载均衡:分布式/热备Web Server的搭建

    Nginx是一款轻量级的Web server/反向代理server及电子邮件(IMAP/POP3)代理server.并在一个BSD-like 协议下发行.由俄罗斯的程序设计师Igor Sysoev所开 ...

  4. Web Server 分布式服务: Nginx负载均衡

    Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器.由俄罗斯的程序设计师Igor Sysoev所开发,供俄国大型的入口网站及搜索引擎Rambler使用.其 ...

  5. Azkaban2.5安装部署(系统时区设置 + 安装和配置mysql + Azkaban Web Server 安装 + Azkaban Executor Server安装 + Azkaban web server插件安装 + Azkaban Executor Server 插件安装)(博主推荐)(五)

    Azkaban是什么?(一) Azkaban的功能特点(二) Azkaban的架构(三) Hadoop工作流引擎之Azkaban与Oozie对比(四) 不多说,直接上干货! http://www.cn ...

  6. 【转】推荐介绍几款小巧的Web Server程序

    原博地址:http://blog.csdn.net/heiyeshuwu/article/details/1753900 偶然看到几个小巧有趣的Web Server程序,觉得有必要拿来分享一下,让大家 ...

  7. Jexus-5.6.3使用详解、Jexus Web Server配置

    一.Jexus Web Server配置   在 jexus 的工作文件夹中(一般是“/usr/jexus”)有一个基本的配置文件,文件名是“jws.conf”. jws.conf 中至少有 Site ...

  8. 多台web如何共享session进行存储(转载)

    session的存储了解以前是怎么做的,搞清楚了来龙去脉,才会明白进行共享背后的思想和出发点.我喜欢按照这样的方式来问(或者去搞清楚):为什么要session要进行共享,不共享会什么问题呢? php中 ...

  9. 如何写一个简单的Web Server(一)

      在本篇博文中我将介绍如何写一个Web Server.博文中大部分资料我是参考的这篇文章(http://www.linuxhowtos.org/C_C++/socket.htm),英文不错的同学可以 ...

随机推荐

  1. git、github、gitlab之间的关系

    GIt-版本控制工具:GitHub-一个网站平台,提供给用户空间存储git仓储,保存用户的一些数据文档或者代码等:GitLab - 基于Git的项目管理软件. Git分布式版本控制系统 Git是一款自 ...

  2. PHP7--PHP的一次重大变革

    PHP7--PHP的一次重大变革 一.写在开头 PHP7是PHP编程语言全新的一个版本,主要在性能方面获得了极大的提升.官方的文档显示,PHP7可以达到PHP5.x版本两倍的性能.同时还对PHP的语法 ...

  3. Linux-基础学习(五)-mariadb主从复制以及redis学习

    开始今日份整理 1.mariadb的主从复制 主从复制大致图示: 1.1 mysql基本命令复习 linux下的操作 .启动mysql systemctl start mariadb .linux客户 ...

  4. WiFi广告强推的基本技术原理和一些相关问题

    WiFi推原理(转) 本文地址:http://jb.tongxinmao.com/Article/Detail/id/412 WiFi广告强推的基本技术原理和一些相关问题 WiFi广告推送原理就是利用 ...

  5. .NET Core 开源工具 IPTools - 快速查询 IP 地理位置、经纬度信息

    快速查询IP信息,支持国内和国外IP信息查询,支持查询经纬度,地理位置最高支持到城市. 1. IPTools.China 快速查询中国IP地址信息,包含国家.省份.城市.和网络运营商.非中国IP只支持 ...

  6. C# GDI+绘制一维条码打印模糊的解决办法

    最近遇到使用zxing生成的一维条码打印出来的条码图形很模糊根本识别不了.其实原因只有一句话: bitmap没有直接使用PrintDocument的Graphics画布进行绘制,而是中间处理了一下外部 ...

  7. 通过FactoryBean配置Bean

    这是配置Bean的第三种方式,FactoryBean是Spring为我们提供的,我们先来看看源码: 第一个方法:public abstract T getObject() throws Excepti ...

  8. Nginx整合tomcat,实现反向代理和负载均衡

    1.Nginx与Tomcat整合,通过Nginx反向代理Tomcat. Nginx安装路径为:/usr/local//nginx 首先切换路径到:/usr/local//nginx/conf通过命令  ...

  9. git 学习(3) ----- 代码共享和多人协作

    当我们开发项目的时候,项目会越来越大,就有可能需要其它同事进行参与,甚至进行开源,这时就需要找一个地方把代码存放起来,好供其它人下载并开发.这个地方,最好放到服务器上,因为只要能上网,就可以获取到, ...

  10. eclipse安装Activiti

    一. eclipse自己下载 打开eclipse软件,然后点击菜单栏的help选项,选择install New Software,示例如下: 出现如下对话框: 点击添加[Add]按钮,出现如下对话框 ...