(三)Redis中的事务和锁

目录

事务概念

Redis事务

正常执行事务

放弃事务

分布式锁

基于redis乐观锁(因为递归调用,可能出现栈内存溢出,所以不建议使用,但是Cas原理要知道)

基于setnx

问题一:如果代码没有执行完,宕机了, finally没有执行,就没有释放锁,就会照成死锁

问题二:因为设置锁与设置锁过期时间不是原子性的,可能出现设置锁完成,就挂了,没有执行到设置过期时间的代码

问题三:如果设置过期时间,但是代码运行时间大于过期时间,代码就会出现问题,导致误删问题

问题四:上面的获取uuid与删除释放也不是原子性,也会存在问题

问题五:可能出现不可重入问题(或者死锁)

总结

基于 hash+lua脚本

lua脚本基本使用

可重入锁概念

分布式锁思路

lua脚本加锁

lua脚本解锁

分布式Redis锁完整代码实现

注意事项

关于自动续期

总结


事务概念
  1. 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
  2. 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
  3. 隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  4. 持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

在Redis事务没有没有隔离级别的概念!

在Redis单条命令式保证原子性的,但是事务不保证原子性!

Redis事务
正常执行事务
127.0.0.1:6379> multi  #开启事务
OK
127.0.0.1:6379> set name dingyongjun  #添加数据
QUEUED
127.0.0.1:6379> set high 172  #添加数据
QUEUED
127.0.0.1:6379> exec  执行事务
1) OK
2) OK
127.0.0.1:6379> get name  #获取数据成功,证明事务执行成功
"dingyongjun"
127.0.0.1:6379> get age
"26"
放弃事务
127.0.0.1:6379> multi  #开启事务
OK
127.0.0.1:6379> set name dingyongjun  #添加数据
QUEUED
127.0.0.1:6379> set age 26  #添加数据
QUEUED
127.0.0.1:6379> discard  #放弃事务
OK
127.0.0.1:6379> get name  #不会执行事务里面的添加操作
(nil)
分布式锁
基于redis乐观锁(因为递归调用,可能出现栈内存溢出,所以不建议使用,但是Cas原理要知道)
  • watch: 可以监控一个或者多个key的值,如果在事务(exec)执行之前,key的值发生变化则取事务执行
  • muti: 开启事务
  • exec: 执行事务
public String redisLockDome() {
    //必须使用SessionCallback 才可以开启事务,这个是接口,只要外部类定义,实现这个接口也行
    this.stringRedisTemplate.execute(new SessionCallback<Object>() {
        @SneakyThrows
        @Override
        public Object execute(RedisOperations redisTemplate) throws DataAccessException {
            //watch //监听key
            redisTemplate.watch("stock");
            //获取
            String stock = redisTemplate.opsForValue().get("stock").toString();
            //判断
            if (stock != null && stock.length() != 0) {
                Integer st = Integer.valueOf(stock);
                if (st > 0) {
                    //multi 开启事务
                    redisTemplate.multi();
                    //扣减
                    redisTemplate.opsForValue().set("stock", String.valueOf(--st));
                    //exec 执行
                    List exec = redisTemplate.exec();
                    // CAS 如果执行事务的返回结果集为空,则代表减库存失败,重试
                    if (exec == null || exec.size() == 0) {
                        //重试的时候睡一会,防止栈内存溢出
                        Thread.sleep(40);
                        redisLockDome();
                    }
                    return exec;
                }
            }
            return null;
        }
    });
    return "OK";
}
基于setnx

先设置setnx 设置锁,如果设置成功就代表获得锁,执行,如果没有设置成功就重试获取锁

  • 加锁:setnx 如果存在,不设置,不存在才设置,设置成功返回成功数
  • 解锁:del 删除 锁,这样其他重试的就可以设置锁
  • 如果没有获得,就重试
public String redisLockDome() {
    //setIfAbsent  == setnx, 如果不存在,就设置
    while (!this.stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111")) {
        //如果没有设置成功,40毫秒后重试,这个睡眠可以使竞争压力小,本质也是使用CAS调用
        try { Thread.sleep(40); } catch (InterruptedException e) { e.printStackTrace(); }
    }
    //获取成功
    try {
        //获取
        String stock = this.stringRedisTemplate.opsForValue().get("stock").toString();
        if (stock != null && stock.length() != 0) {
            Integer st = Integer.valueOf(stock);
            if (st > 0) {
                //扣减
                this.stringRedisTemplate.opsForValue().set("stock", String.valueOf(--st));\
            }
        }
    } finally {
        //解锁
        this.stringRedisTemplate.delete("lock");
    }
    return "OK";
}
问题一:如果代码没有执行完,宕机了, finally没有执行,就没有释放锁,就会照成死锁

解决:添加锁过期时间,防止死锁,expire设置, 获取成功,添加下面代码

//获取成功
while (!this.stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111")) {
        //如果没有设置成功,40毫秒后重试,这个睡眠可以使竞争压力小,本质也是使用CAS调用
        try { Thread.sleep(40); } catch (InterruptedException e) { e.printStackTrace(); }
}
//设置过期时间,防止死锁
this.stringRedisTemplate.expire("lock", 3, TimeUnit.SECONDS);
问题二:因为设置锁与设置锁过期时间不是原子性的,可能出现设置锁完成,就挂了,没有执行到设置过期时间的代码

解决:

  1. set lock 111 ex 20 xx :lock 键值, 111 设置的值, ex 20 过期时间,毫秒 xx表示存在才设置,不存在,不设置 (nx) 相反 不存在才设置 ,存在不设置
  2. ttl key 获取过期时间
//java代码实现
this.stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111", 3, TimeUnit.SECONDS)
问题三:如果设置过期时间,但是代码运行时间大于过期时间,代码就会出现问题,导致误删问题

解决:防误删:设置锁的UUID等唯一表示

String uuid = UUID.randomUUID().toString();
//setIfAbsent  == setnx, 如果不存在,就设置
while (!this.stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS)) {
    //如果没有设置成功,40毫秒后重试
    try { Thread.sleep(40); } catch (InterruptedException e) { e.printStackTrace(); }
}
try{
    //业务代码.......
}finally {
//解锁
//先判断是不是自己的锁, 是自己的才删除,防止误删
   if (this.stringRedisTemplate.opsForValue().get("lock").equals(uuid)){
       this.stringRedisTemplate.delete("lock");
   }
}
问题四:上面的获取uuid与删除释放也不是原子性,也会存在问题

解决:使用Lua脚本解决,redis默认支持, 一次性发送多个指令给redis,redis单线程执行指令遭守one-by-one规则

String uuid = UUID.randomUUID().toString();
//setIfAbsent  == setnx, 如果不存在,就设置
while (!this.stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS)) {
    //如果没有设置成功,40毫秒后重试
    try { Thread.sleep(40); } catch (InterruptedException e) { e.printStackTrace(); }
}
try{
    //业务代码.......
}finally {
            //解锁
//            先判断是不是自己的锁, 是自己的才删除,防止误删
//            if (this.stringRedisTemplate.opsForValue().get("lock").equals(uuid)){
//                this.stringRedisTemplate.delete("lock");
//            }
            //等价于
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +  (等价于 //this.stringRedisTemplate.opsForValue().get("lock").equals(uuid))
                    "then " +
                    "   return redis.call('del', KEYS[1]) " +  (等价于// this.stringRedisTemplate.delete("lock");)
                    "else " +
                    "   return 0 " +
                    "end";
//            new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock"), uuid
//            script:脚本
//            Boolean.class 返回类型
//            Arrays.asList("lock") KEYS值
//            uuid ARGV值
            this.stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock"), uuid);
}
问题五:可能出现不可重入问题(或者死锁)
  1. 线程有A方法和B方法,现在A方法获取到锁之后调用B方法,但是B方法没有锁,那么A就需要等待B,这样A明明已经获取到锁,就不能等待了
  2. 可重入性就可以解决这个尴尬的问题,当线程拥有锁之后,往后再遇到加锁方法,直接将加锁次数加 1,然后再执行方法逻辑。退出加锁方法之后,加锁次数再减
总结

解决上诉所有问题,我们采用 hash+lua脚本解决,看下一章

基于 hash+lua脚本
lua脚本基本使用
redis命令:
EVAL script numkeys key [key ...arg [arg...]  输出的不是print,而是return
变量:
全局变量:a=5
局部变量:local a=5
lua使用:
EVAL script numkeys key [key ...] arg [arg ...]
script:lua脚本字符串,这段Lua脚本不需要(也不应该)定义函数。
numkeys:lua脚本中KEYS数组的大小
key [key ...]:KEYS数组中的元素
arg [arg ...]:ARGV数组中的元素

例子:
EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 5 10 20 30 40 50 60 70 80 90
# 输出:10 20 60 70, [ 5(是key的长度)10 20 30 40 50(keys值)60 70 80 90(剩下都是ARGA值)]
KEYS[1] 10
KEYS[2] 20
KEYS[3] 30
KEYS[4] 40
KEYS[5] 50
ARGV[1] 60
ARGV[2] 70
ARGV[3] 80
ARGV[4] 90

EVAL "if KEYS[1] > ARGV[1] then return 1 else return 0 end" 1 10 20
# 输出:0

EVAL "if KEYS[1] > ARGV[1] then return 1 else return 0 end" 1 20 10
# 输出:1
可重入锁概念

ReentrantLock就是可重入锁,底层原理如下:

  • 可重入锁加锁流程:ReentrantLock.lock() --> NonfairSync.lock() --> AQS.acquire(1) --> NonfairSync.tryAcquire(1) --> Sync.nonfairTryAcquire(1)

1.CAS获取锁,如果没有线程占用锁(state==0),加锁成功并记录当前线程是有锁线程(两次)

2.如果state的值不为0,说明锁已经被占用。则判断当前线程是否是有锁线程,如果是则重入(state + 1)

3.否则加锁失败,入队等待

  • 可重入锁解锁流程:ReentrantLock.unlock() --> AQS.release(1) --> Sync.tryRelease(1)

1.判断当前线程是否是有锁线程,不是则抛出异常

2.对state的值减1之后,判断state的值是否为0,为0则解锁成功,返回true

3.如果减1后的值不为0,则返回false

当加锁次数为 0 时,锁才被真正的释放。可以看到可重入锁最大特性就是计数,计算加锁的次数。所以当可重入锁需要在分布式环境实现时,我们也就需要统计加锁次数

分布式锁思路
  1. redis hash结构(双层Map结构): Map<'lock', Map<uuid, state(可重入次数)>>

例子:hset lock 1213-3453-6234-232 1

  1. 加锁
  • 判断锁是否存在(exists lock == null),不存在则直接获取锁 hset lock uuid 1
  • 如果锁存在(exists lock != null)则判断是否自己的锁(hexists lock uuid != null,是自己的锁),如果是自己的锁则重入:hincrby lock uuid 1(这是递增+1)
  • 如果锁存在(exists lock != null)则判断是否自己的锁(hexists lock uuid == null,不是自己的锁) 否则重试:递归 循环
  1. 解锁
  • exists lock 判断锁是否存在
  • 判断自己的锁是否存在(hexists),不存在则返回nil
  • 如果自己的锁存在,则减1(hincrby -1),判断减1后的值是否为0,为0则释放锁(del)并返回1,不为0,返回0
lua脚本加锁
--------------------------------------版本1-------------------------------------------------------------------
if redis.call('exists','lock')==0   //判断锁是否存在(exists lock == 0)
then
    redis.call('hset', 'lock', uuid, 1) //不存在则直接获取锁 hset lock uuid 1
    redis.call('expire','lock',30) //设置lock的过期时间
    return 1
elseif redis.call('hexists', 'lock', uuid)==1 //判断是否自己的锁(hexists lock uuid != 0,是自己的锁)
then
    redis.call('hincrby', 'lock', uuid, 1) //如果是自己的锁则重入:hincrby lock uuid 1(这是递增+1)
    redis.call('expire', 'lock', 30) //重置一下过期时间
    return 1
else
    return 0
end
--------------------------------------改进版本2-----------------------------------------------------------------------
lock = KEYS[1]
uuid = ARGV[1]
过期时间 = ARGV[2]

改成
if redis.call('exists',KEYS[1])==0   //判断锁是否存在(exists lock == 0)
then
    redis.call('hset', KEYS[1], ARGV[1], 1) //不存在则直接获取锁 hset lock uuid 1
    redis.call('expire', KEYS[1], ARGV[2]) //设置lock的过期时间
    return 1
elseif redis.call('hexists', KEYS[1], ARGV[1])==1 //判断是否自己的锁(hexists lock uuid != 0,是自己的锁)
then
    redis.call('hincrby', KEYS[1], ARGV[1], 1) //如果是自己的锁则重入:hincrby lock uuid 1(这是递增+1)
    redis.call('expire', KEYS[1], ARGV[2]) //重置一下过期时间
    return 1
else
    return 0
end

------------------------------------------改进版本3(最终版本)--------------------------------------------------------------------------
因为if与if else里面的内容体redis.call('hset', KEYS[1], ARGV[1], 1) 与 redis.call('hincrby', KEYS[1], ARGV[1], 1) 不一样,但是都可以使用hincrby替代
所以代码可以改成

if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1
then  
    redis.call('hincrby', KEYS[1], ARGV[1], 1)
    redis.call('expire', KEYS[1], ARGV[2])
    return 1
else
    return 0
end
--------------------------------------------------------------------------------------------------------------------
lua脚本解锁
if redis.call('hexists', KEYS[1], ARGV[1]) == 0              //判断锁是否存在 并且是自己的锁
then
    return nil                                              //没有,直接不需要释放,返回nil
elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0      //如果自己的锁存在,则减1(hincrby -1),并判断减1后的值是否为0
then
    return redis.call('del', KEYS[1])                        //为0则释放锁(del)并返回1
else
    return 0                                                //不为0,返回0
end
-------------------------------------------
KEYS[1]: lock
ARGV[1]: uuid
分布式Redis锁完整代码实现
  1. DistributedLockClient工厂类完整代码具体实现:
package com.cg.service.utils;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;


import java.util.UUID;


@Component
public class DistributedLockClient {
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 这个是单例的,一台服务器启动spring boot 的时候,初始化就已经定义好了,所以分布式下的话解决不了
     */
    private String uuid;

    public DistributedLockClient() {
        this.uuid = UUID.randomUUID().toString();
    }
    /**
     * 工厂lock
     * @param lockName 锁名称
     * @return
     */
    public DistributedRedisLock getRedisLock(String lockName){
        return new DistributedRedisLock(redisTemplate, lockName, uuid);
    }
}
  1. DistributedRedisLock实现完整代码如下:
package com.cg.service.utils;


import lombok.Data;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;


import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;


/**
* 基于redis锁
*/
public class DistributedRedisLock implements Lock {
    private StringRedisTemplate redisTemplate;

    private String lockName;
    private String uuid;
    private long expire = 30;

    public DistributedRedisLock(StringRedisTemplate redisTemplate, String lockName, String uuid) {
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.uuid = uuid + ":" + Thread.currentThread().getId();
    }
    /**
     * 加锁方法
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (time != -1){
            this.expire = unit.toSeconds(time);
        }
        String script = "if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "   redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
                "   redis.call('expire', KEYS[1], ARGV[2]) " +
                "   return 1 " +
                "else " +
                "   return 0 " +
                "end";
        while (!this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire))){
            Thread.sleep(50);
        }

        //加锁成功,返回之前,开启定时器,自动续期
        renewExpire();
        return true;
    }
    /**
     * 解锁方法
     */
    @Override
    public void unlock() {
        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 " +
                "then " +
                "   return nil " +
                "elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 " +
                "then " +
                "   return redis.call('del', KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";
        Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuid);
        if (flag == null){
            throw new IllegalMonitorStateException("this lock doesn't belong to you!");
        }
    }

    @Override
    public void lock() {
        this.tryLock();
    }

    @Override
    public boolean tryLock() {
        try {
            return this.tryLock(-1L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    //下面不需要实现
    @Override
    public void lockInterruptibly() throws InterruptedException {}

    @Override
    public Condition newCondition() {return null;}

    /**
     * 自动续期
     */
    private void renewExpire() {
       String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 1" +
               "then" +
               "    return redis.call('expire', KEYS[1], ARGV[2])" +
               "else" +
               "    return 0" +
               "end";


       new Timer().schedule(new TimerTask() {
           @Override
           public void run() {
                if(redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire))){
                    //重置
                    renewExpire();//又开启定时任务
                }
           }
           //延迟三分之一过期时间执行lua脚本, 一次性脚本
       }, this.expire * 1000 / 3);
    }
}
  1. 使用
public String redisLockDome() {
    DistributedRedisLock lock = distributedLockClient.getRedisLock("lock");
    lock.lock();
    try {
        String stock = this.stringRedisTemplate.opsForValue().get("stock").toString();
        if (stock != null && stock.length() != 0) {
            Integer st = Integer.valueOf(stock);
            if (st > 0) {
                //扣减
                this.stringRedisTemplate.opsForValue().set("stock", String.valueOf(--st));
            }
        }
        test();// 测试可重入锁
    } finally {
        lock.unlock();
    }
    return "OK";
}

public void test() {
    DistributedRedisLock lock = distributedLockClient.getRedisLock("lock");
    lock.lock();
    try {
        System.out.println("测试可重入锁, 业务处理");
    } finally {
        lock.unlock();
    }
}
注意事项
  1. 因为spring boot 容器@Component后,默认是单例模式,uuid在服务初始化就已经确定,所以多服务,多服务器,每个服务器uuid相同, 为了实现分布式锁,且满足多线程访问,所以要在 uuid后面加上线程id
String getId(){
        return uuid + ":" + Thread.currentThread().getId();
    }
  1. 使用的时候用
Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), getId());
  1. 代码思路理解
A服务 容器uuid = uuid_service_A,初始化就确定
线程1:锁uuid = uuid_service_A:thread1(容器uuid+线程id)
线程2:锁uuid = uuid_service_A:thread2(容器uuid+线程id)

B服务 容器uuid = uuid_service_B 初始化就确定
线程1:锁uuid = uuid_service_B:thread1(容器uuid+线程id)
线程2:锁uuid = uuid_service_B:thread2(容器uuid+线程id)

锁结构:Map<lock, Map<uuid, state>>

A服务线程1 调用方法1 new DistributedLockClient().lock() 加锁
    -> 锁不存在(redis.call('exists','lock')==0)
    -> 加锁  Map<lock, Map<uuid_service_A:thread1, 1>> state+1
    -> 继续执行业务逻辑...
    ->调用方法2 new DistributedLockClient().lock() 加锁
    ->锁存在(redis.call('exists','lock')!=0)
    ->判断是自己的锁 /uuid = uuid_service_A:thread1/ 设置可重入锁  Map<lock, Map<uuid_service_A:thread1, 2>> state再+1=2
    ->继续执行业务逻辑...

这时A服务线程2 进来处理代码
    -> 锁存在(redis.call('exists','lock')==0) 因为A服务在处理,加过了
    -> 判断不是自己的锁(hexists lock uuid == 0,不是自己的锁)自己的锁uuid = uuid_service_B:thread2/目前的锁uuid=uuid_service_A:thread1
    -> while重试(等待服务A释放删除,或者过期)
关于自动续期

解决:可以使用定时任务 + lua脚本

  1. 定时器
//定时器,延迟10S执行,每隔5S执行
new Timer().schedule(new TimerTask() {
    @Override
    public void run() {
    }
}, 10000, 5000);
  1. lua脚本
if redis.call('hexists', KEYS[1], ARGV[1]) == 1
then
    return redis.call('expire', KEYS[1], ARGV[2])
else
    return 0
end

KEYS[1]: lock
ARGV[1]: uuid
ARGV[2]: 过期时间
  1. 自动续期最终代码
/**
* 自动续期
*/
private void renewExpire() {
   String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 1" +
           "then" +
           "    return redis.call('expire', KEYS[1], ARGV[2])" +
           "else" +
           "    return 0" +
           "end";
   new Timer().schedule(new TimerTask() {
       @Override
       public void run() {
            if(redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), getId(), String.valueOf(expire))){
                //重置
                renewExpire();//又开启定时任务,递归
            }
       }
       //延迟三分之一过期时间执行lua脚本, 一次性脚本
   }, this.expire * 1000 / 3);
}
总结
  1. 锁实现了的功能
  • 独占排他:setnx
  • 防死锁:redis客户端程序获取到锁之后,立马宕机。给锁添加过期时间
  • 不可重入:可重入
  • 防误删:先判断是否自己的锁才能别除
  • 原子性:

加锁和过期时间之间:set k v ex3nx

判断和释放锁之间:lua脚本

  • 可重入性:hash(key field value)+lua脚本
  • 自动续期:Timer定时器+lua脚本
  1. 加锁:
  • setnx:独占排他,原子性,死锁、不可重入
  • set k v ex30nx: 独占排他、死锁,不可重入
  • hash+lua脚本:可重入锁

判断锁是否被占用(exists),如果没有被占用则直接获取锁(hset/hincrby)并设置过期时间(expire)

如果锁被占用,则判断是否当前线程占用的(hexists),如果是则重入(hincrby)并重置过期时间(expire)

否则获取锁失败,将来代码中重试

  • Timer定时器+lua脚本:实现锁的自动续期
  • 判断锁是否自己的锁(hexists==1),如果是自己的锁则执行expire重置过期时间
  1. 解锁
  • del:导致误删除
  • 先判断再删除同时保证原子性:ua脚本
  • hash+lua脚本:可重入

判断当前线程的锁是否存在,不存在则返回il,将来抛出异常

存在则直接减1(hincrby-1),判断减1后的值是否为0,为0则释放锁(del),并返回1

不为0,则返回0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星星学霸

您的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值