项目开发过程中,难免会有许多定时任务的需求进来。如果项目中还没有引入quarzt框架的情况下,我们通常会使用Spring的@Schedule(cron="* * * * *")注解

样例如下:

package com.slowcity.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled; public class SentMailTask {
private static final Logger log = LoggerFactory.getLogger(SentMailTask.class);
/**
* 定时任务
*/
@Scheduled(cron = "0 0/1 * * * ? ")
public void closeOrderTaskV1() {
log.info(".........schedule task start........."); sentMailToCustomer(); log.info(".........schedule task end.........");
} public void sentMailToCustomer() {
log.info(".........sent mail to customer.........");
}
}

这样实现自然是没有什么问题,对于单台机器部署,任务每一分钟执行一次。部署多台机器时,同一个任务会执行多次

在我们的项目当中,使用定时任务是避免不了的,我们在部署定时任务时,通常只部署一台机器,此时可用性又无法保证现实情况是独立的应用服务通常会部署在两台及以上机器的时候,假如有3台机器,则会出现同一时间3台机器都会触发的情况,结果就是会向客户发送三封一模一样的邮件,真让人头疼。如果使用quarzt,就不存在这个情况了。

这种并发的问题,简单点说是锁的问题,具体点是分布式锁的问题,所以在这段代码上加个分布式锁就可以了。分布式锁,首先想到的是redis,毕竟轮子都是现成的。

package com.slowcity.redis;

import java.util.Collections;
import redis.clients.jedis.Jedis; public class RedisPool {
private static final String LOCK_SUCCESS="OK";
private static final String SET_IF_NOT_EXIST="NX";
private static final String SET_WITH_EXPIRE_TIME="PX";
private static final Long RELEASE_SUCCESS=1L; /**
* 获取分布式锁
* @param jedis
* @param lockKey
* @param requestID
* @param expireTime
* @return
*/
public static boolean getDistributedLock(Jedis jedis,String lockKey,String requestId,int expireTime) {
String result = jedis.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,expireTime);
if(LOCK_SUCCESS.equals(result)) {
return true;
}
return false; }
/**
* 释放分布式锁
* @param jedis
* @param lockKey
* @param requestId
* @return
*/
public static boolean releaseDistributedLock(Jedis jedis,String lockKey,String requestId) {
String script = "if redis.call('get',KEYS[1])== ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Object result = jedis.eval(script,Collections.singletonList(lockKey),Collections.singletonList(requestId));
if(RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}

改造一下定时任务,增加分布式锁

package com.slowcity.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled; import redis.clients.jedis.Jedis; public class SentMailTask {
private static final Logger log = LoggerFactory.getLogger(SentMailTask.class);
/**
* 定时任务
*/
@Scheduled(cron = "0 0/1 * * * ? ")
public void closeOrderTaskV1() {
log.info(".........schedule task start.........");
Jedis jedis = new Jedis("10.2.1.17",6379);
boolean locked = RedisPool.getDistributedLock(jedis, "", "", 10*1000);
if(locked) {
sentMailToCustomer();
}
RedisPool.releaseDistributedLock(jedis, "", "");
jedis.close();
log.info(".........schedule task end.........");
} public void sentMailToCustomer() {
log.info(".........sent mail to customer.........");
}
}

再执行定时任务,多台机器部署,只执行一次。

关于jedis对象的获取,一般都是springboot自动化配置的,所有会想到工厂方法。优化如下:

package com.slowcity.redis;

import java.lang.reflect.Field;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.ReflectionUtils;
import redis.clients.jedis.Jedis; public class SentMailTask {
private static final Logger log = LoggerFactory.getLogger(SentMailTask.class); @Autowired
private RedisConnectionFactory redisConectionFactory; /**
* 定时任务
*/
@Scheduled(cron = "0 0/1 * * * ? ")
public void closeOrderTaskV1() {
log.info(".........schedule task start........."); RedisConnection redisConnection = redisConectionFactory.getConnection();
Field jedisField = ReflectionUtils.findField(JedisConnection.class, "jedis");
Jedis jedis = (Jedis) ReflectionUtils.getField(jedisField, redisConnection); boolean locked = RedisPool.getDistributedLock(jedis, "lockKey", "requestId", 10*1000);
if(locked) {
sentMailToCustomer();
}
RedisPool.releaseDistributedLock(jedis, "", "");
jedis.close();
log.info(".........schedule task end.........");
} public void sentMailToCustomer() {
log.info(".........sent mail to customer.........");
}
}
 

再也不用担心,应用服务多台机器部署,每台机器都触发的尴尬了。如果定时任务很多,最好的还是老老实实写个任务调度中心,一来方便管理,二来方便维护。

补充部分:

一些关于lua脚本的解释

String script = "if redis.call('get',KEYS[1])== ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Object result = jedis.eval(script,Collections.singletonList(lockKey),Collections.singletonList(requestId));

如果一个请求更新缓存的时间比较长,甚至比锁的有效期还要长,导致在缓存更新的过程中,锁就失效了,此时另一个请求就会获取锁,但前一个请求在缓存更新完毕的时候,如果不加以判断就直接删除锁,就会出现误删除其它请求创建的锁的情况。

【end】

一点补充的话,写完这篇博客后来看其他博客,也有一种redis锁是关联主机ip的,思路上是可行的,不失一个方法点,主要描述如下:

每个定时任务都在Redis中设置一个Key-Value,Key为自定义的每个定时任务的名字(如task1:redis:lock),Value为服务器Ip,同时设置合适的过期时间(例如设置为5min)。

每个节点在执行时,都要进行以下操作:

  • 1.是否存在Key,若不存在,则设置Key-Value,Value为当前节点的IP
  • 2.若存在Key,则比较Value是否是当前Ip,若是则继续执行定时任务,若不是,则不往下执行。

基于Redis实现分布式定时任务调度的更多相关文章

  1. 基于redis的分布式锁(转)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  2. 基于redis的分布式锁(不适合用于生产环境)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  3. redis系列:基于redis的分布式锁

    一.介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...

  4. 分布式定时任务调度系统技术解决方案(xxl-job、Elastic-job、Saturn)

    1.业务场景 保险人管系统每月工资结算,平安有150万代理人,如何快速的进行工资结算(数据运算型) 保险短信开门红/电商双十一 1000w+短信发送(短时汇聚型) 工作中业务场景非常多,所涉及到的场景 ...

  5. 基于Redis的分布式锁设计

    前言 基于Redis的分布式锁实现,原理很简单嘛:检测一下Key是否存在,不存在则Set Key,加锁成功,存在则加锁失败.对吗?这么简单吗? 如果你真这么想,那么你真的需要好好听我讲一下了.接下来, ...

  6. 基于redis 实现分布式锁的方案

    在电商项目中,经常有秒杀这样的活动促销,在并发访问下,很容易出现上述问题.如果在库存操作上,加锁就可以避免库存卖超的问题.分布式锁使分布式系统之间同步访问共享资源的一种方式 基于redis实现分布式锁 ...

  7. 基于redis的分布式锁

    <?php /** * 基于redis的分布式锁 * * 参考开源代码: * http://nleach.com/post/31299575840/redis-mutex-in-php * * ...

  8. 基于Redis的分布式锁真的安全吗?

    说明: 我前段时间写了一篇用consul实现分布式锁,感觉理解的也不是很好,直到我看到了这2篇写分布式锁的讨论,真的是很佩服作者严谨的态度, 把这种分布式锁研究的这么透彻,作者这种技术态度真的值得我好 ...

  9. 基于 Redis 的分布式锁

    前言 分布式锁在分布式应用中应用广泛,想要搞懂一个新事物首先得了解它的由来,这样才能更加的理解甚至可以举一反三. 首先谈到分布式锁自然也就联想到分布式应用. 在我们将应用拆分为分布式应用之前的单机系统 ...

随机推荐

  1. 第二节: Redis之Set类型和SortedSet类型的介绍和案例应用

    一. Set类型基础 1. 类型说明 1个key→多个value,value的值不重复! Set一种无序且元素内容不重复的集合,不用做重复性判断了,和我们数学中的集合概念相同,可以对多个集合求交集.并 ...

  2. Python Jupyter 网站编辑器

    Python Jupyter 网站编辑器 jupyter 是 python的网站编辑器可以直接在网页内编写python代码并执行,内置是通过ipython来调用的.很方便灵活. 安装 1.安装ipyt ...

  3. Powershell ExecutionPolicy 执行策略

    简单说明 powershell对于脚本的执行有着严格的安全限制 Get-ExecutionPolicy -List #查看当前的执行策略 Set-ExecutionPolicy -Scope Curr ...

  4. Elasticsearch PUT 插入数据

    { "error": { "root_cause": [ { "type": "illegal_argument_exceptio ...

  5. c#编码注释

    1      目录 2       前言... 3 2.1        编写目的... 3 2.2        适用范围... 4 3       命名规范... 4 3.1        命名约 ...

  6. Entity Framework 导航属性(2)

    1.学校 [Table("School")] public partial class School { public School() { Students = new List ...

  7. php字符串查找函数 php查找字符串中出现的次数函数substr_count,判断字符串中是否包含另一个字符串函数strpos

    php字符串查找函数 php查找字符串中出现的次数函数substr_count,判断字符串中是否包含另一个字符串函数strpossubstr_count($haystack, $needle [,$o ...

  8. wpf 打开win8系统软件盘

    三个函数 一) /// <summary> /// 判断进程是否正在运行 /// </summary> /// <param name="process&quo ...

  9. maven 学习---使用Maven清理项目

    在基于Maven的项目中,很多缓存输出在“target”文件夹中.如果想建立项目部署,必须确保清理所有缓存的输出,从面能够随时获得最新的部署. 要清理项目缓存的输出,发出以下命令: mvn clean ...

  10. 【推荐】全球最全面的Telegram组群频道的集合网站 持续收集中

    全球最全面的Telegram组群频道的集合网站 https://www.telegramgroup.org Telegram 组群频道分享 可搜索自己想找的组群频道 从小白到大神,一个 telegra ...