分布式架构下的定时任务及分布式任务锁实现
Posted 攻城狮的那点事
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式架构下的定时任务及分布式任务锁实现相关的知识,希望对你有一定的参考价值。
定时任务的需求在众多应用系统中广泛存在, 从实现的技术上来分类,目前主要有三种技术:
1,Java自带的java.util.Timer类。
这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。
class MyTask extends TimerTask{
@Override
public void run() {
System.out.println("This is a test data!");
}
}
public class Job {
public static void main(String[] args) {
//创建定时器对象
Timer t=new Timer();
long delay = 0;//延时执行时间,单位毫秒
long intevalPeriod = 1 * 1000;
t.schedule(new MyTask(), delay, intevalPeriod);
}
}
2,使用Quartz。
这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂。
在Spring中声明并且配置作业调度的触发方式:
<!-- 任务类 -->
<bean id="taskJob" class="com.XXX.MyTask />
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="group" value="job_work" />
<property name="name" value="job_work_name" />
<!--false表示等上一个任务执行完后再开启新的任务 -->
<property name="concurrent" value="false" />
<property name="targetObject">
<ref bean="taskJob" />
</property>
<property name="targetMethod">
<value>job</value>
</property>
</bean>
<bean id="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="name" value="work_default_name" />
<property name="group" value="work_default" />
<property name="jobDetail">
<ref bean="jobDetail" />
</property>
<property name="cronExpression" value="0/10 * 09-16 * * ?"/>
</bean>
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="myTrigger"/>
</list>
</property>
</bean>
任务执行类:
public class MyTask{
public void job(){
//TODO 要执行的方法
System.out.println("quartz task ......");
}
}
这里 Quartz的作业触发器有两种,分别是:
org.springframework.scheduling.quartz.SimpleTriggerBean
org.springframework.scheduling.quartz.CronTriggerBean
第一种SimpleTriggerBean,只支持按照一定频度调用任务,如每隔一段时间运行一次。
第二种CronTriggerBean,支持到指定时间运行一次,如每天12:00运行一次。
3,Spring3.0以后自带的task。
可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。
@Component
public class SchedulerJob {
/**
* 每天0点执行一次
*
*/
@Scheduled(cron = "0 0 0 * * ?")
public void job1(){
//TODO 要执行的任务
}
/**
* 每5秒执行一次
*
*/
@Scheduled(fixedRate = 5000)
public void job2(){
//TODO 执行的任务
}
}
在分布式环境中,当带定时任务的服务做集群时,怎么才能确保计划任务最多同时执行一次呢?这时我们就需要用任务锁来解决,如果正在一个节点上执行任务,它将获取一个锁,以防止从另一个节点(或线程)执行相同任务。具体实现方式很多,如支持Mongo、数据库、redis、hazelcast或zookeeper等协调的spring scheduled task。这里我讲讲用mysql数据库和Redis的实现。
1,用mysql实现定时任务锁
CREATE TABLE `job_manager` (
`job_id` int(20) NOT NULL PRIMARY KEY COMMENT '主键',
`job_name` varchar(50) NOT NULL COMMENT '名称',
`job_desc` varchar(50) COMMENT 'job描述',
`server_ip` varchar(50) NOT NULL COMMENT '服务器ip',
`is_locked` tinyint(1) DEFAULT 0 COMMENT '任务是否锁定 0-否,1-是',
`update_date` datetime NOT NULL COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='job管理'
当某一节点上的任务获取锁时,就修改数据库job_manager中的is_locked 字段到1,另一个节点在执行前获取此任务信息,并判断锁定状态,如果已经锁定,就不在执行,这样就防止多节点同时执行某一定时任务。
2,通过Redis实现任务锁
下面为 Redis加锁和解锁的方法实现,在加锁时,为防止任务执行完不能释放,因此给锁设了过期机制。
/**
* 加锁
*/
public boolean lock(String key, String value){boolean bool = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
if(bool){
//锁有效期5分钟
stringRedisTemplate.expire(key, 300, TimeUnit.SECONDS);
}
return bool;
}
/**
* 解锁
*/
public void unLock(String key){stringRedisTemplate.delete(key);
}
任务实现方式:
@Component
public class SchedulerJob {
/**
* 每5分钟执行一次
*/
@Scheduled(cron = "0 0/5 * * * ?")
public void job1(){
String key = "test";
String value = "test";
if(!cacheService.isExistLock(key)){
try {
if(cacheService.lock(key, value)){
//TODO 要执行的任务
}
} catch (Exception e) {
//异常处理
}finally {
if(cacheService.isExistKey(key)){
//解锁cacheService.unLock(key);
}
}
}
}
以上是关于分布式架构下的定时任务及分布式任务锁实现的主要内容,如果未能解决你的问题,请参考以下文章
定时任务部署多少台服务器,怎么确保只有一台服务器执行--redis 分布式锁