基于Redis实现分布式定时任务调度
项目开发过程中,难免会有许多定时任务的需求进来。如果项目中还没有引入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实现分布式定时任务调度的更多相关文章
- 基于redis的分布式锁(转)
基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...
- 基于redis的分布式锁(不适合用于生产环境)
基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...
- redis系列:基于redis的分布式锁
一.介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...
- 分布式定时任务调度系统技术解决方案(xxl-job、Elastic-job、Saturn)
1.业务场景 保险人管系统每月工资结算,平安有150万代理人,如何快速的进行工资结算(数据运算型) 保险短信开门红/电商双十一 1000w+短信发送(短时汇聚型) 工作中业务场景非常多,所涉及到的场景 ...
- 基于Redis的分布式锁设计
前言 基于Redis的分布式锁实现,原理很简单嘛:检测一下Key是否存在,不存在则Set Key,加锁成功,存在则加锁失败.对吗?这么简单吗? 如果你真这么想,那么你真的需要好好听我讲一下了.接下来, ...
- 基于redis 实现分布式锁的方案
在电商项目中,经常有秒杀这样的活动促销,在并发访问下,很容易出现上述问题.如果在库存操作上,加锁就可以避免库存卖超的问题.分布式锁使分布式系统之间同步访问共享资源的一种方式 基于redis实现分布式锁 ...
- 基于redis的分布式锁
<?php /** * 基于redis的分布式锁 * * 参考开源代码: * http://nleach.com/post/31299575840/redis-mutex-in-php * * ...
- 基于Redis的分布式锁真的安全吗?
说明: 我前段时间写了一篇用consul实现分布式锁,感觉理解的也不是很好,直到我看到了这2篇写分布式锁的讨论,真的是很佩服作者严谨的态度, 把这种分布式锁研究的这么透彻,作者这种技术态度真的值得我好 ...
- 基于 Redis 的分布式锁
前言 分布式锁在分布式应用中应用广泛,想要搞懂一个新事物首先得了解它的由来,这样才能更加的理解甚至可以举一反三. 首先谈到分布式锁自然也就联想到分布式应用. 在我们将应用拆分为分布式应用之前的单机系统 ...
随机推荐
- A Pattern Language for Parallel Programming
The pattern language is organized into four design spaces. Generally one starts at the top in the F ...
- ubuntu 换源 aliyun
sudo sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list sudo sed -i 's/securit ...
- Spring Boot 2.0 快速集成整合消息中间件 Kafka
欢迎关注个人微信公众号: 小哈学Java, 每日推送 Java 领域干货文章,关注即免费无套路附送 100G 海量学习.面试资源哟!! 个人网站: https://www.exception.site ...
- VS2019打开旧项目导致引用失效的解决方案
用VS2019打开VS2015创建的MVC项目时所有引用全部失效: 解决方案: 打开项目的csproj文件,删除 Target节点,在重新打开项目. <Target Name="Ens ...
- Entity Framework 导航属性(2)
1.学校 [Table("School")] public partial class School { public School() { Students = new List ...
- Android Studio 3.5+ 使用androidx的recyclerView
一 File->project structure->Dependencies: 点击All Dependencies处的加号,选择Library Dependency: 在step1处输 ...
- selenium和AutoIt工具辅助下载和上传
上传 根据AutoIt Windows Info 所识别到的控件信息打开SciTE Script Editor编辑器,编写脚本. ;ControlFocus("title",&qu ...
- Python列表操作与深浅拷贝(7)——列表深浅拷贝、删除、反转、排序
列表复制 浅拷贝:简单类型元素全复制,引用类型元素只复制引用 L1 = [3,2,1,[4,5,6],8,'abc'] L1 [3, 2, 1, [4, 5, 6], 8, 'abc'] L2 = L ...
- 201871010106-丁宣元 《面向对象程序设计(java)》第十六周学习总结
201871010106-丁宣元 <面向对象程序设计(java)>第十六周学习总结 正文开头: 项目 内容 这个作业属于哪个课程 https://home.cnblogs.com/u/nw ...
- 201871010132--张潇潇--《面向对象程序设计(java)》第十二周学习总结
博文正文开头格式:(2分) 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.co ...