zookeeper初体验之关于解决quartz重复执行任务的一种思路
前阵子工作中遇到了一个很麻烦的问题。
本人所在的项目组做了一个机遇quartz集群的任务系统。通俗点讲就是用quartz框架(quartz是一款能跑定时任务的框架支持复杂的时间表达式)
来执行定时任务。但是这里定时任务的并发数很多,就出现了一个问题,同一个trigger被多个机器重复的触发了,这就造成了执行的任务数目
比预期的多很多。领导就让我处理这个问题。
开始我以为是这个框架本身的配置有问题,结果翻了很多资料还是没解决(这里不过多讲这个,有兴趣可以留言)。那么问题出在哪里呢?
quartz的任务工作的方式是这样的。当任务达到触发条件的时候(当这条任务满足qrtz_cron_triggers表中定义的相关的时间表达式的时候)
qrtz_triggers表对应的这条记录的状态发生改变,同时下次触发时间根据时间表达式做出改变,同时根据sched_name找到qrtz_job_details
表中的具体job去执行,下面就是具体的业务了。我这里的问题就出在同一时间内(前后相差几ms)多台机器触发了同一条trigger。然而这个
我是没有办法解决的或者说不想动quartz的源码(有朋友能从这一步就把问题搞定的可以交流一下),所以我就顺着quartz的工作流程继续往下
到了job这里,由于多个trigger被触发所以执行了多次job,那么我是否能通过让他只执行一次job来防止重复执行呢。如果有一种方法可以
让这个job执行一次就可以达到我的要求了(选择在job这一步处理其实还是因为不想动源码,到java这里我就好办了)。正好我对zookeeper
有一些了解,zookeeper恰好有一种注册机制可以解决这个问题。
回顾一下zookeeper关于节点注册的用法:
zookeeper只可以注册一个同名节点如果节点已经存在则返回nodeexits.
那么运用到我这个场景就是当任务进入job之后用job id(同时触发的这几个job的id是一样的)去向zookeeper完成注册,由于
id是一样的那么只能有一个注册成功,只要在注册成功的条件下我才允许task。这样就保证了不做重复的运算。
具体如下:
public class PlatformQuartzJobBean extends QuartzJobBean {
private String path = "/zk_triggerID";
private String lock = "/zk_lock";
private static ZooKeeper zk = null;
static{
try {
zk = new ZooKeeper(PropsUtil.get("zooKeeperUrl")+":"+PropsUtil.get("zooKeeperPort"), 50000,new ZKWatcher());
} catch (IOException e) {
logger.error(e.getMessage(),e);
}
}
//任务执行的具体逻辑
protected void executeInternal(JobExecutionContext jobexecutioncontext)
throws JobExecutionException {
Trigger trigger = jobexecutioncontext.getTrigger();
String triggerName = trigger.getKey().getName();//triggername是唯一的
boolean createSuccess = false;
boolean doTask = false; //不对zookeeper注册执行任务
int childrens = 0;
List<String> children = null;
boolean onDelete = false;//是否获取删除节点的权限
try {
zk.create(path+"/"+triggerName, triggerName.getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);//如果注册出现问题说明节点存在是重复的任务
createSuccess = true;
children = zk.getChildren(path, false);
if(children != null){
childrens = children.size();
}
if(childrens>99){//节点个数达到100个就执行删除操作
try {
zk.create(lock+"/dodelete", "dodelete".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
onDelete = true;
} catch (KeeperException e1) {
if(e1 instanceof NodeExistsException){
logger.info("already on delete!");
}else{
logger.error(e1.getMessage(),e1);
}
} catch (InterruptedException e1) {
logger.error(e1.getMessage(),e1);
} }
//执行具体的任务
execuTask(trigger,triggerName,jobexecutioncontext,af);
} catch (KeeperException e) {
if(e instanceof NodeExistsException){
logger.info("already on do");
}else if(e instanceof ConnectionLossException){
logger.info("ConnectionLoss ,do task without registered!!");
doTask = true;
}else if(e instanceof SessionExpiredException){
logger.info("session expired ,do task without registered!!");
doTask = true;
try {
zk = new ZooKeeper(PropsUtil.get("zooKeeperUrl")+":"+PropsUtil.get("zooKeeperPort"), 50000,new ZKWatcher());
} catch (IOException e1) {
logger.error(e1.getMessage(),e1);
}
}else{
logger.error(e.getMessage(),e);
}
} catch (InterruptedException e) {
logger.error(e.getMessage(),e);
}
if(createSuccess && onDelete){//如果创建成功并且root下有执行删除的权利
try {
for(String str:children){
zk.delete(path+"/"+str, -1);
}
} catch (KeeperException e1) {
logger.error(e1.getMessage(),e1);
} catch (InterruptedException e1) {
logger.error(e1.getMessage(),e1);
}finally{
if(onDelete){
try {
zk.delete(lock+"/dodelete", -1);
} catch (InterruptedException e) {
logger.error(e.getMessage(),e);
} catch (KeeperException e) {
if(e instanceof ConnectionLossException){
logger.info("ConnectionLoss ,reconnect zk!!");
try {
zk.close();//人为失效,删除dodelete节点
zk = new ZooKeeper(PropsUtil.get("zooKeeperUrl")+":"+PropsUtil.get("zooKeeperPort"), 50000,new ZKWatcher());
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
} }else{
logger.error(e.getMessage(),e);
} }
}
} }
//如果出现connectloss和sessionexpired 可能是网络有点问题找不到zookeeper就不管重复问题了完成任务为最重要的
if(doTask){//如果出现connectloss和sessionexpired 就直接执行任务
execuTask(trigger,triggerName,jobexecutioncontext,af);
} } }
整个过程就是:当job开始的时候去向zookeeper申请注册,只有当注册成功的时候才执行业务,失败则退出job。同时由于我这里是每天循环的
定时任务所以当zookeeper下的节点数目达到一定的个数的时候加一个删除锁(就是向zookeeper create一个ondetele节点),同时删除之前
的triggername节点,这样保证了明天这些任务可以继续完成。至此,任务重复执行的问题就解决了。下一篇博客将简单的介绍一下zookeeper和
zookeeper的布置,虽然网上这方面东西很多,不过自己写出来(自己实践过可以用的),以后可以直接拿来用。。
zookeeper初体验之关于解决quartz重复执行任务的一种思路的更多相关文章
- Zookeeper 初体验之——伪分布式安装(转)
原文地址: http://blog.csdn.net/salonzhou/article/details/47401069 简介 Apache Zookeeper 是由 Apache Hadoop 的 ...
- ZooKeeper 初体验
安装Zookeeper Mac OS Mac 用户可以使用 Homebrew 安装和管理 Zookeeper 服务: brew install zookeeper 配置文件地址在: /usr/loca ...
- kvm初体验之五:vm连接网络的两种方式:bridge和nat
1. 在安装vm时指定网络连接方式 1)bridge virt-install --name vm1 --ram=1024 --vcpus=1 --disk path=/vm-images/vm1,s ...
- 【Spark深入学习 -15】Spark Streaming前奏-Kafka初体验
----本节内容------- 1.Kafka基础概念 1.1 出世背景 1.2 基本原理 1.2.1.前置知识 1.2.2.架构和原理 1.2.3.基本概念 1.2.4.kafka特点 2.Kafk ...
- Node.js 网页瘸腿爬虫初体验
延续上一篇,想把自己博客的文档标题利用Node.js的request全提取出来,于是有了下面的初哥爬虫,水平有限,这只爬虫目前还有点瘸腿,请看官你指正了. // 内置http模块,提供了http服务器 ...
- Flume日志采集系统——初体验(Logstash对比版)
这两天看了一下Flume的开发文档,并且体验了下Flume的使用. 本文就从如下的几个方面讲述下我的使用心得: 初体验--与Logstash的对比 安装部署 启动教程 参数与实例分析 Flume初体验 ...
- RPC框架基础概念理解以及使用初体验
RPC:Remote Procedure Call(远程服务调用) RPC是做什么的 通过RPC框架机器A某个进程可以通过网络调用机器B上的进程方法,就像在本地上调用一样. RPC可以基于HTTP或者 ...
- Handlebars的基本用法 Handlebars.js使用介绍 http://handlebarsjs.com/ Handlebars.js 模板引擎 javascript/jquery模板引擎——Handlebars初体验 handlebars.js 入门(1) 作为一名前端的你,必须掌握的模板引擎:Handlebars 前端数据模板handlebars与jquery整
Handlebars的基本用法 使用Handlebars,你可以轻松创建语义化模板,Mustache模板和Handlebars是兼容的,所以你可以将Mustache导入Handlebars以使用 Ha ...
- go语言的初体验
分享最近学习 Go 语言的心得和体会,适合有编程基础的人,因为这里只做经验性的总结概述,不做基础教学的入门知识讲解,如果想要学习编程语言的基础知识,请出门左转进入官方文档,查看基础教学文档. Go 概 ...
随机推荐
- phpcms二次开发步骤
文件目录结构 根目录 | – api 接口文件目录 | – caches 缓存文件目录 | – configs 系统配置文件目录 | – caches_* 系统缓存目录 | – phpcms phpc ...
- CLR via C#字符串和文本处理
一.字符 在.NET Framewole中,字符总是表示成16位Unicode代码值,这简化了国际化应用程序的开发. 每个字符都表示成System.Char结构(一个值类型) 的一个实例.Sy ...
- 文成小盆友python-num14 - web 前端基础 html ,css, JavaScript
本部分主要内容 html - 基础 css - 基础 一.html 标签 html 文档标签树如下: head 部分 Meta(metadata information) 提供有关页面的元信息,例:页 ...
- JAVA - 多线程的同步
多线程的同步 1. 锁对象. 应用场景:当某个数据可能被其他线程修改时,给涉及到数据的方法上锁,保证同一时刻只有拥有这个锁的线程能访问该数据,其他要调用这个方法的线程被阻塞.注意:必须是不同线程访问同 ...
- 转 Android - 文件操作
一.资源文件的读取: 1) 从resource的raw中读取文件数据: String res = ""; try{ //得到资源中的Raw数据流 InputStream in = ...
- webserver and application server
http://www.diffen.com/difference/Application_Server_vs_Web_Server http://www.differencebetween.com/d ...
- C51 库函数
C-51软件包的库包含标准的应用程序,每个函数都在相应的头文件(.h)中有原型声明.如果使用库函数,必须在源程序中用预编译指令定义与该函数相关的头文件(包含了该函数的原型声明).例如:#include ...
- Windows开发中一些常用的辅助工具
经常有人问如何快速的定位和解决问题,很多时候答案就是借助工具, 记录个人Windows开发中个人常用的一些辅助工具. (1) Spy++ 相信windows开发中应该没人不知道这个工具, 我们常用 ...
- cf500A New Year Transportation
A. New Year Transportation time limit per test 2 seconds memory limit per test 256 megabytes input s ...
- IMS 相关名词解释
IMS: IMS(IP Multimedia Subsystem)是IP多媒体系统,是一种全新的多媒体业务形式,它能够满足现在的终端客户更新颖.更多样化多媒体业务的需求. RCS:Rich Commu ...