分布式锁

Redis

  • 通过setnx函数来进行原子性设置值(如果存在,设置失败)
  • 通过Watch、multi、exec、unwatch的命令组合来实现原子性和数据的准确性
  • 使用的spring boot中的 RedisTemplate

代码

package com.wyj.destributed.lock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;

public class RedisDistributedLock implements DistributedLock {

    private static Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class);

    // 默认过期时间(毫秒)
    private static final int DEFAULT_EXPIRE_MS = 60000;

    private RedisTemplate redisTemplate;

    // 锁的key值
    private String lockKey;

    // 锁的过期时间间隔
    private int expireMS;

    private ReentrantLock lock;

    // 锁定成功的值
    private String lockedSuccessValue;

    public RedisDistributedLock(RedisTemplate redisTemplate, String lockKey) {
        this(redisTemplate, lockKey, DEFAULT_EXPIRE_MS);
    }

    public RedisDistributedLock(RedisTemplate redisTemplate, String lockKey, int expireMS) {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey;
        this.expireMS = expireMS;
        lock = new ReentrantLock();
    }

    @Override
    public void lock() {
        // 使用lock的原因:主要为了防止同一个进程间的冲突,减少访问redis的次数
        lock.lock();
        try {
            for (; ; ) {
                if (tryAcquire()) return;
            }
        } finally {
            lock.unlock();
        }
    }

    private boolean tryAcquire() {
        // 生成锁的value
        long expires = System.currentTimeMillis() + expireMS;
        String lockValue = generateLockKey(expires);

        // 原子操作,设置值,如果存在,则设置失败;否则,设置成功
        if (setNx(lockValue)) {
            handleLockSuccessful(lockValue);
            return true;
        }
        try {
            // 检测是否过期
            // 通过redis事务,来保证操作的原子性
            // 通过监控来保证数据的准确性(具体可以查阅 Redis事务以及WATCH)
            return (boolean) redisTemplate.execute(new SessionCallback<Boolean>() {
                @Override
                public Boolean execute(RedisOperations operations) throws DataAccessException {
                    // 开启监控
                    operations.watch(lockKey);
                    // 获取当前value
                    String currentValue = (String) operations.boundValueOps(lockKey).get();
                    // 解析得到过期时间
                    long currentExpires = getCurrentExpires(currentValue);
                    // 判断是否过期
                    if (currentValue != null && currentExpires < System.currentTimeMillis()) {
                        // 开启事务
                        operations.multi();
                        operations.boundValueOps(lockKey).getAndSet(lockValue);
                        // 执行
                        List<String> exec = operations.exec();
                        // 判断是否执行成功
                        if (exec != null && exec.size() > 0 && exec.get(0).equals(currentValue)) {
                            handleLockSuccessful(lockValue);
                            return true;
                        }
                    } else {
                        // 取消监控
                        operations.unwatch();
                    }
                    return false;
                }
            });
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public void unlock() {
        try {
            if (lockedSuccessValue != null) {
                // 通过redis事务,来保证操作的原子性
                // 通过监控来保证数据的准确性(具体可以查阅 Redis事务以及WATCH)
                redisTemplate.execute(new SessionCallback<Boolean>() {
                    @Override
                    public Boolean execute(RedisOperations operations) throws DataAccessException {
                        operations.watch(lockKey);
                        String currentValue = (String) operations.boundValueOps(lockKey).get();
                        if (lockedSuccessValue.equals(currentValue)) {
                            operations.multi();
                            operations.delete(lockKey);
                            operations.exec();
                        } else {
                            operations.unwatch();
                        }
                        return null;
                    }
                });
            }
        } catch (Exception e) {
            logger.error("{} unlock error!", lockKey, e);
        }
    }

    // 锁定成功
    private void handleLockSuccessful(String lockValue) {
        lockedSuccessValue = lockValue;
    }

    // 生成锁定的value
    private String generateLockKey(long expires) {
        // 使用UUID,主要用来防止多个进程的时间点相同,导致的问题
        return expires + ":" + UUID.randomUUID().toString();
    }

    // 根据锁定的value获取当前的过期时间
    private static long getCurrentExpires(String currentValue) {
        if (currentValue == null) {
            return 0;
        }
        int i = currentValue.indexOf(":");
        return Long.parseLong(currentValue.substring(0, i));
    }

    // 设置value
    private boolean setNx(final String value) {
        return redisTemplate.opsForValue().setIfAbsent(lockKey, value);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值