SpringSchedule - 应用(关闭订单V2版)

那我们现在开始来写一个分布式锁,那这个方法叫就叫V2,第二个版本。

    /**
     * 可能出现死锁,虽然在执行close的时候有防死锁,但是还是会出现,继续演进V3
     */
//    @Scheduled(cron="0 */1 * * * ?")//每1分钟(每个1分钟的整数倍)
    public void closeOrderTaskV2() throws InterruptedException {
        long lockTimeout = Long.parseLong(PropertiesUtil
		.getProperty("lock.timeout","5000"));//锁5秒有效期
        //这个时间如何用呢,看下面。和时间戳结合起来用。
        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);
        }
    }

我们这个分布式锁要锁多久,lockTimeout,锁的超时时间,实际生产环境肯定不能设置成这么长时间了,我们分布式锁就锁5秒,那么这个时间怎么用呢,我们要配合时间戳一起来使用,RedisShardedPoolUtil我们封装了一个setnx,这个方法和set方法差不多。

  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;
    }

  // 也就是我们要set这个锁,如果锁的key不存在的话,他才会设置成功,我们声明锁的一个key
  public interface REDIS_LOCK{
        String CLOSE_ORDER_TASK_LOCK = "CLOSE_ORDER_TASK_LOCK"; // 关闭订单分布式锁
    }

把现在的时间转换成字符串,现在的毫秒数,再加上lockTimeout,现在的lockTime是50秒,如果设置成功了,就代表我获取这个锁了,如果没有获取锁,就打印一个日志,没有获得分布式锁,获取锁之后我们要怎么做,我们在下面声明一个方法,这个方法是什么呢,如果我在这里面直接调用closeOrder,但是他有一个问题,这个锁并没有释放,也就是说我们设置的这个锁呢,是没有有效期的,我们拿到的是永久,也就是第一次启动定时任务的时候,设置上锁,那以后再执行到这里的时候呢,这里面拿到的肯定是0,因为这个key已经存在,setnx返回的肯定是失败,那我们这里面就存在了问题,那我们现在来封装一下,我们要把这个锁设置一个有效期,那我们写成private就OK了,因为只在内部调用。

    private void closeOrder(String lockName){
//        expire命令用于给该锁设定一个过期时间,用于防止线程crash,导致锁一直有效,从而导致死锁。
        RedisShardedPoolUtil.expire(lockName,50);//有效期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(lockName);//释放锁
        log.info("释放{},ThreadName:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,
		Thread.currentThread().getName());
        log.info("=============================");
    }

这里面传一个lockName,锁的名字,我们要调用expire方法,把这个key要设置一个有效期,50秒,有效期50秒,防止死锁,也就是我肯定要把这个锁设置一个有效期的,那即使我们现在设置50秒,也是OK的,当然线上的时候我们要把它改成5秒,这里面也是因为5秒太短,我们还看不到那个锁,可能就过期了,我在关闭订单获取锁之后,设置一个有效期,在这个锁失效的时候呢,这个锁也就释放了,打印日志,当前线程的名字,代表哪个线程获取了这个锁,然后把这个时间拿过来,既然我们执行完了关闭完了订单,即使我们没有超过50秒,订单十分少,那么我们也不能让他等待,我们要及时的释放锁,着呢吗释放呢,我们直接来删除就可以了,把这个lock进行一个删除,也就是我们要主动释放这个分布式锁,我们主要是关注分布式锁,这个逻辑执不执行都OK,我们分析一下,首先TOMCAT1里面这个定时任务开始启动,然后他获取到了分布式锁,又释放掉了,TOMCAT2启动,但是他没有获取分布式锁,然后这个定时任务就结束了,也就是说在调用tast的时候,直接走到else里边,我们在执行分布式锁的时候,可以看到,设置完分布式锁,然后就设置了他的有效期,然后立刻就删除了,我们看一下redis里面,REDIS也是分布式的,这次通过一致性算法,存到了REDIS里面,6379的端口,那我们看一下他的时间,看到他现在是-1。

这个时候设置有效期,这里主要是为了防止死锁,我们的锁肯定要有一个有效期的,redis自动来释放他,因为我们在TOMCAT2里面设置好了,所以这里返回值是0,失败,可以看到现在都是空的。

因为TOMCAT2已经把lock锁删除掉,也就是TOMCAT2获取了分布式锁,我们看一下现在V2版本有什么问题,这里边要说的是,当我们setnx成功之后,这个lock已经存到redis里面了,但是我们的lock并没有有效期,然后tomcat就重启了,关闭了,tomcat2也重启关闭了,而这个时候我们在分布式的环境下,这个分布式锁就发生了死锁,这个锁根本就不会释放,因为还没有走到expire这个方法里面,前提是说,也就是setnx已经执行完成,这样我们这个版本的分布式锁就存在了死锁的问题,虽然我们在closeOrder里面,这里面设置了有效期,防止死锁,但是在一定条件下,还是会发生死锁的,那我们有一种折中的方案,关闭tomcat有两种方式,一种我们找到tomcat进程,来对他进行一个kill,这种是直接把进程关掉了,那么还有一种温柔的方式,就是调用tomcat的shutdown方法,那如果调用shutdown方法的话,我们还有一种方式来解决他,在最上边我们声明一个方法,delLock方法,删除锁,我们把删除锁的代码拿过来,放到这里边然后我们要加一个注解,@PreDestroy,这个注解是干什么用的呢,也就是说当我们没有使用kill进程的方式,来关闭tomcat,也就是温柔的关闭tomcat的时候,使用tomcat的shutdown命令,关闭tomcat,那tomcat容器会调用predestory,也就是说在毁灭之前,再调用这个方法,那么这里面就会执行了,那同样的也能起到这个效果,但是这种方式也有弊端,如果我这里关闭1千个锁和1万个锁,那我们在shutdown的时候呢,这个时间会非常长,这里面用了一个极限的思维,要关闭的东西非常的多,如果我们直接kill掉tomcat的进程,那这个方法根本不会执行,也就没有用武之地了,现在来看虽然V2做了分布式锁,但是在防死锁方面,是有问题,在一定情况下,就是刚刚说的,还是防不住死锁的,所以我们分布式锁,继续演进,我们来形成V3版本的分布式锁。

发布了909 篇原创文章 · 获赞 1770 · 访问量 87万+

猜你喜欢

转载自blog.csdn.net/Dream_Weave/article/details/105290715
v2
今日推荐