redis cluster pipeline

redis cluster

从3.0开始,redis开始支持集群模式。

cluster

  • 无中心架构

  • 按key将数据哈希到16384个slot上 HASH_SLOT = CRC16(key) mod 16384
  • 集群中的不同节点分别负责一部分slot

redis pipeline

代码

CronTrigger教程

这篇文章翻译自Quartz官方文档。

介绍

Cron是一个存在了很长时间的UNIX工具,它的调度功能很强大而且经过了验证。CronTrigger类基于cron的调度功能。 CronTrigger使用“cron表达式”,它能够创建类似这样的触发策略:“每周一至周五上午8:00”或“每月最后一个星期五凌晨1:30” Cron表达式很强大,但(规则)可能令人很困惑。 本教程旨在揭示撰写Cron表达式的一些谜团,为用户提供在论坛或邮件列表咨询之前可以访问的资源。

格式

Cron表达式是由空格分隔的6或7个字段组成的字符串。 字段可以包含任何允许的值,以及该字段允许的特殊字符的各种组合。 字段如下:

字段 是否必须 允许值 允许特殊字符
Seconds 0-59 , - * /
Minutes 0-59 , - * /
Hours 0-23 , - * /
Day of month 1-31 , - * ? / L W
Month 1-12 or JAN-DEC , - * /
Day of week 1-7 or SUN-SAT , - * ? / L #
Year empty, 1970-2099 , - * /

所以cron表达式可以像这样简单:* * * * ? * 或者更复杂一些,像这样:0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010

特殊字符

  • * (所有值) - 表示选择字段中所有的值。例如,“*” 在Minutes 字段表示“每分钟”
  • ?(没有具体的值)- 当您需要在允许该字符的两个字段之一中指定某些内容时很有用。 例如,如果我希望我的触发器在该月的某个特定日期(例如,第10天)触发,但不关心在一周的哪一天,我会在day-of-month字段放置“10”,以及day-of-week字段放置“?”。 请参阅以下示例以获得说明。

    这里有点拗口,其实很好理解,就是day-of-month和day-of-week2个字段是互斥的,因为它们都表示的天。如果两者中有一个指定了值,另一个必须设置成”?”,用来区分。译者注。

  • - - 用于指定范围。 例如,Hours字段中的“10-12”表示“小时10,11和12”。
  • , - 用于指定具体值。 例如,day-of-week字段中的“MON,WED,FRI”表示“星期一,星期三和星期五”。
  • / - 用于指定增量。 例如,Seconds字段中的“0/15”表示“秒0,15,30和45”。 秒字段中的“5/15”表示“秒5,20,35和50”。 你也可以在’‘字符后指定’/’ - 在这种情况下’‘相当于在’/’之前有’0’。 日期字段中的“1/3”表示“从该月的第一天开始每3天触发一次”。

    即 /10 相当于 0/10。译者注

  • L(LAST) - 在允许它的两个字段中的每个字段中具有不同的含义。 例如,day-of-month字段中的值“L”表示“月份的最后一天” - 1月31日,非闰年2月28日。 如果在day-of-week字段中,则表示“7”或“SAT”。 但是如果在day-of-week字段中用在另一个值后,则表示“该月的最后一个xxx日” - 例如“6L”表示“该月的最后一个星期五”。 您还可以指定从该月的最后一天开始的偏移量,例如“L-3”,这意味着该日历月的倒数第三天。 使用“L”选项时,重要的是不要指定列表或值范围,因为您会得到令人困惑/意外的结果。
  • W(weekday) - 用于指定最接近给定日期的工作日(周一至周五)。 例如,如果您指定“15W”作为day-of-month字段的值,则含义为:“最近的工作日到该月的15日”。 因此,如果15日是星期六,触发器将在14日星期五触发。 如果15日是星期日,触发器将在16日星期一触发。 如果15日是星期二,那么它将在15日星期二触发。 但是,如果您指定“1W”作为day-of-month的值,并且第1日是星期六,则触发器将在星期一(3日)触发,因为它不会“跳过”一个月的边界。 只有当day-of-month是一个单个的天,而不是范围或天数列表时,才能使用“W”字符。

‘L’和’W’字符也可以在day-of-month字段中组合以产生’LW’,这转换为“最后一个工作日”

  • # - 用于指定当月的“第n个”XXX天。 例如,day-of-week字段中的“6#3”的值表示“该月的第三个星期五”(第6天=星期五,“#3”=该月份的第3个星期五)。 其他例子:“2#1”=本月的第一个星期一,“4#5”=该月的第五个星期三。 请注意,如果您指定“#5”并且该月中没有给定星期几的5,则该月不会发生任何触发。

法定字符以及一周中几个月和几天的名称不区分大小写。 MON与mon相同。

例子

这是几个完整的例子:

表达式 含义
0 0 12 * * ? 每天12:00点触发
0 15 10 ? * * 每天10:15触发
0 15 10 * * ? 每天10:15触发
0 15 10 * * ? * 每天10:15触发
0 15 10 * * ? 2005 在2015年,每天上午10:15触发
0 * 14 * * ? 每天,从14:00开始,到14:59结束,每1分钟触发
0 0/5 14 * * ? 每天,从14:00开始,到14:55结束,每5分钟触发
0 0/5 14,18 * * ? 每天,在14:00-14:55,18:00-18:55时间段内,每5分钟触发
0 0-5 14 * * ? 每天,在14:00-14:05,每分钟触发
0 10,44 14 ? 3 WED 每年3月的每个星期3,14:10和14:44触发
0 15 10 ? * MON-FRI 每个星期一到星期五的10:15触发
0 15 10 15 * ? 每月15日,10:15触发
0 15 10 L * ? 每月最后一天,10:15触发
0 15 10 L-2 * ? 每月距最后1天的前2天,10:15触发
0 15 10 ? * 6L 每个月最后一个星期五的10:15触发
0 15 10 ? * 6L 2002-2005 在2002,2004,2004,2005年,每个月最后一个星期五的10:15触发
0 15 10 ? * 6#3 每个月第3个星期五的10:15触发
0 0 12 1/5 * ? 从1日开始,每隔5天的12:00触发
0 11 11 11 11 ? 每个11月的11:11触发

注意’?’ 和’*’ 在day-of-week和day-of-month字段中的用法!

说明

  • 同时指定day-of-week和day-of-month的支持尚未完成(您必须在其中一个字段中使用’?’字符)。
  • 在您的语言环境中发生“夏令时”更改的早上几小时之间设置触发时间时要小心(对于美国语言环境,这通常是凌晨2:00之前和之后的小时),因为时移会导致跳过或重复执行,这取决于时间向后移动还是向前跳跃。您可以参考此维基百科条目这有助于确定您的语言环境的细节:https://secure.wikimedia.org/wikipedia/en/wiki/Daylight_saving_time_around_the_world

Quartz最佳实践

这篇文章翻译自Quartz官方文档。

生产系统建议

跳过更新检查

Quartz包含一个“更新检查”机制,这个机制会尝试连接远端服务器,来检查是否有新版本可供下载。该检查是异步运行的,不会影响到启动/初始化,并且如果连接失败检查的节奏会逐渐衰减。如果运行检查时发现有更新,Quartz会通过打印日志的方式报告出来。

你可以使用Quartz配置属性“org.quartz.scheduler.skipUpdateCheck:true”或系统属性 “org.terracotta.quartz.skipUpdateCheck = true”(你可以在系统环境中设置或在java命令中以 -D的形式) 来禁用更新检查。 建议你生产环境部署时禁用更新检查。

JobDataMap建议

在JobDataMap中只存储原始数据类型(包括String)

在JobDataMap中只存储原始数据类型(包括String),短期和长期来看,这可以避免数据序列化问题。

使用Merged JobDataMap

在Job执行时,可以很方便的从JobExecutionContext中获取JobDataMap使用。此JobDataMap是从JobDetail和Trigger获取的JobDataMap的合并,且前者中同名的值会被后者覆盖。

你在调度器上的一个Job,可供多个Triggers定期/重复使用,此时将JobDataMap值存储在Trigger上,每次独立触发时,你的Job就可以获取不同的输入。

鉴于以上所有,以下是我们推荐的最佳实践:在Job.execute(..)的代码中,应该从JobExecutionContext中的JobDataMap获取数据,而不是直接从JobDetail中获取。

Trigger建议

使用TriggerUtils

TriggerUtils:

  • 提供一种更简单的方法去创建triggers(schedulers)
  • 包含丰富的方法去用符合特定描述的方式去创建triggers和schedulers,而不是直接实例化具体的triggers(例如:SimpleTrigger, CronTrigger等),然后调用setter方法去配置它们
  • 提供一种更简单的方式创建Dates(for start/end dates)
  • 给分析triggers提供帮助(例如:计算未来触发时间)

JDBC建议

不要对Quartz的表直接写入

直接想数据库写入调度数据(通过SQL)而不是使用scheduling API:

  • 导致数据占用(删除数据、抢占数据)
  • 导致trigger触发时间到了,job没有执行但数据莫名其妙“消失”
  • 导致trigger触发时间到了,job没有执行执行
  • 可能导致死锁
  • 其他奇怪的问题、数据占用

在非集群模式下,不要将两个调度器(scheduler)指向一个数据库。

如果你将多于一个scheduler实例指向同一组数据库表,且这些实例没有配置集群模式,可能产生以下结果:

  • 导致数据占用(删除数据、抢占数据)
  • 导致trigger触发时间到了,job没有执行但数据莫名其妙“消失”
  • 导致trigger触发时间到了,job没有执行执行
  • 可能导致死锁
  • 其他奇怪的问题、数据占用

确保充足的数据库连接

建议将数据源最大连接大小配置为至少为线程池中的工作线程数加3。如果您的应用程序也频繁调用调度程序API,则可能需要其他连接。 如果您使用的是JobStoreCMT,则“非托管”数据源的最大连接大小应至少为4。

夏令时(DST)

避免在夏令时过渡时间附近调度job

注意: 过渡时间的细节和时钟向前或向后移动的时间因地点而异,详细参考:https://secure.wikimedia.org/wikipedia/en/wiki/Daylight_saving_time_around_the_world.

SimpleTriggers不受夏令时影响,因为它们总是以精确的毫秒时间触发,并重复精确的毫秒数.

因为CronTriggers在给定的小时/分钟/秒时触发,所以当DST转换发生时它们会受到一些奇怪的影响.

作为可能出现问题的一个例子,在美国的TimeZones /位置观察夏令时,如果使用CronTrigger并在凌晨1:00和凌晨2:00安排开火时间,可能会出现以下问题:

  • 1:05 AM 可能触发两次! - 可能在CronTrigger上重复触发
  • 2:05 AM 可能会丢失触发! - 可能错过CronTrigger的触发

同样,时间和调整量的具体情况因地区而异。

其他基于沿日历滑动的触发类型(而不是精确的时间量),例如CalenderIntervalTrigger,也会受到类似的影响 - 但是不是错过了一次触发,或者两次触发,最终可能会让它的触发时间偏移一个小时。

Jobs

等待条件

长时间运行的job可能会阻止别的任务执行(如果线程池中所有的线程都是busy状态) 如果你需要在job执行线程中调用Thread.sleep(),这通常表明你的Job还没有准备好做接下来的工作,因为它需要等待一些其他条件(例如一些数据还没有准备好)。 一个更好的办法是,释放工作线程(exit the job),把资源留给其他的job。你可以让重新调度这个job,或者在退出前调度其他job。

抛出异常

一个job的execute方法应该包含try-catch块,处理所有可能的异常。 如果一个job抛出异常,quartz典型的做法是重新执行它(当然它多半再次抛出同样的异常)。如果一个job 捕获可能遇到的所有异常,处理他们,重新调度它自己,或其他任务,就能解决这个问题。

可恢复性和幂等性

进行中的job,如果标记为“可恢复”,在scheduler失效后会被重新调度。这意味着一些job做的工作会被执行两次。 这意味着job需要被编码成幂等的方式。

Listeners(TiggerListener,JobListener,SchedulerListener)

确保Listeners中的代码够简洁、高效。

不建议在Listener中做大量的工作,因为执行Job的线程和Listeners捆绑在一起。

处理你的Exceptions

每个listener的方法都应该包含try-catch代码块,处理所有可能的exception。 如果一个listener抛出一个异常,可能导致别的listeners不能被通知到,或者会中止job的执行。

通过应用程序暴露Scheduler的方法

小心安全!

一些用户会把quartz的Scheduler方法通过应用程序接口暴露出去。这看起来很有用,不过也极其危险。

你要确定不能让用户随意定义他们想要的Job类型。例如,quartz提供了一种预制的任务 org.quartz.jobs.NativeJob,这种Job可以执行任意的、他们事先定义好的原生系统命令(操作系统级别)。 同样,其他的任务例如SendEmailJob,包括其他任何有恶意意图的Job

为了更高效率,允许用户定义任意的Job,你可能都将会面临与OWASP和MITER定义的命令行攻击 相当的威胁。

Java日志框架

这篇文章写于2014年,适当做了一些调整。

名次解释

Log4j (The original apache logging framework for java),很早以来,使用最为广泛的日志框架。
Commons Logging,Apache基金会所属的项目,是一套Java日志接口,之前叫Jakarta Commons Logging,后更名为Commons Logging。
SLF4J (Simple logging facade for java),类似于Commons Logging,是一套Java日志接口。
Logback,SLF4J日志接口的实现。
JUL (Java util logging),自Java1.4以来的官方日志实现。

是不是觉得很混乱?先看一段历史八卦吧:

早在1996年,E.U. SEMPER项目组(Secure Electronic Marketplace for Europe Research,欧洲安全电子交易研究所,欧盟建立的一个通过Internet进行货币支付的项目)就决定开发自己的tracing API,这套API就是Log4j的前身。其作者是Ceki Gülcü。后来这套api经过各种变种,最终成为Apache基金会项目中的一员。 期间Log4j近乎成了Java社区的日志标准。据说Apache基金会还曾建议sun引入log4j到java的标准库中,但Sun拒绝了(未求证)。

2002年Java1.4发布,Sun推出了自己的日志库,JUL(Java Util Logging),其实现基本模仿了Log4j的实现。

接着,Apache推出了Jakarta Commons Logging项目,JCL只是定义了一套日志接口(其内部提供一个Simple Log的简单实现),支持运行时动态加载日志组件的实现,也就是说,在你应用代码里,只需调用Commons Logging的接口,底层实现可以是Log4j,也可以是Java Util Logging。 即便Sun推出了官方的Log API,但很少有人用。

2006年,Ceki Gülcü不适应Apache的工作方式,离开了Apache(据说,未求证)。然后先后创建了SLF4J(日志门面接口,类似于Commons Logging)和Logback(SLF4J的实现)两个项目,并回瑞典创建了QOS公司(Quality Open Software,based in Lausanne, witzerland.),以在线销售Log4j Manual文档、SLF4J/Logback技术支持和为期2天的SLF4J/Logback技术培训为主营业务。

QOS官方网站上是这样描述Logback的:

一个通用,可靠,快速且灵活的日志框架。

Logback还声称:

某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高。这个操作在Logback中需要3纳秒,而在Log4J中则需要30纳秒。LogBack创建记录器(logger)的速度也更快:13毫秒,而在Log4J中需要23毫秒。更重要的是,它获取已存在的记录器只需94纳秒,而Log4J需要2234纳秒,时间减少到了1/23。跟JUL相比的性能提高也是显著的。

此后一直持续到现今,Java日志领域被划分为两大阵营:Commons Logging和SLF4J。Commons Logging在Apache大树的笼罩下,有很大的用户基数。但有证据表明,形式正在发生变化。有人分析了GitHub上30000个项目,统计出了最流行的100个Libraries,可以看出一些端倪。

八卦完毕。是否Log4j,commons-logging,SLF4J,Logback之间的关系一下子就清晰了?

SLF4J/Logback对比commons logging/log4j的一些优势:

  1. 性能方面的优势
  2. Commons logging,为了减少构建日志信息的开销,通常的做法是:

    if(log.isDebugEnabled()) { log.debug(“User name: “ + user.getName() + “ buy goods id:” + good.getId()); }

    在SLF4J中,你只需这么做:

     log.debug("User name:{}, buy goods id :{}", user.getName(), good.getId());
    

    也就是说,slf4j把构建日志的开销放在了它确认需要显示这条日志之后,减少了内存和cpu的开销,代码也更为简洁。但计算参数的开销并没有被推迟。比如:

     log.debug("User name:{}, user’s password:{}", user.getName(), crypt(password));
    

    这也是SLF4J仍保留isDebugEnabled方法的原因。

  3. Commons logging是通过动态查找机制,在程序运行时,使用自己的ClassLoader寻找和载入本地具体的实现。详细策略可以看commons-logging-*.jar包中的org.apache.commons.logging.impl.LogFactoryImpl.java文件。由于OSGi不同的插件使用独立的的classLoader,其机制限制了commons logging在OSGi中的正常使用。Slf4j在编译期间,静态绑定本地的LOG库,因此可以在OSGi中正常使。
  4. SLF4J/Logback的文档是免费的,Log4j是收费的。
  5. 丰富的Appender(当然你可以继承Logback提供的接口,定制自己的Appender)。

做出选择

对于一个新项目,不要犹豫,使用SLF4J+Logback吧。 仅仅是这样么?答案是NO。 去看一看,你项目中使用的第三方jar吧。Spring依赖的是Commons logging,xSocket则使用Java Util Logging记录日志,如果只配置了Logback,你可能会丢失一部分日志甚至出错 有没有好的办法呢?当然有:使用桥接器! 桥接器是一个伪造的日志实现,它允许你将Commos logging收集的日志,重定向至SLF4J。

你唯一需要做的,就是将以下jar包引入你的工程:

log4j-over-slf4j-xx.jar //log4j to slf4j
jcl-over-slf4j-xx.jar  //commos logging
jul-to-slf4j-xx.jar  //java Util Logging

注意,如果你的工程中同时存在log4j-over-slf4j.jar,slf4j-log4j12.jar,你的日志会陷入死循环进而可能导致内存溢出。

Logback的配置

参考logback.xml

Logback加载配置流程:

  1. 尝试在classpath下查找logback-test.xml文件
  2. 如果文件不存在,查找logback.xml文件
  3. 如果文件不存在,使用BasicConfigurator自动对其进行配置。

选择规则

日志记录请求级别为p,其logger的有效级别为q,只有则当p>=q时,该请求才会被执行。 该规则是logback的核心,其他各日志框架也遵循此规则。 Level排序为:

TRACE < DEBUG < INFO < WARN < ERROR。

Logger的有效Level

Logger L可以手动分配级别。如果L未被分配,它将从父层次等级中第一个非Null继承。所以为了确保每一个logger都持有一个level,根logger需要持有一个level。 例如com.flyer,如果手动指定了,其有效Level即为手动指定。若未指定,从com继承。若com为null,从root logger继承。

Logback的Appender

列举一些常见的appender:

  • ConsoleAppender,输出至控制台
  • FileAppender,输出至文件
  • RollingFileAppender,输出至可以滚动的文件。TriggeringPolicy,决定是否以及何时进行滚动,RollingPolicy,负责滚动。TimeBasedRollingPolicy 它根据时间来制定滚动策略
  • SocketAppender,它通过序列化LoggingEven 将日志记录输出到远程。如果远程服务是关闭的,日志会被丢弃,其后台通过一个线程定时去尝试连接远程
  • JMSTopicAppender和JMSQueueAppender,它允许将日志输出至JMS。
  • SMTPAppender,输出至邮件服务器。
  • DBAppender,输出至DB,支持DB2,MySQL,Oracle,SQLServer,PostgreSQL等。
  • SyslogAppender,输出至*nix的syslog守护线程。

Logback的过滤器

基于三值逻辑(ternary logic),允许把它们组装成链,从而组成任意的复合过滤策略。过滤器很大程度上受到Linux的iptables启发。

  • DENY,立即被抛弃。
  • NEUTRAL,交给下一个过滤器处理。
  • ACCEPT,立即处理。

其他特性

Logback的其他特性,例如排版规则,MDC(Mapped Diagnostic Context),JMX注册等可以参考Logback的官方文档