Spring Schedule+Redis分布式锁实现分布式任务调度(难点)

解决问题:

redis分布式锁可以解决超卖的现象

Spring Schedule解决定时关单的问题

1、redis分布式锁流程图

出现的问题:这种情况的当一个线程拿到了锁,但是还没有释放,(就是将key,value设置到redis里面成功,但是没有删除)这时将tomcat都关闭,以后再启动项目,这个锁就是死锁,永远不会释放了。

解决上面的问题,可以在CloseOrderTask类里面写这方法:

    @PreDestroy
    public void delLock(){
        RedisShardedPoolUtil.del(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);

    }

但当锁比较多的时候,关闭的时间就非常长,如果直接kill掉tomacat的进程,上面的方法就不会执行,故产生下面升级版的方法解决以上问题

2、redis分布式锁升级版流程图

升级版的就解决了上面的出现死锁的问题。

1、设置一个lockTimeout 的时间,开启后多少时间后进行关单的时间

2、获取分布式锁,将key,value通过setnx设置到redis里面,因为key是固定的,如key在redis里面存在是不能设置的,必须等它被删除后

扫描二维码关注公众号,回复: 8892437 查看本文章

3、expire设置锁的有效期

备注:当一个线程拿到了锁,就是在redis里面已经设置了,第二线程是拿不到锁的(就是以key,value设置在redis里面),必须等设置的有效期过了(expire),将设置在redis里面的key,value删除掉,第二个线程才能拿到锁(设置key,value在redis里面)

1、redis分布式锁

第一步:在CloseOrderTask类写一个方法
 

package com.mmall.task;

import com.mmall.common.Const;
import com.mmall.common.RedissonManager;
import com.mmall.service.IOrderService;
import com.mmall.util.PropertiesUtil;
import com.mmall.util.RedisShardedPoolUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class CloseOrderTask {

    @Autowired
    private IOrderService iOrderService;

    @Autowired
    private RedissonManager redissonManager;

    @PreDestroy
    public void delLock(){
        RedisShardedPoolUtil.del(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);

    }


//    @Scheduled(cron="0 */1 * * * ?")
    public void closeOrderTaskV2(){
        log.info("关闭订单定时任务启动");
        long lockTimeout = Long.parseLong(PropertiesUtil.getProperty("lock.timeout","5000"));
//key为自定义的字符串,value为当前时间加设置多久关闭的时间
        Long setnxResult = RedisShardedPoolUtil.setnx(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,String.valueOf(System.currentTimeMillis()+lockTimeout));
        if(setnxResult != null && setnxResult.intValue() == 1){
            //如果返回值是1,代表设置成功,获取锁
            closeOrder(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类里面写一个setnx方法(表示如果锁的key不存在,才会设置成功)

    public static Long setnx(String key,String value){
        ShardedJedis jedis = null;
        Long result = null;

        try {
            jedis = RedisShardedPool.getJedis();
            result = jedis.setnx(key,value);
        } catch (Exception e) {
            log.error("setnx key:{} value:{} error",key,value,e);
            RedisShardedPool.returnBrokenResource(jedis);
            return result;
        }
        RedisShardedPool.returnResource(jedis);
        return result;
    }

2、redis分布式锁升级版(分布式锁双重防死锁)

将时间戳可以利用起来了

如果没有获取到锁,在redis里面进行get锁的key,并判断value的的值和当前时间进行对比,如存在,获取到redis里面旧锁的值,并进行判断,如果为null或从redis里面进行get锁的key得到的值和getset得到的值是一样的话,表示我们真正获取到新锁,并且set到了redis里面。

第一步:在CloseOrderTask类写一个方法

    @Scheduled(cron="0 */1 * * * ?")
    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)){
//重新设置锁,并拿到旧锁的值,getset具有原子性
                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("关闭订单定时任务结束");
    }
发布了241 篇原创文章 · 获赞 145 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/Richard_666/article/details/103578977