Redis分布式锁Java实现

一、背景

在天猫、京东、苏宁等等电商网站上有很多秒杀活动,例如在某一个时刻抢购一个原价1999现在秒杀价只要999的手机时,会迎来一个用户请求的高峰期,可能会有几十万几百万的并发量,来抢这个手机,在高并发的情形下会对数据库服务器、文件服务器、应用服务器造成巨大的压力,严重时甚至宕机了。另一个问题是,秒杀的东西都是有量的,例如一款手机只有10台的量秒杀,那么,在高并发的情况下,成千上万条数据更新数据库(例如10台的量被人抢一台就会在数据集某些记录下减1),那这个时候的先后顺序是很乱的,很容易出现10台的量,抢到的人就不止10个这种严重的问题。

那么,这个高并发+限量的问题我们该如何去解决呢?使用任务队列和分布式锁,本节主要阐述基于redis的分布式锁实现思路。

二、设计思路

1、基本思路

主要用到的是redis函数setNX(),这个应该是实现分布式锁最主要的函数。首先是将某一任务标识名(lockKey)作为键存到redis里,并为其设个过期时间,如果是还有lockKey请求过来,先是通过setNX()看看是否能将lockKey插入到redis里,可以的话就返回true,不可以就返回false。

2、锁过期时间

为避免特殊原因导致获得的锁无法释放,在加锁成功后,通过redis函数expire()给锁赋予一个生存时间,超出生存时间锁会被自动释放。

三、实现

1、获取锁

/**
     * 获取redis锁
     *
     * @param jedisTemplate 没啥好说的
     * @param lockName 锁的唯一名称
     * @param lockTimeout 锁过期时间(单位:秒)
     * @param acquireTimeout 请求锁的超时时间 (单位:秒)
     * @param retryDuration 请求锁的重试间隔时间 (单位:毫秒)
     * @return
     */
    @Deprecated
    public static Boolean tryLock(final RedisTemplate jedisTemplate, final String lockName, final int lockTimeout, final int acquireTimeout, final long retryDuration) {
        if (isEmpty(lockName) || lockTimeout <= 0) {
            return false;
        }
        final String lockKey = lockName;
        String identifier = UUID.randomUUID().toString();
        Calendar atoCal = Calendar.getInstance();
        atoCal.add(Calendar.SECOND, acquireTimeout);
        Date atoTime = atoCal.getTime();

        log.info("Try to acquire the lock. lockKey={},acquireTimeout={}s,lockTimeout={}s", lockKey, acquireTimeout, lockTimeout);
        while (true) {
            //开始获取锁
            boolean acquiredLock = (boolean) jedisTemplate.execute(new RedisCallback<Boolean>() {
                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    return connection.setNX(jedisTemplate.getStringSerializer().serialize(lockKey), jedisTemplate.getStringSerializer().serialize(identifier));
                }
            });

            if (acquiredLock) {//成功获取锁后,设置锁的过期时间,并返回ID
                jedisTemplate.execute(new RedisCallback<Boolean>() {
                    @Override
                    public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                        return connection.expire(jedisTemplate.getStringSerializer().serialize(lockKey), lockTimeout);
                    }
                });
                log.info("acquired lock. lockKey={}", lockKey);
                return true;
            } else { //如果获取失败,则重试获取锁,或者直接返回null
                log.info("Retry to acquire the lock. lockKey={},acquireTimeout={}s,lockTimeout={}s", lockKey, acquireTimeout, lockTimeout);
                if (acquireTimeout < 0)
                    return false;
                else {
                    try {
                        log.info("wait 1000 milliseconds before retry. lockKey={}", lockKey);
                        Thread.sleep(retryDuration);
                    } catch (InterruptedException ex) {
                    }
                }
                if (new Date().after(atoTime)) {
                    break;
                }
            }
        }
        return false;
    }

2、释放锁

public static void unLock(final RedisTemplate jedisTemplate, final String lockName) {
        if (isEmpty(lockName)) {
            return;
        }
        final String lockKey = lockName;
        jedisTemplate.execute(new RedisCallback<Void>() {
            @Override
            public Void doInRedis(RedisConnection connection) throws DataAccessException {
                connection.del(jedisTemplate.getStringSerializer().serialize(lockKey));
                return null;
            }
        });
    }

3、检查锁状态

//检查锁状态,锁住返回true,否则false
    public static Boolean checkWhetherLockExists(final RedisTemplate jedisTemplate, final String lockName) {
        if (isEmpty(lockName)) {
            return false;
        }
        final String lockKey = lockName;
        boolean lockExists = (boolean) jedisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] bytes = connection.get(jedisTemplate.getStringSerializer().serialize(lockKey));
                if(null != bytes){
                    return true;
                } else {
                    return false;
                }
            }
        });

        return lockExists;
    }

猜你喜欢

转载自blog.csdn.net/hehuanchun0311/article/details/79863719