双机热备的Quartz集群
sqlserver搭建高可用双机热备的Quartz集群部署【附源码】
一般拿Timer和Quartz相比较的,简直就是对Quartz的侮辱,两者的功能根本就不在一个层级上,如本篇介绍的Quartz强大的集群机制,可以采用基于
sqlserver,mysql的集群方案,当然还可以在第三方插件的基础上实现quartz序列化到热炒的mongodb,redis,震撼力可想而知,接下来本篇就和大家聊
一聊怎么搭建基于sqlserver的quartz集群,实现这么一种双机热备的强大功能。
一:下载sqlserver版的建表脚本
首先大家可以通过github上搜索quartz的源代码,在源码项目的/database/tables目录下,可以找到firebird,oracle,mysql,sqlserver等建库脚本,
本篇只需拿取sqlserver版本即可。 https://github.com/quartznet/quartznet/tree/master/database/tables 如下图所示


从上面的截图中可以看到,我接下来要做的事情就是增加一个你需要创建的database名字,这里取为:【quartz】,完整的脚本如下:

二:配置quartz的集群参数
当我们写var scheduler = StdSchedulerFactory.GetDefaultScheduler()这段代码的时候,如果大家看过源码的话,会知道这个GetScheduler的
过程中有一个初始化方法【Instantiate】方法,此方法中你会发现在做DBProvider的时候会需要几个参数来初始化DB的,比如下面看到的几个标红属性。

1 IList<string> dsNames = cfg.GetPropertyGroups(PropertyDataSourcePrefix);
2 foreach (string dataSourceName in dsNames)
3 {
4 string datasourceKey = "{0}.{1}".FormatInvariant(PropertyDataSourcePrefix, dataSourceName);
5 NameValueCollection propertyGroup = cfg.GetPropertyGroup(datasourceKey, true);
6 PropertiesParser pp = new PropertiesParser(propertyGroup);
7
8 Type cpType = loadHelper.LoadType(pp.GetStringProperty(PropertyDbProviderType, null));
9
10 // custom connectionProvider...
11 if (cpType != null)
12 {
13 IDbProvider cp;
14 try
15 {
16 cp = ObjectUtils.InstantiateType<IDbProvider>(cpType);
17 }
18 catch (Exception e)
19 {
20 initException = new SchedulerException("ConnectionProvider of type '{0}' could not be instantiated.".FormatInvariant(cpType), e);
21 throw initException;
22 }
23
24 try
25 {
26 // remove the type name, so it isn't attempted to be set
27 pp.UnderlyingProperties.Remove(PropertyDbProviderType);
28
29 ObjectUtils.SetObjectProperties(cp, pp.UnderlyingProperties);
30 cp.Initialize();
31 }
32 catch (Exception e)
33 {
34 initException = new SchedulerException("ConnectionProvider type '{0}' props could not be configured.".FormatInvariant(cpType), e);
35 throw initException;
36 }
37
38 dbMgr = DBConnectionManager.Instance;
39 dbMgr.AddConnectionProvider(dataSourceName, cp);
40 }
41 else
42 {
43 string dsProvider = pp.GetStringProperty(PropertyDataSourceProvider, null);
44 string dsConnectionString = pp.GetStringProperty(PropertyDataSourceConnectionString, null);
45 string dsConnectionStringName = pp.GetStringProperty(PropertyDataSourceConnectionStringName, null);
46
47 if (dsConnectionString == null && !String.IsNullOrEmpty(dsConnectionStringName))
48 {
49
50 ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings[dsConnectionStringName];
51 if (connectionStringSettings == null)
52 {
53 initException = new SchedulerException("Named connection string '{0}' not found for DataSource: {1}".FormatInvariant(dsConnectionStringName, dataSourceName));
54 throw initException;
55 }
56 dsConnectionString = connectionStringSettings.ConnectionString;
57 }
58
59 if (dsProvider == null)
60 {
61 initException = new SchedulerException("Provider not specified for DataSource: {0}".FormatInvariant(dataSourceName));
62 throw initException;
63 }
64 if (dsConnectionString == null)
65 {
66 initException = new SchedulerException("Connection string not specified for DataSource: {0}".FormatInvariant(dataSourceName));
67 throw initException;
68 }
69 try
70 {
71 DbProvider dbp = new DbProvider(dsProvider, dsConnectionString);
72 dbp.Initialize();
73
74 dbMgr = DBConnectionManager.Instance;
75 dbMgr.AddConnectionProvider(dataSourceName, dbp);
76 }
77 catch (Exception exception)
78 {
79 initException = new SchedulerException("Could not Initialize DataSource: {0}".FormatInvariant(dataSourceName), exception);
80 throw initException;
81 }
82 }
83 }

接下来的问题就是这几个属性是如何配置进去的,仔细观察上面代码,你会发现所有的配置的源头都来自于cfg变量,ok,接下来你可以继续翻看代码,相信
你会看到有一个Initialize方法就是做cfg变量的初始化,如下代码所示:

1 public void Initialize()
2 {
3 // short-circuit if already initialized
4 if (cfg != null)
5 {
6 return;
7 }
8 if (initException != null)
9 {
10 throw initException;
11 }
12
13 NameValueCollection props = (NameValueCollection) ConfigurationManager.GetSection(ConfigurationSectionName);
14
15 string requestedFile = QuartzEnvironment.GetEnvironmentVariable(PropertiesFile);
16
17 string propFileName = requestedFile != null && requestedFile.Trim().Length > 0 ? requestedFile : "~/quartz.config";
18
19 // check for specials
20 try
21 {
22 propFileName = FileUtil.ResolveFile(propFileName);
23 }
24 catch (SecurityException)
25 {
26 log.WarnFormat("Unable to resolve file path '{0}' due to security exception, probably running under medium trust");
27 propFileName = "quartz.config";
28 }
29
30 if (props == null && File.Exists(propFileName))
31 {
32 // file system
33 try
34 {
35 PropertiesParser pp = PropertiesParser.ReadFromFileResource(propFileName);
36 props = pp.UnderlyingProperties;
37 Log.Info(string.Format("Quartz.NET properties loaded from configuration file '{0}'", propFileName));
38 }
39 catch (Exception ex)
40 {
41 Log.Error("Could not load properties for Quartz from file {0}: {1}".FormatInvariant(propFileName, ex.Message), ex);
42 }
43
44 }
45 if (props == null)
46 {
47 // read from assembly
48 try
49 {
50 PropertiesParser pp = PropertiesParser.ReadFromEmbeddedAssemblyResource("Quartz.quartz.config");
51 props = pp.UnderlyingProperties;
52 Log.Info("Default Quartz.NET properties loaded from embedded resource file");
53 }
54 catch (Exception ex)
55 {
56 Log.Error("Could not load default properties for Quartz from Quartz assembly: {0}".FormatInvariant(ex.Message), ex);
57 }
58 }
59 if (props == null)
60 {
61 throw new SchedulerConfigException(
62 @"Could not find <quartz> configuration section from your application config or load default configuration from assembly.
63 Please add configuration to your application config file to correctly initialize Quartz.");
64 }
65 Initialize(OverrideWithSysProps(props));
66 }

仔细阅读上面的一串代码,你会发现,默认quartz参数配置来源于三个地方。
1. app.config中的section节点。
2. bin目录下的~/quartz.config文件。
3. 默认配置的NameValueCollection字典集合,也就是上一篇博客给大家做的一个演示。
我个人不怎么喜欢通过quartz.config文件进行配置,这样也容易写死,所以我还是喜欢使用最简单的NameValueCollection配置,因为它的数据源可来源
于第三方存储结构中,配置代码如下:

1 //1.首先创建一个作业调度池
2 var properties = new NameValueCollection();
3 //存储类型
4 properties["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz";
5
6 //驱动类型
7 properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz"; //数据源名称
8 properties["quartz.jobStore.dataSource"] = "myDS";
9
10 //连接字符串
11 properties["quartz.dataSource.myDS.connectionString"] = @"server=.;Initial Catalog=quartz;Integrated Security=True";
12 //sqlserver版本
13 properties["quartz.dataSource.myDS.provider"] = "SqlServer-20";
14
15 //是否集群
16 properties["quartz.jobStore.clustered"] = "true";
17 properties["quartz.scheduler.instanceId"] = "AUTO";

上面的代码配置我都加过详细的注释,大家应该都能看得懂,而且这些配置就是这么定死的,没什么修改的空间,大家记住即可。
三:Job和Trigger定义
在集群中环境下,job和trigger的定义该怎么写的?大家也不要想的太复杂,注意一点就可以了,在Schedule一个Job时候,通过CheckExists判断一下
这个Job在Scheduler中是否已经存在了,如果存在,你就不能再次通过Schedule去重复调度一个Job就可以了。。。所以判断的代码也很简单,如下所示:

1 IScheduler scheduler = factory.GetScheduler();
2
3 scheduler.Start();
4
5 var jobKey = JobKey.Create("myjob", "group");
6
7 if (scheduler.CheckExists(jobKey))
8 {
9 Console.WriteLine("当前job已经存在,无需调度:{0}", jobKey.ToString());
10 }
11 else
12 {
13 IJobDetail job = JobBuilder.Create<HelloJob>()
14 .WithDescription("使用quartz进行持久化存储")
15 .StoreDurably()
16 .RequestRecovery()
17 .WithIdentity(jobKey)
18 .UsingJobData("count", 1)
19 .Build();
20
21 ITrigger trigger = TriggerBuilder.Create().WithSimpleSchedule(x => x.WithIntervalInSeconds(2).RepeatForever())
22 .Build();
23
24 scheduler.ScheduleJob(job, trigger);
25
26 Console.WriteLine("调度进行中!!!");
27 }

上面这段代码,大家就可以部署在多台机器中了,是不是很简单?
四:强大的cluster完整演示
所有的初始化工作都做完了,接下来我们copy一份bin文件,同时打开两个console程序,如下所示,可以看到job任务只会被一个console调度,另外
一个在空等待。

然后你肯定很好奇的跑到sqlserver中去看看,是否已经有job和trigger的db存储,很开心吧,数据都有的。。。
、
好了,一切都是那么完美,接下来可以展示一下quartz集群下的高可用啦,如果某一个console挂了,那么另一台console会把这个任务给接过来,实
现强大的高可用。。。所以我要做的事情就是把console1关掉,再看看console2是不是可以开始调度job了???

完美,这个就是本篇给大家介绍的Quartz的Cluster集群,一台挂,另一台顶住,双机热备,当然这些console你可以部署在多台机器中,要做的就是保持各
个server的时间同步,因为quarz是依赖于本机server的时间,好了,本篇就先说到这里吧。
小礼物走一波,双击666。。。 完整代码:SimpleSchedulerApp.zip
双机热备的Quartz集群的更多相关文章
- 使用sqlserver搭建高可用双机热备的Quartz集群部署【附源码】
一般拿Timer和Quartz相比较的,简直就是对Quartz的侮辱,两者的功能根本就不在一个层级上,如本篇介绍的Quartz强大的序列化机制,可以序列到 sqlserver,mysql,当然还可以在 ...
- 第十节: 利用SQLServer实现Quartz的持久化和双机热备的集群模式 :
背景: 默认情况下,Quartz.Net作业是持久化在内存中的,即 quartz.jobStore.type = "Quartz.Simpl.RAMJobStore, Quartz" ...
- keepalived+LVS 实现双机热备、负载均衡、失效转移 高性能 高可用 高伸缩性 服务器集群
本章笔者亲自动手,使用LVS技术实现实现一个可以支持庞大访问量.高可用性.高伸缩性的服务器集群 在读本章之前,可能有不少读者尚未使用该技术,或者部分读者使用Nginx实现应用层的负载均衡.这里大家都可 ...
- HA(High available)--Heartbeat高可用性集群(双机热备)菜鸟入门级
HA(High available)--Heartbeat高可用性集群(双机热备) 1.理解:两台服务器A和B ,当A提供服务,B闲置待命,当A服务宕机,会自动切换至B机器继续提供服务.当主机恢复 ...
- 基于RHCS的web双机热备集群搭建
基于RHCS的web双机热备集群搭建 RHCS集群执行原理及功能介绍 1. 分布式集群管理器(CMAN) Cluster Manager.简称CMAN.是一个分布式集群管理工具.它执行在集群的各个节 ...
- 使用Docker-compose搭建nginx-keepalived双机热备来实现高可用nginx集群
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_117 最近同学出去面试经常会被问到一个问题. 面试官:你说你们公司使用nginx反向代理tornado,部署了多少多少台机器,好像 ...
- mysql双机热备+heartbeat集群+自动故障转移
环境说明:本环境由两台mysql 数据库和heartbeat 组成,一台的ip 为 192.168.10.197,一台为192.168.10.198,对外提供服务的vip 为192.168.10.20 ...
- RAID与双机热备简单介绍与区别
一. RAID技术详解 RAID是英文Redundant Array of Independent Disks的缩写,翻译成中文意思是“独立磁盘冗余阵列”,有时也简称磁盘阵列(Disk Arra ...
- oracle双机热备概念
1. 双机热备概述 双机热备有两种实现模式,一种是基于共享的存储设备的方式,另一种是没有共享的存储设备的方式,一般称为纯软件方式. 基于存储共享的双机热备是双机热备的最标准方案. ...
随机推荐
- 基于EmguCV的摄像机标定及矫正
标签: EmguCV摄像头标定C# 2015-05-03 14:55 501人阅读 评论(6) 收藏 举报 分类: C# 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] ...
- iOS8: 企业开发的终结?
iOS 8 的公布(如今是 iOS8.1),并非对全部人来说都是值得高兴的事情. 对那些使用企业部署(不经过商店公布)的 app 开发人员来说,又被苹果坑到了. 由于 iOS 8 的一个Bug.导致企 ...
- 什么是MVC,什么是WCF
在C#中总会遇到这几个概念,网上搜了一下,做一下总结和比较,东拼西凑,如有雷同,纯属直接拷贝,人懒,但无意侵权. 1.什么是MVC MVC是三个单词的首字母缩写,它们是Model(模型).View(视 ...
- 【习题 3-8 UVA - 202】Repeating Decimals
[链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 余数出现循环节. 就代表出现了循环小数. [代码] #include <bits/stdc++.h> using nam ...
- docker基础(二)
1.base镜像的特点 (1)不依赖于其他的镜像 (2)其它的可以通过它位基础作为容器的底层 2.在下载镜像的时候,只有几百M,但是安装一个Ubuntu虚拟机的镜像就比较大,首先会想到图形化界面,其实 ...
- 13、虚拟驱动vivi.c注册过程分析及怎么写V4L2驱动及启动过程
UVC设备也是一个usb设备,在uvc_driver.c中的init函数会调用usb_register注册,根据id_table发送可支持的设备后调用probe函数,其会去uvc_register_c ...
- Perl遍历查找文件
Perl遍历查找文件 使用Perl查找当前目录下的所有PDF文件 ******************************************************************* ...
- Canvas基础知识总结之中的一个
canvas的HTML语法: <canvas> Canvas not supported </canvas> 上面这句代码中内容部分所含的文本,这种文本的叫法"后备内 ...
- swift学习第一天:认识swift以及swift的常量和变量
一:认识swift // 1.导入框架 //#import <UIKit/UIKit.h> import UIKit // 2.定义一个标识符 // int a = 10; // swif ...
- before/after伪类常见用法
1.清除浮动 ; } /*IE6/IE7*/ .clearfix:after{ display: block; content: "clear"; ; clear: both; o ...