Redis分布式锁

先讲下流程:

1.在redis中设置一个锁setnx(lockkey , currenttime+timeout)。

2.返回1代表之前没有这个锁,那么就调用expire(lockkey)来重新设置超时时间,执行业务,接下来del(key),释放锁,最后结束。

3.返回0代表有这个锁,那么传统流程中就直接结束。优化后,调用get(lockkey)来获取这个锁的超时时间的value。

3.1接下来判断如果value不是空,并且已经超时的话,那么调用getset(lockkey,currenttime+timeout)方法,重新设置value值,同时会返回一个老的值,判断如果这个值为空,或者旧值和新值相等,那么代表这个锁没有变化,获取锁成功,调用第二步操作。

3.2如果条件不满足,那么结束任务。

Redis+Scheduled

模拟一个需求,用户提交订单后,若干时间后如果还没有支付,那么关闭这个订单。那么我们需要用定时任务做轮询,把超时的订单全部删除。

在分布式的情况下,多个程序,我只需要其中一个运行这个定时任务,不需要所有程序都做轮询,因此我们可以用到redis锁来管理。接下来写代码:

@Component
public class CloseOrderTask {

    @Scheduled(cron = "0 */1 * * * ?")
    public void closeOrder() throws InterruptedException {
        System.out.println("关闭订单定时任务启动");
        long timeout = Long.parseLong(PropertiesUtils.getProperty("lock.timeout", "5000"));
        Long setnxResult = RedisShardedPoolUtil.setnx("CLOSE_ORDER_TASK_LOCK" , String.valueOf(System.currentTimeMillis() + timeout));
        if (setnxResult != null && setnxResult.intValue() == 1){
            //设置成功,获取锁
            closeOrder("CLOSE_ORDER_TASK_LOCK");
        }else {
            System.out.println("没有获得锁");
        }
        System.out.println("关闭订单定时任务关闭");
    }

    private void closeOrder(String lockName) throws InterruptedException {
        //设置key的有效期50秒,防止死锁
        RedisShardedPoolUtil.expire(lockName , 50);
        System.out.println(Thread.currentThread().getName() + "获取到锁" + lockName);
        System.out.println("执行关闭订单操作");
        Thread.sleep(3000);
        RedisShardedPoolUtil.del(lockName);
        System.out.println(Thread.currentThread().getName() + "释放锁" + lockName);
    }
}
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) {
            e.printStackTrace();
            RedisShardedPool.returnBrokenResource(jedis);
            return result;
        }
        RedisShardedPool.returnResource(jedis);
        return result;
    }

每次请求都会去redis中找是否有锁,如果有的话就不做删除,如果没有的话就插入一条,并且设置超时时间。运行结果:

tomcat1:

tomcat2:

这样就实现了只有一个可以运行的目的,但是这里会有一个死锁的问题:假如程序运行到第9行,刚set进值,还没有设置超时时间的时候,程序崩掉了,那么这个锁就一直待在redis中了,那也就死锁了。 

这种情况我们可以在定时任务中加一个方法:

    @PreDestroy
    public void delLock(){
        RedisShardedPoolUtil.del("CLOSE_ORDER_TASK_LOCK");
    }

@PreDestroy在程序结束时运行,但是仅限于shutdown这种平和的方式,如果kill进程,这个也是没有用的,因此我们需要演进一下代码:

@Scheduled(cron = "0 */1 * * * ?")
    public void closeOrder2() throws InterruptedException {
        System.out.println("关闭订单定时任务启动");
        long timeout = Long.parseLong(PropertiesUtils.getProperty("lock.timeout", "5000"));
        Long setnxResult = RedisShardedPoolUtil.setnx("CLOSE_ORDER_TASK_LOCK" , String.valueOf(System.currentTimeMillis() + timeout));
        if (setnxResult != null && setnxResult.intValue() == 1){
            //设置成功,获取锁
            closeOrder("CLOSE_ORDER_TASK_LOCK");
        }else {
            //未获取到锁,继续判断时间戳,看是否可以重置并获取到锁
            String lockValueStr = RedisShardedPoolUtil.get("CLOSE_ORDER_TASK_LOCK");
            if (lockValueStr != null && System.currentTimeMillis() > Long.parseLong(lockValueStr)){
                String getSetResult = RedisShardedPoolUtil.getSet("CLOSE_ORDER_TASK_LOCK" , String.valueOf(System.currentTimeMillis() + timeout));
                //再次用当前时间戳getset
                //返回给定的key的旧值,通过判断旧值来决定是否可以获取锁
                if (lockValueStr == null || (lockValueStr != null && StringUtils.equals(lockValueStr , getSetResult))){
                    closeOrder("CLOSE_ORDER_TASK_LOCK");
                }else {
                    System.out.println(Thread.currentThread().getName() + "没有获得锁");
                }
            }else {
                System.out.println(Thread.currentThread().getName() + "没有获得锁");
            }
        }
        System.out.println("关闭订单定时任务关闭");
    }
    public static String getSet(String key , String value){
        ShardedJedis jedis = null;
        String result = null;
        try {
            jedis = RedisShardedPool.getJedis();
            result = jedis.getSet(key , value);
        } catch (Exception e) {
            e.printStackTrace();
            RedisShardedPool.returnBrokenResource(jedis);
            return result;
        }
        RedisShardedPool.returnResource(jedis);
        return result;
    }

其实通过代码就很清楚地看出流程了,里面用了多重防死锁,这是因为在分布式环境下,程序可能不止2台,那么所有的机器同时运行大概率会有类似问题,所以我们通过多重验证来判断是否获得锁。

 

 

发布了84 篇原创文章 · 获赞 23 · 访问量 1万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 像素格子 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览