使用Redis的分布式锁来在Tomcat集群环境下保证使用一个Tomcat服务来处理业务。
首先来使用原生的Redis操作来实现Redis分布式锁和防止死锁。
Redis的setnx()方法是原子性操作,调用setnx()方法时,先判断key是否为空,如果为空在set值,如果不为空则返回值为0。通过setnx()来获取Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK的值,获取到值后 在进行业务处理。如果没有获取到值,继续判断,判断时间戳,看是否可以重置并获取到锁,通过get()方法获取值,在判断该值是否过期了,如果过期则再将Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK的值重置。getset()方法表示先获取在设置值。如果没有获取到值,则说明当前的锁没有人拿到,则就可以进行后面的业务操作。如果获取到值并且将值重置,这段时间内可能会被别的服务器获取到锁,所以要进行判断getSetResult != null && StringUtils.equals(lockValueStr,getSe。tResult。条件成立表示这个锁当前是没有人获取的,则进行业务处理,真的的获取到了锁。
public void closeOrderTaskV3(){
log.info("关闭订单定时任务启动");
long lockTimeout = Long.parseLong(PropertiesUtil.getProperty("lock.timeout","5000"));
Long setnxResult = RedisShardedPoolUtil.setnx(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,String.valueOf(System.currentTimeMillis()+lockTimeout));
if(setnxResult != null && setnxResult.intValue() == 1){
closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}else{
//未获取到锁,继续判断,判断时间戳,看是否可以重置并获取到锁
String lockValueStr = RedisShardedPoolUtil.get(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
if(lockValueStr != null && System.currentTimeMillis() > Long.parseLong(lockValueStr)){
String getSetResult = RedisShardedPoolUtil.getSet(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,String.valueOf(System.currentTimeMillis()+lockTimeout));
//再次用当前时间戳getset。
//返回给定的key的旧值,->旧值判断,是否可以获取锁
//当key没有旧值时,即key不存在时,返回nil ->获取锁
//这里我们set了一个新的value值,获取旧的值。
if(getSetResult == null || (getSetResult != null && StringUtils.equals(lockValueStr,getSetResult))){
//真正获取到锁
closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}else{
log.info("没有获取到分布式锁:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}
}else{
log.info("没有获取到分布式锁:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}
}
log.info("关闭订单定时任务结束");
}
private void closeOrder(String lockName){
RedisShardedPoolUtil.expire(lockName,5);//有效期50秒,防止死锁
log.info("获取{},ThreadName:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,Thread.currentThread().getName());
int hour = Integer.parseInt(PropertiesUtil.getProperty("close.order.task.time.hour","2"));
iOrderService.closeOrder(hour);
RedisShardedPoolUtil.del(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
log.info("释放{},ThreadName:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,Thread.currentThread().getName());
log.info("===============================");
}
RedisShardedPoolUtil.expire(lockName,5);有效期5秒,防止死锁,并且当业务处理完后使用 RedisShardedPoolUtil.del(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK)来释放锁。减少了锁的占用时间。
再来通过Redisson来实现Redis的分布式锁。首先是Redisson的配置。
@Component
@Slf4j
public class RedissonManager {
private Config config = new Config();
private Redisson redisson = null;
public Redisson getRedisson() {
return redisson;
}
private static String redis1Ip = PropertiesUtil.getProperty("redis1.ip");
private static Integer redis1Port = Integer.parseInt(PropertiesUtil.getProperty("redis1.port"));
private static String redis2Ip = PropertiesUtil.getProperty("redis2.ip");
private static Integer redis2Port = Integer.parseInt(PropertiesUtil.getProperty("redis2.port"));
@PostConstruct
private void init(){
try {
config.useSingleServer().setAddress(new StringBuilder().append(redis1Ip).append(":").append(redis1Port).toString());
redisson = (Redisson) Redisson.create(config);
log.info("初始化Redisson结束");
} catch (Exception e) {
log.error("redisson init error",e);
}
}
}
在来看通过Redisson升级的版本。
@Scheduled(cron="0 */1 * * * ?")
public void closeOrderTaskV4(){
RLock lock = redissonManager.getRedisson().getLock(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
boolean getLock = false;
try {
if(getLock = lock.tryLock(0,50, TimeUnit.SECONDS)){
log.info("Redisson获取到分布式锁:{},ThreadName:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,Thread.currentThread().getName());
int hour = Integer.parseInt(PropertiesUtil.getProperty("close.order.task.time.hour","2"));
// iOrderService.closeOrder(hour);
}else{
log.info("Redisson没有获取到分布式锁:{},ThreadName:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,Thread.currentThread().getName());
}
} catch (InterruptedException e) {
log.error("Redisson分布式锁获取异常",e);
} finally {
if(!getLock){
return;
}
lock.unlock();
log.info("Redisson分布式锁释放锁");
}
}
这里的lock.tryLock(0,50, TimeUnit.SECONDS)第一个参数为等待时间,第二个参数为锁的有效时间为50s,第三个为单位为秒。这里的第一个参数等待时间必须设置为0,否则会出现两个Tomcat都获取到锁。