描述:
不管用不用动态执行,单机服务都是没有问题的,但是如果服务是集群模式下,那么一个任务在每台机器都会执行一次,这肯定不是我们需要的,我们要实现的是整个集群每次只有一个任务执行成功,但是spring对此并没有很好的支持,所以我们需要有一个统一的数据获取处,参考网上决定利用rides的一致性来实现,开始就参考网上利用setnx命令实现的,但是还是感觉不太完善,就去抛开定时任务,直接实现Redis分布式锁应该是最合适的,在定时任务业务处加锁,业务执行完解锁即可。
此事例经过多个网站参考并自己实际操作是不存在任何问题,完全可以正常使用。
实现:
import redis.clients.jedis.JedisCluster;
import java.util.Collections;
/**
*
* redis实现分布式锁,并释放锁
*/
public class RedisTool {
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 Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间,毫秒
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(JedisCluster jedis, String lockKey, String requestId, int expireTime) {
/*
*设置锁并设置超时时间,lockKey表示Redis key,requestId表示Redis value,SET_IF_NOT_EXIST表示有值不进行设置(NX),
* SET_WITH_EXPIRE_TIME表示是否设置超时时间(PX)设置,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 Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(JedisCluster jedis, String lockKey, String requestId) {
/*
* 利用Lua脚本代码,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)
* eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令,这样就不会出现上一个代码执行完挂了后边的出现问题,还是一致性的解决
* */
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.rails.travel.conf.task.myschedule;
import com.rails.travel.common.FrameSpringBeanUtil;
import com.rails.travel.common.RedisTool;
import redis.clients.jedis.JedisCluster;
import java.util.Date;
public class MyRunnable3 implements Runnable {
//因为是线程所以无法注入bean对象,只能通过这种方法去获取bean对象
private JedisCluster jedisCluster = FrameSpringBeanUtil.getBean(JedisCluster.class);
@Override
public void run() {
//加锁,调用上边的工具类,参数具体看工具类说明
boolean lock = RedisTool.tryGetDistributedLock(jedisCluster, MyRunnable3.class.toString(), MyRunnable3.class.toString(), 3000);
//加锁成功则执行业务,不成功则不执行业务
if (lock){
//此处业务代码
System.out.print("业务执行了3" + new Date());
//业务执行完成解锁,如果在执行业务中或者是在解锁出现了异常,宕机等,锁会根据加锁时的key过期时间自己消除
RedisTool.releaseDistributedLock(jedisCluster,MyRunnable3.class.toString(), MyRunnable3.class.toString());
}
}
}
说明:
加锁解锁工具类直接是查看博客和官网等获取用的,博客必须看,可以完全明白实现原理及现在网上的错误做法的问题所在,我开始用的就是博客中提到的第一种错误方法
Redis中文命令参考中在set命令最后也给出了同样的解决方式:http://doc.redisfans.com/string/set.html
Redis官网关于分布式锁中也是这种方式包括推荐的GitHub实现也是这种方式:https://redis.io/topics/distlock