[spring]task标签及定时任务相关

一、 前言

本篇文章主要分为以下三部分:

  • applicationContext.xml-task命名空间中各配置项的回顾及梳理;
  • Spring Task定时任务的实现(注解方式);
  • Spring Task的问题及相关解决方式;

本次学习使用的Spring的版本:4.3.15。

二、Spring Task

1. 半注解半XML配置

  前面我们依次重新梳理了beans,mvc,context等命名空间的各项配置,今天我们再来看一下task命名空间的一些配置项。简单来说,task命名空间是Spring Framework中用于支持定时任务和异步调用的。首先,要引入task命名空间:

xmlns:task="http://www.springframework.org/schema/task

http://www.springframework.org/schema/task 
http://www.springframework.org/schema/task/spring-task.xsd
1.1 task:annotation-driven标签

该标签是task命名空间中最基础的标签,用于开启定时任务和异步调用的注解支持,来简单看下该注解的几个属性:

  1. scheduler,配置对应的定时任务对象,如果没有配置,默认将使用TaskScheduler实例;
  2. executor,配置对应的异步执行的对象,如果没有配置,默认将使用SimpleAsyncTaskExecutor实例;
  3. exception-handler,在异步执行期间,抛出的异常的实例,默认使用SimpleAsyncUncaughtExceptionHandler,抛出的异常不会被程序捕获到;
  4. proxy-target-class,是否要创建CGLIB代理,默认是false,也就是创建的是基于Java接口的代理;
  5. mode,异步调用的模式,默认的异步调用是通过Spring AOP来实现的,不过我们可以通过该属性指定是否需要使用Aspectj的支持,使用Spring Aop代理时还可以通过proxy-target-class属性指定是否需要强制使用CGLIB做基于Class的代理;该属性有两个选项:proxyaspectj
1.2 task:executor标签

该标签是用于对任务执行的通用配置,可用于执行不同的任务策略:同步的,异步的,使用线程池的等。通常用于异步调用,来简单看下属性:

  1. id,这个不多说了,对应的executor的实例名称,通常是executor,也就是ThreadPoolTaskExecutor实例,一般用于Async异步执行的时候需要配置对应的executor;
  2. pool-size,线程池中线程的数量(单个值或者范围,如5-10);如果为10,表示核心线程数是10,最大线程数也是10;如果是5-10,表示核心线程数是5,最大线程数是10;如果不指定,则默认核心线程数是1,最大线程数是Integer.MAX_VALUE;最大线程数只有在队列容量不是无限制的时候才有用;
  3. queue-capacity,队列容量,如果没有指定,默认Integer.MAX_VALUE;
  4. keep-alive,表示超过核心线程数的线程 在完成任务之后,处于空闲状态的时间限制,也就是说过了这段时间之后,线程会终止掉,单位是秒;为0会导致多余的线程在执行完任务后立即终止,而不需要在任务队列中执行后续工作;
  1. rejection-policy,线程池中的任务队列满了以后对于新任务的处理策略:
    • ABORT 默认,抛出异常,然后不执行相应的任务;
    • DISCARD 不执行任务,也不抛出异常,也就是忽略这个任务;
    • DISCARD_OLDEST 将队列中最旧的那个任务丢弃,执行新任务;
    • CALLER_RUNS 不在新线程中执行任务,而是强制由调用者所在的线程来执行;
1.3 task:scheduler标签

该标签很简单,配置项不多,是用于定时任务相关的统一的配置,来看下属性:

  1. id,用于定时调度任务的ThreadPoolTaskScheduler的bean的id,一般用于@Scheduled定时任务执行;
  2. pool-size,定时调度线程池的大小,默认是1;
1.4 task:scheduled-tasks和task:scheduled标签

  task:scheduled-tasks标签及其子标签task:scheduled是用于配置具体的定时任务,该标签唯一的属性scheduler指定定时任务使用的scheduler实例,我们来看下task:scheduled标签的一些属性:

  1. ref,所引用的schedule的实例id;
  2. method,定时任务要调用的方法的名称;
  3. cron,cron表达式,这个就不多介绍了,网上有许多详细的介绍;
  4. fixed-delayfixed-rateinitial-delay,这三个属性在前篇文章中已经详细介绍过了,这里不多介绍了,文章地址[spring注解]Spring相关注解(四),然后ctrl + f,搜索scheduled字符串即可;
  5. trigger,实现触发器接口的bean;
1.5 简单总结

  到这,task标签的配置项已经介绍完了,这里的配置其实是一种半注解半XML的形式,上述配置项配置完之后,我们就可以使用注解AsyncScheduled来完成我们的异步调用和定时调度任务了。简单贴一下我们常用的配置形式:

<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>
2. 注解形式
2.1 具体实现

  上面的配置其实不是完全注解的形式,在上述配置中,我们可以通过task:scheduled标签来执行定时任务,也可以通过注解@Scheduled来执行定时任务。而这里,我们来看一下完全使用注解的形式:

  1. 在定时任务对象上添加两个用于开关的注解:@EnableScheduling(开启定时调度任务),@EnableAsync(开启异步执行);
  2. 定义用于定时调用的schedule实例和用于异步执行的executor实例,然后在需要调用的地方使用注解@Scheduled和@Async;

我们来简单看下实例:

@EnableWebMvc
@EnableScheduling
@EnableAsync
public class SpringConfig extends WebMvcConfigurerAdapter {

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(5);
        return taskScheduler;
    }

    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setMaxPoolSize(12);
        taskExecutor.setCorePoolSize(4);
        return taskExecutor;
    }
}

这里简单介绍下TaskExecutor

TaskExecutor,任务执行接口,可用于执行不同的任务策略:同步的,异步的,使用线程池的等;TaskExecutorjava.util.concurrent.Executor接口是相同的,实际上,它的存在主要是为了在使用线程池的时候,将对Java5的依赖抽象出来;该接口只有一个execute(Runnable task)方法,它根据线程池的语义和配置,来接受一个执行任务。

而针对执行任务的@Scheduled和@Async注解,在前文也已经介绍过了,这里就不再多介绍了,只简单说下@Async:

Async 注解用于类或者方法,用于标识某个方法或某个类的所有方法都是异步执行的,方法被调用的时候,会在新线程中执行,而调用它的方法会在原来的线程中执行;

2.2 其他说明
  1. TaskExecutor的实现类有许多,比如SimpleAsyncTaskExecutorSyncTaskExecutorConcurrentTaskExecutorSimpleThreadPoolTaskExecutorThreadPoolTaskExecutor等,而这里最常用的就是基于线程池的实现ThreadPoolTaskExecutor,有兴趣的可以看下它的常用参数,都很简单这里就不多说了,如果要了解更多,可以参考后面给出的官方文档地址。
  1. @Async注解标识的方法默认会被<task:annotation-driven/>指定的TaskExecutor执行,如果需要针对某个特定的异步执行使用一个特定的TaskExecutor,则可以通过@Async的value属性指定需要使用的TaskExecutor对应的bean名称;至于该注解的其他属性,这里就不多说了。
2.3 注意事项

  使用@Async注解标识的方法进行异步调用是通过Spring AOP来实现的,所以@Async注解的方法必须是public方法,且必须是外部调用。这点其实和Transactional注解是类似的,也就是在同一个类中,一个方法调用另一个有注解的方法,注解是不会生效的。

3. Spring Task的集群问题

  使用Spring的注解@Scheduled可以很方便的执行一个定时任务,单台服务器下是没有问题的,如果在多台服务器做负载均衡的情况下,有可能会出现定时任务的重复执行的问题,因为集群的各台服务器之间数据是不会共享的,每台服务器上的任务都会按时执行。这种情况其实就是集群环境下的状态同步问题,针对这种情况,我们需要考虑的就是如何保证在同一时刻只有一个任务在执行。

3.1 借助数据库Mysql来实现

  由于数据库本身的锁机制,我们可以借助数据库来实现,其实也就是说两个任务同时操作表里的一条记录,只有一条能操作成功,我们可以借助数据库排他锁的这个特性来实现。

当然这种情况有一些问题需要考虑,比如说某台服务器挂了数据库锁的释放,表中记录状态的维护等。

3.2 基于redis实现

  其实这个问题就是一个任务互斥的问题,如果学过操作系统的话,就很好理解,就是操作系统中所谓的PV操作,也就是说,声明一个分布式互斥锁,一台服务器获得锁后,改变锁状态,然后执行任务,任务完成还原状态;另一台服务器获取锁,如果是锁定状态,说明正在有其他服务器执行,不执行任何操作,直接结束。

  1. 我们可以借助Redis的原子特性,使用递增递减方法来实现,如果使用jedis来操作的话,使用incr和decr来实现;
  2. 无论任务是否执行成功,一定要记得执行状态的还原,也就是锁失效状态,这种可以通过指定超时时间来实现;同样,如果服务器重启,记得重置该锁的状态;
  3. 超时时间的设置不要大于两次任务间隔的时间;

简单写下代码:

String cacheKeyPV = ...;

@PostConstruct
public void init() {
    // 封装方法,设置超时时间等
    jedis.incr(cacheKeyPV);
}

try {
    //P:设置为1
    // 借助jedis的expire等方法简单封装一下incr
    long resources = jedis.incr(cacheKeyPV,10, CacheTimeUnit.SECOND);
    if(resources == 1) {
        // 执行操作
    }
} catch (Exception e) {
    // exception
} finally {
    try {
        //V:重新设置为0
        jedis.decr(cacheKeyPV) ;
    } catch (Exception e) {
        // catch
    }
}
3.3 Quartz框架

  Quartz框架是一个优秀的框架,功能十分强大,支持集群环境下的任务调度,但功能有点复杂,对于一些常规的简单的项目,是不建议使用的,如果有兴趣的,可以了解下。

3.4 借助一些开源的分布式任务调度系统

  目前,市面上有许多开源的分布式的任务调度系统,比如说LTS,Elastic-Job,Uncode-Schedule等,如果需要了解更多,可以参考:https://my.oschina.net/editorial-story/blog/883856

参考地址:
官方文档:Spring4.3.15.Release-docs-html-scheduling
官方文档的中文翻译可参考简书的一篇文章:https://www.jianshu.com/p/69e44b93bb47
Spring API地址:https://docs.spring.io/spring/docs/
其他参考自:在同一个类中,一个方法调用另外一个有注解方法失败的原因
另外,刚兴趣的童鞋可以再去看下操作系统的PV操作,这个很有意思的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,856评论 25 707
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,778评论 6 342
  • 生活啊,多的是呻吟,更多的人感觉生活就像一滩泥泽,被昏暗的天空,浑浊的空气压抑着。 在我曾经的角度去看待这个世界是...
    成为自己蛮阅读 698评论 1 2
  • 身体力行和感觉认知。 1,文字,单就以本身的存在形式而言,更偏向于感觉认知,除非你练习书法,绘画,你有笔墨纸砚,这...
    鲁莽书生阅读 696评论 0 0