redis分布式锁实现

一. redis单机部署时原生实现的分布式锁

满足:

  1. 互斥性。在任意时刻,只有一个客户端能持有锁。
  2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  3. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

缺陷:

  1. 不可重入
  2. 不能满足多个Redis实例的情况
  3. 获取锁没有循环获取,一次不成功就返回了。
/**
 * @Author: zqf
 * @Description: 同一时间只能有一个线程可以获取到锁,其他没有获取到锁的线程直接丢掉时(比如redis避免缓存击穿时,数据失效异步更  新数据)
 * @Date 2021/8/25 10:08
 */
public class DistributedLock {

    /**
     * 释放锁lua脚本,原子操作:lua脚本是作为一个整体执行的,所以中间不会被其他命令插入。
     */
    private final static String RELEASE_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    private static final Long RELEASE_SUCCESS = 1L;

    /**
     *
     * @param lockId 锁
     * @param requestId 请求标识
     * @param seconds 过期时间
     * @return
     */
    public static boolean getLock(RedisTemplate redisTemplate, String lockId, String requestId, long seconds) {
        Boolean success = redisTemplate.opsForValue().setIfAbsent(lockId, requestId, seconds, TimeUnit.SECONDS);
        return success != null && success;
    }

    /**
     * 释放锁
     * @param redisTemplate
     * @param lockId 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseLock(RedisTemplate redisTemplate, String lockId, String requestId) {
        // 指定 lua 脚本,并且指定返回值类型
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT,Long.class);
        // 参数一:redisScript,参数二:key列表,参数三:arg(可多个)
        Long result = (Long) redisTemplate.execute(redisScript, Collections.singletonList(lockId), requestId);
        return RELEASE_SUCCESS.equals(result);
    }
}

二.jedis实现分布式锁

public class RedisLockUtils {

    private static final String LOCK_KEY = "HT:LOCK:";

    /**
     * 分布式锁
     *
     * @param jedis
     * @param lockName
     * @param acquireTimeout
     * @param lockTimeout
     * @return
     */
    public static boolean lock(Jedis jedis, String lockName, long acquireTimeout, long lockTimeout) {
        String realLockName = LOCK_KEY + lockName;
        String theadId = String.valueOf(Thread.currentThread().getId());
        if (acquireTimeout == 0) {
            //如未设置锁获取超时时间默认为10秒
            acquireTimeout = 10000l;
        }
        if (lockTimeout == 0) {
            //如未设置锁超时时间默认未60秒
            lockTimeout = 60000l;
        }
        long lockTimeoutMin = (lockTimeout / 1000l);
        long acquireEndTime = System.currentTimeMillis() + acquireTimeout;
        //防止未设置时间产生死锁,当 key 不存在时,返回 -2 ,当 key 存在但没有设置剩余生存时间时,返回 -1 。
        // 在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。
        if (jedis.ttl(realLockName) == -1) {
            jedis.expire(realLockName, lockTimeoutMin);
        }
        while (System.currentTimeMillis() < acquireEndTime) {
            //加锁成功
            if (theadId.equals(jedis.get(realLockName)) || jedis.setnx(realLockName, theadId) == 1) {
                log.info("线程{}获取锁成功", theadId);
                jedis.expire(realLockName, lockTimeoutMin);
                return true;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 当前线程 释放锁
     *
     * @param jedis
     * @param lockName
     * @return
     */
    public static boolean releaseLock(Jedis jedis, String lockName) {
        String realLockName = LOCK_KEY + lockName;
        String theadId = String.valueOf(Thread.currentThread().getId());
        boolean isRelease = false;
        while (true) {
            //jedis事务操作   监控realLockName  如果其中值发生变化tx.exec事务不会提交(注jedis中的事务不具备原子性,其中一个命令有错误,另外的仍然执行)
            jedis.watch(realLockName);
            if (theadId.equals(jedis.get(realLockName))) {
                Transaction tx = jedis.multi();
                tx.del(realLockName);
                if (tx.exec().isEmpty()) {
                    continue;
                }
                log.info("线程{}释放锁成功", theadId);
                isRelease = true;
            }
            //取消监控
            jedis.unwatch();
            break;
        }
        return isRelease;
    }
}



三.使用Redisson实现分布式锁
参考:https://www.jianshu.com/p/47fd7f86c848,
https://www.cnblogs.com/qdhxhz/p/11059200.html

Redis 分布式锁实现方法有多种,以下是一些常见方案: ### SETNX + EXPIRE 方案 使用 `SETNX` 命令尝试获取锁,若返回 `1` 表示获取成功,接着使用 `EXPIRE` 命令为锁设置过期时间,防止死锁。 ```python import redis redis_client = redis.Redis(host='localhost', port=6379, db=0) redis_key = "redis_key_001" get_lock = redis_client.setnx(redis_key, "极简版redis锁") if get_lock: redis_client.expire(redis_key, 10) # 设置 10 秒过期时间 print("获取到redis锁") # 业务逻辑 redis_client.delete(redis_key) else: print("未获取到redis锁") ``` ### SET 的扩展命令(SET EX PX NX)方案 使用 `SET` 命令的扩展参数,原子性地完成设置键值过期时间,避免 `SETNX` `EXPIRE` 非原子操作的问题。 ```python import redis redis_client = redis.Redis(host='localhost', port=6379, db=0) redis_key = "redis_key_001" get_lock = redis_client.set(redis_key, "极简版redis锁", ex=10, nx=True) if get_lock: print("获取到redis锁") # 业务逻辑 redis_client.delete(redis_key) else: print("未获取到redis锁") ``` ### 使用 Lua 脚本(包含 SETNX + EXPIRE 两条指令)方案 通过 Lua 脚本保证 `SETNX` `EXPIRE` 操作的原子性。 ```python import redis redis_client = redis.Redis(host='localhost', port=6379, db=0) redis_key = "redis_key_001" lua_script = """ if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 then redis.call('EXPIRE', KEYS[1], tonumber(ARGV[2])) return 1 else return 0 end """ get_lock = redis_client.eval(lua_script, 1, redis_key, "极简版redis锁", 10) if get_lock: print("获取到redis锁") # 业务逻辑 redis_client.delete(redis_key) else: print("未获取到redis锁") ``` ### SET EX PX NX + 校验唯一随机值,再释放锁方案 在加锁时设置唯一随机值,释放锁时先校验该值,避免误解锁。 ```python import redis import uuid redis_client = redis.Redis(host='localhost', port=6379, db=0) redis_key = "redis_key_001" unique_value = str(uuid.uuid4()) get_lock = redis_client.set(redis_key, unique_value, ex=10, nx=True) if get_lock: print("获取到redis锁") # 业务逻辑 lua_script = """ if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end """ release_result = redis_client.eval(lua_script, 1, redis_key, unique_value) if release_result: print("释放锁成功") else: print("释放锁失败") else: print("未获取到redis锁") ``` ### 开源框架 Redisson 方案 Redisson 是一个基于 Redis 实现的分布式可扩展的 Java 数据结构集合,提供了简单易用的分布式锁实现。 ```java import org.redisson.Redisson; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.redisson.config.Config; public class RedissonLockExample { public static void main(String[] args) { Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config); RLock lock = redisson.getLock("redis_key_001"); try { lock.lock(); System.out.println("获取到redis锁"); // 业务逻辑 } finally { lock.unlock(); System.out.println("释放锁成功"); } } } ``` ### 多机实现分布式锁 Redlock 方案 在多个 Redis 节点上获取锁,只有在大多数节点上都成功获取到锁,才认为加锁成功。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值