Redis实现分布式锁

在集群等多服务器中经常要使用到同步处理一下业务,这时普通的事务是满足不要业务需求,需要分布式锁。分布式锁的实现方式有多种,如redis实现分布式锁,zookeeper实现分布式锁等,这篇先实现redis分布式锁。

实现原理

  1. 通过setnx(lock_timeout)实现,如果设置了锁返回1,已经有值没有设置成功返回0。
  2. 死锁问题:通过时间来判断是否过期,如果已经过期,获取到过期时间get(lockKey),然后getset(lock_timeout)判断是否和get相同,相同则证明已经加锁成功,因为可能会导致多个线程同时执行getset(lock_timeout)方法。这是可能导致多个线程都只需getset后,对于判断加锁成功的线程,再加expire(lockKey, LOCK_TIMEOUT, TimeUnit.MILLISECONDS)过期时间,防止多个线程同时叠加时间,导致锁时效时间翻倍。
  3. 针对集群服务器时间不一致问题,可以从调用redis的time()获取当前时间。

代码实现

分布式锁接口

DistributionLock.java

/**
 * 分布式锁
 */
public interface DistributionLock {

    //加锁成功 返回加锁时间
    public Long lock(String lockKey, String threadname);

    //解锁 需要更加加锁时间判断是否有权限
    public void unlock(String lockKey, long lockvalue, String threadname);
}

分布式锁Redis实现

RedisDistributionLock.java

/**
 * redis分布式锁
 */
public class RedisDistributionLock implements DistributionLock{

    private static final long LOCK_TIMEOUT = 60 * 1000; //加锁超时时间 单位毫秒 意味着加锁期间内执行完操作 如果未完成会有并发现象

    private static final Logger LOG = Logger.getLogger(RedisDistributionLock.class); //redis锁日志

    @SuppressWarnings("unchecked")
    private static RedisTemplate<Serializable, Serializable> redisTemplate = (RedisTemplate<Serializable, Serializable>) SpringContextHolder
            .getBean("redisTemplate");

    /**
     * 取到锁加锁 取不到锁一直等待直到获得锁
     */
    @Override
    public Long lock(String lockKey, String threadname) {
        LOG.info(threadname + "开始执行加锁");
        while (true) { //循环获取锁
            Long lock_timeout = System.currentTimeMillis() + LOCK_TIMEOUT + 1; //锁时间
            if (redisTemplate.execute(new RedisCallback<Boolean>() {
                //第一次执行加锁,使用setNX命令(详细看上文标注)
                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    //序列化方式                
                    JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
                    byte[] value = jdkSerializer.serialize(lock_timeout);
                    return connection.setNX(lockKey.getBytes(), value);
                }
            })) { //如果加锁成功
                LOG.info(threadname + "加锁成功1");
                redisTemplate.expire(lockKey, LOCK_TIMEOUT, TimeUnit.MILLISECONDS); //设置超时时间,释放内存
                return lock_timeout;
            }else {
                // redis里的时间
                Long currt_lock_timeout_Str = (Long) redisTemplate.opsForValue().get(lockKey); 
                 // 判断是否为空,不为空的情况下,说明已经失效(一定要有过期时间,不然会死锁)
                if (currt_lock_timeout_Str != null && currt_lock_timeout_Str < System.currentTimeMillis()) {
                    // 获取上一个锁到期时间
                    Long old_lock_timeout_Str = (Long) redisTemplate.opsForValue().getAndSet(lockKey, lock_timeout);
                    // 如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁
                    if (old_lock_timeout_Str != null && old_lock_timeout_Str.equals(currt_lock_timeout_Str)) {
                        LOG.info(threadname + "加锁成功2");
                        //设置超时时间,释放内存
                        redisTemplate.expire(lockKey, LOCK_TIMEOUT, TimeUnit.MILLISECONDS); 
                        return lock_timeout;//返回加锁时间
                    }
                }
            }

            try {
                LOG.info(threadname +  "等待加锁,睡眠100毫秒"); 
                TimeUnit.MILLISECONDS.sleep(100);//睡眠100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            } 
        }
    }

    @Override
    public void unlock(String lockKey, long lockvalue, String threadname) {
        LOG.info(threadname + "执行解锁=");//正常直接删除 如果异常关闭判断加锁会判断过期时间
        Long currt_lock_timeout_Str = (Long) redisTemplate.opsForValue().get(lockKey); // redis里的时间

        if (currt_lock_timeout_Str != null && currt_lock_timeout_Str == lockvalue) {//如果是加锁者 则删除锁 如果不是则等待自动过期 重新竞争加锁
            redisTemplate.delete(lockKey); //删除键
            LOG.info(threadname + "解锁成功-----------------");
        }
    }

}

多服务器采用获取redis时间,代替上面使用到的所有当前时间System.currentTimeMillis()。

public long currtTimeFromRedis(){ //获取redis当前时间
    return redisTemplate.execute(new RedisCallback<Long>() {
        @Override
        public Long doInRedis(RedisConnection connection) throws DataAccessException {
            return connection.time();
        }
    });
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值