使用Redis实现多节点任务调度
1. 目的
目前中有部分功能需要定时更新或者推送数据,但正式环境存在多个server运行,会导致定时任务在多台服务器或者多个server同时运行。
解决思路1:可以使用一台固定的服务器的一个节点单独部署定时任务的程序。
解决思路2:多节点调度时,使用Redis锁来实现只有一个节点单独执行。
结论:只需要保证在同一时间只有一个节点执行调度程序。
2. redis实现方式
2.1 引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2.2 配置redis地址、端口、密码
使用本地的redis,默认无密码
2.3 实现redis工具类
package com.hrss.common.common.util; import com.google.common.base.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; public class RedisLock { private final Logger log = LoggerFactory.getLogger(this.getClass()); private RedisTemplate redisTemplate; private String key; // 分布式锁 private String value; //单位:秒 默认60秒 private int expireTime = 60; /** * @param redisTemplate * @param key * @param expireTime 过期时间 */ public RedisLock(RedisTemplate redisTemplate, String key, int expireTime){ this.redisTemplate = redisTemplate; this.key = key; this.expireTime=expireTime; this.value = UUID.randomUUID().toString(); } public RedisLock(RedisTemplate redisTemplate, String key, int expireTime,String value){ this.redisTemplate = redisTemplate; this.key = key; this.expireTime=expireTime; this.value = value; } public RedisLock(RedisTemplate redisTemplate, String key){ this.redisTemplate = redisTemplate; this.key = key; } /** * 设置redis 分布式锁 * @param acquireTimeout // 设置锁超期时间 * @return */ public String acquireDistributedLockWithTimeout(long acquireTimeout) { String lockValue = this.value; String lockKey = this.key; long acquireTime = System.currentTimeMillis() + acquireTimeout; ValueOperations ops = this.redisTemplate.opsForValue(); while(acquireTime > System.currentTimeMillis()) { //是否存在相同的锁 boolean locked = ops.setIfAbsent(lockKey, lockValue); // 执行第一条服务挂掉后服务挂掉。expire方法无法执行,key永久存在 if (locked) { log.warn("值{}",ops.get(this.key)); this.redisTemplate.expire(lockKey, this.expireTime, TimeUnit.SECONDS); log.warn("redis"+key+"设置成功"); return lockValue; } try { TimeUnit.MILLISECONDS.sleep(1L); } catch (InterruptedException var12) { Thread.currentThread().interrupt(); } } return null; } public void putSimpleValue() { ValueOperations ops = this.redisTemplate.opsForValue(); ops.set(this.key,this.value,this.expireTime,TimeUnit.SECONDS); } public Object getSimpleValue() { ValueOperations ops = this.redisTemplate.opsForValue(); return ops.get(this.key); } /** * 释放redis锁 * @return */ public void releaseDistributedLock() { String lockKey = this.key; List<Object> result = null; ValueOperations ops = this.redisTemplate.opsForValue(); do { if (!Objects.equal(this.value, ops.get(lockKey))) { break; } this.redisTemplate.delete(lockKey); log.info("redis释放锁"); } while(result == null); } }
2.4 在任务调度中使用Redis锁
创建RedisTemplate对象、创建序列化对象、设置 key 和 hashKey 采⽤ String 的序列化⽅式
@Resource private RedisTemplate redisTemplate; @Autowired() public void setRedisTemplate(RedisTemplate redisTemplate) { RedisSerializer stringSerializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(stringSerializer); redisTemplate.setValueSerializer(stringSerializer); redisTemplate.setHashKeySerializer(stringSerializer); redisTemplate.setHashValueSerializer(stringSerializer);
配置和使用key和过期时间