使用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的,比如下面看到的几个标红属性。

IList<string> dsNames = cfg.GetPropertyGroups(PropertyDataSourcePrefix);

            foreach (string dataSourceName in dsNames)

            {

                string datasourceKey = "{0}.{1}".FormatInvariant(PropertyDataSourcePrefix, dataSourceName);

                NameValueCollection propertyGroup = cfg.GetPropertyGroup(datasourceKey, true);

                PropertiesParser pp = new PropertiesParser(propertyGroup);


                Type cpType = loadHelper.LoadType(pp.GetStringProperty(PropertyDbProviderType, null));


                // custom connectionProvider...

                if (cpType != null)

                {

                    IDbProvider cp;

                    try

                    {

                        cp = ObjectUtils.InstantiateType<IDbProvider>(cpType);

                    }

                    catch (Exception e)

                    {

                        initException = new SchedulerException("ConnectionProvider of type '{0}' could not be instantiated.".FormatInvariant(cpType), e);

                        throw initException;

                    }


                    try

                    {

                        // remove the type name, so it isn't attempted to be set

                        pp.UnderlyingProperties.Remove(PropertyDbProviderType);


                        ObjectUtils.SetObjectProperties(cp, pp.UnderlyingProperties);

                        cp.Initialize();

                    }

                    catch (Exception e)

                    {

                        initException = new SchedulerException("ConnectionProvider type '{0}' props could not be configured.".FormatInvariant(cpType), e);

                        throw initException;

                    }


                    dbMgr = DBConnectionManager.Instance;

                    dbMgr.AddConnectionProvider(dataSourceName, cp);

                }

                else

                {

                    string dsProvider = pp.GetStringProperty(PropertyDataSourceProvider, null);

                    string dsConnectionString = pp.GetStringProperty(PropertyDataSourceConnectionString, null);

                    string dsConnectionStringName = pp.GetStringProperty(PropertyDataSourceConnectionStringName, null);


                    if (dsConnectionString == null && !String.IsNullOrEmpty(dsConnectionStringName))

                    {


                        ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings[dsConnectionStringName];

                        if (connectionStringSettings == null)

                        {

                            initException = new SchedulerException("Named connection string '{0}' not found for DataSource: {1}".FormatInvariant(dsConnectionStringName, dataSourceName));

                            throw initException;

                        }

                        dsConnectionString = connectionStringSettings.ConnectionString;

                    }


                    if (dsProvider == null)

                    {

                        initException = new SchedulerException("Provider not specified for DataSource: {0}".FormatInvariant(dataSourceName));

                        throw initException;

                    }

                    if (dsConnectionString == null)

                    {

                        initException = new SchedulerException("Connection string not specified for DataSource: {0}".FormatInvariant(dataSourceName));

                        throw initException;

                    }

                    try

                    {

                        DbProvider dbp = new DbProvider(dsProvider, dsConnectionString);

                        dbp.Initialize();


                        dbMgr = DBConnectionManager.Instance;

                        dbMgr.AddConnectionProvider(dataSourceName, dbp);

                    }

                    catch (Exception exception)

                    {

                        initException = new SchedulerException("Could not Initialize DataSource: {0}".FormatInvariant(dataSourceName), exception);

                        throw initException;

                    }

                }

            }

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

你会看到有一个Initialize方法就是做cfg变量的初始化,如下代码所示:

public void Initialize()

        {

            // short-circuit if already initialized

            if (cfg != null)

            {

                return;

            }

            if (initException != null)

            {

                throw initException;

            }


            NameValueCollection props = (NameValueCollection) ConfigurationManager.GetSection(ConfigurationSectionName);


            string requestedFile = QuartzEnvironment.GetEnvironmentVariable(PropertiesFile);


            string propFileName = requestedFile != null && requestedFile.Trim().Length > 0 ? requestedFile : "~/quartz.config";


            // check for specials

            try

            {

                propFileName = FileUtil.ResolveFile(propFileName);

            }

            catch (SecurityException)

            {

                log.WarnFormat("Unable to resolve file path '{0}' due to security exception, probably running under medium trust");

                propFileName = "quartz.config";

            }


            if (props == null && File.Exists(propFileName))

            {

                // file system

                try

                {

                    PropertiesParser pp = PropertiesParser.ReadFromFileResource(propFileName);

                    props = pp.UnderlyingProperties;

                    Log.Info(string.Format("Quartz.NET properties loaded from configuration file '{0}'", propFileName));

                }

                catch (Exception ex)

                {

                    Log.Error("Could not load properties for Quartz from file {0}: {1}".FormatInvariant(propFileName, ex.Message), ex);

                }


            }

            if (props == null)

            {

                // read from assembly

                try

                {

                    PropertiesParser pp = PropertiesParser.ReadFromEmbeddedAssemblyResource("Quartz.quartz.config");

                    props = pp.UnderlyingProperties;

                    Log.Info("Default Quartz.NET properties loaded from embedded resource file");

                }

                catch (Exception ex)

                {

                    Log.Error("Could not load default properties for Quartz from Quartz assembly: {0}".FormatInvariant(ex.Message), ex);

                }

            }

            if (props == null)

            {

                throw new SchedulerConfigException(

                    @"Could not find <quartz> configuration section from your application config or load default configuration from assembly.

Please add configuration to your application config file to correctly initialize Quartz.");

            }

            Initialize(OverrideWithSysProps(props));

        }

     仔细阅读上面的一串代码,你会发现,默认quartz参数配置来源于三个地方。

1. app.config中的section节点。

2. bin目录下的~/quartz.config文件。

3. 默认配置的NameValueCollection字典集合,也就是上一篇博客给大家做的一个演示。   

     我个人不怎么喜欢通过quartz.config文件进行配置,这样也容易写死,所以我还是喜欢使用最简单的NameValueCollection配置,因为它的数据源可来源于第三方存储结构中,配置代码如下:

//1.首先创建一个作业调度池

                var properties = new NameValueCollection();

                //存储类型

                properties["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz";


                //驱动类型

                properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz";                //数据源名称

                properties["quartz.jobStore.dataSource"] = "myDS";


                //连接字符串

                properties["quartz.dataSource.myDS.connectionString"] = @"server=.;Initial Catalog=quartz;Integrated Security=True";

                //sqlserver版本

                properties["quartz.dataSource.myDS.provider"] = "SqlServer-20";


                //是否集群

                properties["quartz.jobStore.clustered"] = "true";

                properties["quartz.scheduler.instanceId"] = "AUTO";

 上面的代码配置我都加过详细的注释,大家应该都能看得懂,而且这些配置就是这么定死的,没什么修改的空间,大家记住即可。 

三:Job和Trigger定义

     在集群中环境下,job和trigger的定义该怎么写的?大家也不要想的太复杂,注意一点就可以了,在Schedule一个Job时候,通过CheckExists判断一下这个Job在Scheduler中是否已经存在了,如果存在,你就不能再次通过Schedule去重复调度一个Job就可以了。。。所以判断的代码也很简单,如下所示:

                IScheduler scheduler = factory.GetScheduler();

                scheduler.Start();

                var jobKey = JobKey.Create("myjob", "group");

                if (scheduler.CheckExists(jobKey))

                {

                    Console.WriteLine("当前job已经存在,无需调度:{0}", jobKey.ToString());

                }

                else

                {

                    IJobDetail job = JobBuilder.Create<HelloJob>()

                           .WithDescription("使用quartz进行持久化存储")

                           .StoreDurably()

                           .RequestRecovery()

                           .WithIdentity(jobKey)

                           .UsingJobData("count", 1)

                           .Build();


                    ITrigger trigger = TriggerBuilder.Create().WithSimpleSchedule(x => x.WithIntervalInSeconds(2).RepeatForever())

                                                              .Build();


                    scheduler.ScheduleJob(job, trigger);

                    Console.WriteLine("调度进行中!!!");

                }

上面这段代码,大家就可以部署在多台机器中了,是不是很简单?

 

四:强大的cluster完整演示

   

     所有的初始化工作都做完了,接下来我们copy一份bin文件,同时打开两个console程序,如下所示,可以看到job任务只会被一个console调度,另外

一个在空等待。

 

       然后你肯定很好奇的跑到sqlserver中去看看,是否已经有job和trigger的db存储,很开心吧,数据都有的。。。

 

       好了,一切都是那么完美,接下来可以展示一下quartz集群下的高可用啦,如果某一个console挂了,那么另一台console会把这个任务给接过来,实

现强大的高可用。。。所以我要做的事情就是把console1关掉,再看看console2是不是可以开始调度job了??? 

完美,这个就是本篇给大家介绍的Quartz的Cluster集群,一台挂,另一台顶住,双机热备,当然这些console你可以部署在多台机器中,要做的就是保持各个server的时间同步,因为quarz是依赖于本机server的时间,好了,本篇就先说到这里吧。

原文地址:http://www.cnblogs.com/huangxincheng/p/6916246.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/325392.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一个正则表达式酿成的惨案

转载自 一个正则表达式酿成的惨案 导读&#xff1a;正则表达式是程序员经常使用的工具之一。本文作者通过一个正则表达式的陷阱&#xff0c;先深入剖析了出现问题的原因&#xff0c;后给出怎么处理这类问题的方法。最后还给出了一些检测常见正则表达式问题的工具&#xff0c…

详解C# Tuple VS ValueTuple(元组类 VS 值元组)

C# 7.0已经出来一段时间了&#xff0c;大家都知道新特性里面有个对元组的优化&#xff1a;ValueTuple。这里利用详尽的例子详解Tuple VS ValueTuple&#xff08;元组类VS值元组&#xff09;&#xff0c;10分钟让你更了解ValueTuple的好处和用法。 如果您对Tuple足够了解&#…

Eclipse把默认为Gbk的编码变为UTF-8

菜单栏Windows–>Preferences,左侧导航栏展开General–>Workspace,修改左下角的Text file encoding&#xff0c;选中Other改为UTF-8即可

从LINQ开始之LINQ to Objects(上)

LINQ概述 LINQ&#xff0c;语言集成查询(Language Integrated Query)&#xff0c;它允许使用C#或VB代码以查询数据库相同的方式来操作不同的数据源。 1.LINQ体系结构 从上图可以看出&#xff0c;LINQ总共包括五个部分&#xff1a;LINQ to Objects、LINQ to DataSets、LINQ to …

单点登录终极方案之 CAS 应用及原理

转载自 单点登录终极方案之 CAS 应用及原理 Cookie的单点登录的实现方式很简单&#xff0c;但是也问题颇多。例如&#xff1a;用户名密码不停传送&#xff0c;增加了被盗号的可能。另外&#xff0c;不能跨域&#xff01; 1、基于Cookie的单点登录的回顾 基于Cookie的单点登录…

微软亚太区资料科学总监:R 语言是 VS 生态第一顺位

微软亚太区资料科学总监Graham Williams 微软在2015年并购R语言工具商Revolution Analytics之后&#xff0c;随即在2016年&#xff0c;也开始在自家主力开发工具Visual Studio上&#xff0c;支持R语言。微软将如何定位R语言在微软开发工具链的位置&#xff1f;微软亚太区资料科…

java中如何数组是如何赋值的?

由于数组是引用类型&#xff0c;故无法与变量赋值的方式一样&#xff0c;int a 10;int b a; 那么数组是如何赋值的呢&#xff1f; 是这样赋值的&#xff1a; public static void arrayFuZhi(){//八斤的身高和体重int [] ba {170,80};//九斤的身高和体重与八斤的一样int [] …

从LINQ开始之LINQ to Objects(下)

前言 上一篇《从LINQ开始之LINQ to Objects&#xff08;上&#xff09;》主要介绍了LINQ的体系结构、基本语法以及LINQ to Objects中标准查询操作符的使用方法。 本篇则主要讨论LINQ to Objects中的扩展方法以及延迟加载等方面的内容。 扩展方法 1.扩展方法简介 扩展方法能够向…

Localdatetime

根据指定日期/时间创建对象 LocalDate localDate LocalDate.of(2018, 1, 13); LocalTime localTime LocalTime.of(9, 43, 20); LocalDateTime localDateTime LocalDateTime.of(2018, 1, 13, 9, 43, 20); System.out.println(localDate); System.out.println(localTime); Sy…

基于OAuth2的认证(译)

OAuth 2.0 规范定义了一个授权&#xff08;delegation&#xff09;协议&#xff0c;对于使用Web的应用程序和API在网络上传递授权决策非常有用。OAuth被用在各钟各样的应用程序中&#xff0c;包括提供用户认证的机制。这导致许多的开发者和API提供者得出一个OAuth本身是一个认证…

Redis非阻塞I/O多路复用机制

小曲在S城开了一家快递店&#xff0c;负责同城快送服务。小曲因为资金限制&#xff0c;雇佣了一批快递员&#xff0c;然后小曲发现资金不够了&#xff0c;只够买一辆车送快递。 经营方式一 客户每送来一份快递&#xff0c;小曲就让一个快递员盯着&#xff0c;然后快递员开车去…

React前端格式化时间

import moment from "moment";const dateFormat YYYY-MM-DD HH:mm:ss;<DatePicker label"时间" name"insertTime" showTime onChange{onChange} onOk{onOk}defaultValue{moment(location?.defaultValues?.record?.insertTime, dateFormat…

[认证授权] 4.OIDC(OpenId Connect)身份认证授权(核心部分)

1 什么是OIDC&#xff1f; 看一下官方的介绍&#xff08;http://openid.net/connect/&#xff09;&#xff1a; OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol. It allows Clients to verify the identity of the End-User based on the a…

EasyExcel中输出为时间格式

前端传值 后端Excel配置为String类型 配置为其他类型显示格式转化异常

Identity Service - 解析微软微服务架构eShopOnContainers(二)

接上一篇&#xff0c;众所周知一个网站的用户登录是非常重要&#xff0c;一站式的登录&#xff08;SSO&#xff09;也成了大家讨论的热点。微软在这个Demo中&#xff0c;把登录单独拉了出来&#xff0c;形成了一个Service&#xff0c;用户的注册、登录、找回密码等都在其中进行…

TCP/IP协议——ARP详解

转载自 TCP/IP协议——ARP详解 本文主要讲述了ARP的作用、ARP分组格式、ARP高速缓存、免费ARP和代理ARP。 1.学习ARP前要了解的内容 建立TCP连接与ARP的关系 应用接受用户提交的数据&#xff0c;触发TCP建立连接&#xff0c;TCP的第一个SYN报文通过connect函数到达IP层&a…

RPC远程过程调用之 RMI实现

1&#xff09;RMI&#xff08;remote method invocation&#xff09;是java原生支持的远程调用&#xff0c;RMI采用JRMP&#xff08;java RemoteMessageing Protocol&#xff09;作为通信协议。可以认为是纯java版本的分布式远程调用解决方法。 2&#xff09;RMI的核心概念 3&…

[翻译]编写高性能 .NET 代码 第一章:性能测试与工具 -- 选择什么来衡量

选择什么来衡量 在搜集数据测试数据前&#xff0c;你需要知道你要以怎样的指标来衡量测试结果。这听起来很容易&#xff0c;但实际上比你想象中的要难许多。如果你想降低内存使用量&#xff0c;你会选择什么方式呢&#xff1f; 私有工作集&#xff08;Private working set&am…