setNX遇到的问题

先说一次遇到的问题

关于redis分布式锁的问题,

先来一个遇到问题的代码

/**
错误示范
*/

    public boolean lock(final String lockKey) {

        for(String lock : locks) {
            if (lock.equals(lockKey)) {
                return true;
            }
        }

         Long i = jedisClient.execute(new JedisAction<Long>() {

             @Override
             public Long doAction(Jedis jedis) {
                //这个地方上锁
                 Long i = jedis.setnx(lockKey, "1");

                 if( i == null ) {
                     return 0L;
                 }

                 if(i == 1) {
                     //这个地方设置有效时间
                     jedis.expire(lockKey, LOCKKEY_EXPIRE_TIME);
                     locks.add(lockKey);

                     if(LOGGER.isInfoEnabled()) {
                         LOGGER.info("添加锁 [{}]",lockKey);
                     }
                 }
                 return i;
             }
         });

        return i == 1;

    }

以上代码的问题在于 把上锁和设置有效时间分开了,所以导致停服的时候正好上了锁,但是没有设置有效时间,所以一直有问题

 

那么redis应该如何正确有效的上锁呢

目前的setnx完全可以在一个方法里面实现有效时间和key

 


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPool;

/**
 * 基于redis setnx的 分布式锁 实, 前提是所有的锁都要有锁定时间.
 * 获取锁的时候,需要指定value,在unlock的时候,会根据value判断是否remove
 */
public class RedisLockUtil {
    private static final String LOCK_PREFIX = "LOCK";
    private static final Integer DEFAULT_LOCK_TIME = 10;// 默认锁定时间秒
    private static final Long DEFAULT_SLEEP_TIME = 100L;// 默认sleep时间,100毫秒
    private static Logger logger = LoggerFactory.getLogger(RedisLockUtil.class);

    /**
     * 获取缓存的value,随机值,使不同的锁value不同 (多服务器可以使用redis时间+客户端标识等)
     *
     * @return
     */
    public static String getLockValue() {
        int random = (int) ((Math.random() * 9 + 1) * 100000);
        long now = System.currentTimeMillis();
        return String.valueOf(now) + String.valueOf(random);
    }

    /**
     * 获取锁,如果失败,自动重试
     *
     * @param key
     * @param value
     * @return
     */
    public static boolean lock(JedisPool jedisPool, String key, String value) {
        return lock(jedisPool, key, value, DEFAULT_LOCK_TIME);
    }

    /**
     * 获取锁,如果失败,自动重试
     *
     * @param key
     * @param value
     * @param lockTime 获取成功后的锁定时间
     * @return
     */
    public static boolean lock(JedisPool jedisPool, String key, String value, int lockTime) {
        return lock(jedisPool, key, value, lockTime, true);
    }

    private static boolean lock(JedisPool jedisPool, String key, String value, int lockTime, boolean reTry) {
        return lock(jedisPool, key, value, lockTime, reTry, 0, false, 0);
    }

    /**
     * 获取锁,如果失败,直接返回false
     *
     * @param key
     * @param value
     * @return
     */
    public static boolean tryLock(JedisPool jedisPool, String key, String value) {
        return tryLock(jedisPool, key, value, DEFAULT_LOCK_TIME);
    }

    /**
     * 获取锁,如果失败,直接返回false
     *
     * @param key
     * @param value
     * @param lockTime 获取成功后的锁定时间
     * @return
     */
    public static boolean tryLock(JedisPool jedisPool, String key, String value, int lockTime) {
        return lock(jedisPool, key, value, lockTime, false);
    }

    /**
     * 尝试获取锁,如果获取失败,重试,直到成功或超出指定时间
     *
     * @param key
     * @param value
     * @param lockTime 获取成功后的锁定时间
     * @param timeOut  获取锁等待超时时间
     * @return
     */
    public static boolean tryLock(JedisPool jedisPool, String key, String value, int lockTime, long timeOutMillis) {
        return lock(jedisPool, key, value, lockTime, true, 0, true, timeOutMillis);
    }

    /**
     * 释放锁,key对应的value于参数value一致,才删除key
     *
     * @param key
     * @param value
     */
    public static boolean unlock(JedisPool jedisPool, String key, String value) {
        String fullKey = getFullKey(key);
        boolean success = JedisUtil.unlock(jedisPool, fullKey, value);
        if (success) {
            logger.info("unlock success ; key:" + key + ",value:" + value);
        } else {
            logger.info("unlock failed ; key:" + key + ",value:" + value);
        }
        return success;
    }

    /**
     * 获取锁
     *
     * @param key
     * @param value
     * @param lockTime      锁定时间
     * @param reTry         失败是否重试
     * @param curTryTime    当前尝试次数
     * @param needTimeOut   是否需要判断超时时间
     * @param timeOutMillis 尝试超时时间(毫秒)
     * @return
     */
    private static boolean lock(JedisPool jedisPool, String key, String value, int lockTime, boolean reTry, int curTryTime,
                                boolean needTimeOut, long timeOutMillis) {

        logger.info(Thread.currentThread().getName() + ",lock come in ; key:" + key + ",value:" + value
                + ",lockTime:" + lockTime + ",reTry:" + reTry + ",curTryTime:" + curTryTime + ",needTimeOut:"
                + needTimeOut + ",timeOutMillis:" + timeOutMillis);

        curTryTime++;
        String fullKey = getFullKey(key);

        // setnx 并设置超时时间
        boolean success = JedisUtil.setnx(jedisPool, fullKey, value, (long) lockTime * 1000);
        // 获取成功,直接返回
        if (success) {

            logger.info("lock success ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:"
                    + reTry + ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:"
                    + timeOutMillis);

            return true;
        }

        // 获取失败,不需要重试,直接返回
        if (!reTry) {
            logger.info("lock failed ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:"
                    + reTry + ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:"
                    + timeOutMillis);

            return false;
        }

        // 获取失败, 且已超时,返回
        if (needTimeOut && timeOutMillis <= 0) {
            logger.info("lock failed ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:"
                    + reTry + ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:"
                    + timeOutMillis);

            return false;
        }

        // 获取sleep时间
        long sleepMillis = getSleepMillis(needTimeOut, timeOutMillis);

        // sleep后重新获取锁
        sleep(sleepMillis);

        // 大于100次,打印warning日志
        if (curTryTime > 100) {
            logger.warn("lock warning ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:"
                    + reTry + ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:"
                    + timeOutMillis);

        }

        return lock(jedisPool, key, value, lockTime, reTry, curTryTime, needTimeOut, timeOutMillis);
    }

    private static long getSleepMillis(boolean needTimeOut, long timeOutMillis) {
        long sleepMillis = DEFAULT_SLEEP_TIME;
        if (needTimeOut) {
            timeOutMillis = timeOutMillis - DEFAULT_SLEEP_TIME;
            if (timeOutMillis < DEFAULT_SLEEP_TIME) {
                sleepMillis = timeOutMillis;
            }
        }
        return sleepMillis;
    }

    private static void sleep(long sleepMillis) {
        try {
            Thread.sleep(sleepMillis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static String getFullKey(String key) {
        return LOCK_PREFIX + ":" + key;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值