手写简单的redis分布式锁

手写Redis分布式锁

锁的分类

单机版同一个JVM虚拟机内,synchronized或者Lock接口
分布式多个不同JVM虚拟机,单机的线程锁机制不再起作用,资源类在不同的服务器之间共享了。

分布式锁需要具备的条件和刚需

  • 独占性
    OnlyOne,任何时刻只能有且仅有一个线程持有
  • 高可用
    若redis集群环境下,不能因为某一个节点挂了而出现获取锁和释放锁失败的情况
    高并发请求下,依l旧性能OK好使
  • 防死锁
    杜绝死锁,必须有超时控制机制或者撤销操作,有个兜底终止跳出方案
  • 不乱抢
    防止张冠李戴,不能私下unlock别人的锁,只能自己加锁自己释放,自己约的锁含着泪也要自己解
  • 重入性
    同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁。

分布式锁

在这里插入图片描述
setnx key value

base案例(boot+redis)(version 1)

  • 使用场景

多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击)

  • 建Module

redis_distributed_lock2
redis_distributed_lock3

  • pom
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.12</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <lombok.version>1.16.18</lombok.version>
    </properties>



    <dependencies>
        <!--SpringBoot通用依赖模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--SpringBoot与Redis整合依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!--swagger2-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--通用基础配置boottest/lombok/hutool-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.8</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  • yml
server:
  port: 7777

# ========================swagger2=====================
# http://localhost:7777/swagger-ui.html
swagger2:
  enable: true
spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  # ========================redis单机=====================
  redis:
    host: 192.168.183.139
    port: 6379
    password: 123456
  • service
@Service
@Slf4j
public class InventoryService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Value("${server.port}")
    private String port;

    private Lock lock = new ReentrantLock();

    public String sale() {
        String retMessage = "";
        lock.lock();
        // 业务逻辑代码(扣减库存)
        try {
            //1. 查看库存信息
            String result = redisTemplate.opsForValue().get("inventory001");
            //2. 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.valueOf(result);
            //3. 扣减库存
            if (inventoryNumber > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
                System.out.println(retMessage);
            }else {
                retMessage = "商品卖完了";
            }
        }finally {
            lock.unlock();
        }
        return retMessage + "\t" + "服务端口号: " + port;
    }
}
  • controller
@RestController
@Api(tags = "redis分布式锁测试")
public class InventoryController
{
    @Autowired
    private InventoryService inventoryService;

    @ApiOperation("扣减库存,一次卖一个")
    @GetMapping(value = "/inventory/sale")
    public String sale()
    {
        return inventoryService.sale();
    }
}

加入nginx分布式微服务架构(version 2)

在这里插入图片描述

  • nginx配置负载均衡

nginx配置文件
default.conf

location / {
               proxy_pass http://mynginx;
}

nginx.conf

upstream mynginx {
               server 192.168.44.1:7777 weight=1;
               server 192.168.44.1:8888 weight=1;
   }

启动nginx(docker)
docker start nginx

  • 启动两个微服务手点测试(默认轮询)

通过nginx访问
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 高并发模拟

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
76号商品被卖出2次,出现超卖故障现象

  • 解释

单机环境下,可以使用synchronized或Lock来实现。
但是在分布式系统中,因为竞争的线程可能不在同一个节点上(同一个jvm中),
所以需要一个让所有进程都能访问到的锁来实现比如redis来构建
不同进程jvm层面的锁就不管用了,那么可以利用第三方的一个组件,来获取锁,未获取到锁,则阻塞当前想要运行的线程

  • 分布式锁出现

跨进程+跨服务
解决超卖
防止缓存击穿

Redis分布式锁(version 3)

  • service
@Service
@Slf4j
public class InventoryService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Value("${server.port}")
    private String port;

    public String sale() {
        String retMessage = "";
        String key = "redisLock";
        String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        while (!redisTemplate.opsForValue().setIfAbsent(key, uuidValue)) {
            //暂停20毫秒,类似cas自选
            try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        }
        // 业务逻辑代码(扣减库存)
        try {
            //1. 查看库存信息
            String result = redisTemplate.opsForValue().get("inventory001");
            //2. 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.valueOf(result);
            //3. 扣减库存
            if (inventoryNumber > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
                System.out.println(retMessage);
            }else {
                retMessage = "商品卖完了";
            }
        }finally {
            redisTemplate.delete(key);
        }
        return retMessage + "\t" + "服务端口号: " + port;
    }
}

宕机与过期+防止死锁(version 4)

  • version 3问题

部署了微服务的Java程序机器挂了,代码层面根本没有走到finally这块,
没办法保证解锁(无过期时间该key一直存在),这个key没有被删除,需要加入一个过期时间限定key

  • 解决

给key设置过期时间,注意设置key+过期时间必须合并一行具备原子性
redisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)

  • 修改service增加过期时间
@Service
@Slf4j
public class InventoryService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Value("${server.port}")
    private String port;

    public String sale() {
        String retMessage = "";
        String key = "redisLock";
        String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        while (!redisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {
            //暂停20毫秒,类似cas自选
            try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        }
        // 业务逻辑代码(扣减库存)
        try {
            //1. 查看库存信息
            String result = redisTemplate.opsForValue().get("inventory001");
            //2. 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.valueOf(result);
            //3. 扣减库存
            if (inventoryNumber > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
                System.out.println(retMessage);
            }else {
                retMessage = "商品卖完了";
            }
        }finally {
            redisTemplate.delete(key);
        }
        return retMessage + "\t" + "服务端口号: " + port;
    }
}

防止误删key的问题(version 5)

  • version 4 的问题

实际业务处理时间如果超过了默认设置key的过期时间
张冠李戴,删除了别人的锁在这里插入图片描述

  • 解决

删除锁的时候先判断加锁与解锁是不是同一个客户端,同一个才行,自己只能删除自己的锁,不误删他人的
redisTemplate.opsForValue().get(key).equalsIgnoreCase(uuidValue)

  • 修改service增加判断条件
@Service
@Slf4j
public class InventoryService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Value("${server.port}")
    private String port;

    public String sale() {
        String retMessage = "";
        String key = "redisLock";
        String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        while (!redisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {
            //暂停20毫秒,类似cas自选
            try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        }
        // 业务逻辑代码(扣减库存)
        try {
            //1. 查看库存信息
            String result = redisTemplate.opsForValue().get("inventory001");
            //2. 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.valueOf(result);
            //3. 扣减库存
            if (inventoryNumber > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
                System.out.println(retMessage);
            }else {
                retMessage = "商品卖完了";
            }
        }finally {
       		// 判断加锁与解锁是不是同一个客户端,同一个才行,自己只能删除自己的锁,不误删他人的
            if (redisTemplate.opsForValue().get(key).equalsIgnoreCase(uuidValue)) {
                redisTemplate.delete(key);
            }
        }
        return retMessage + "\t" + "服务端口号: " + port;
    }
}

Lua保证原子性(version 6)

  • 问题

finally块的判断+del删除操作不是原子性的

  • 解决

启用lua脚本编写redis分布式锁判断+删除判断代码
在这里插入图片描述

  • Lua脚本简介
  • Lua脚本初识
    Redis调用Lua脚本通过eval命令保证代码执行的原子性,直接用return返回脚本执行后的结果值
    eval luascript numkeys [key [key ...]][arg [arg ..]]
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • Lua脚本进一步
    Redis分布式锁Lua脚本官网练习
    eval "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 zzyyRedisLock 1111-2222-3333
    在这里插入图片描述
    条件判断语法
    在这里插入图片描述
    条件判断案例
    在这里插入图片描述
  • 修改service
@Service
@Slf4j
public class InventoryService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Value("${server.port}")
    private String port;

    public String sale() {
        String retMessage = "";
        String key = "redisLock";
        String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        while (!redisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {
            //暂停20毫秒,类似cas自选
            try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}
        }
        // 业务逻辑代码(扣减库存)
        try {
            //1. 查看库存信息
            String result = redisTemplate.opsForValue().get("inventory001");
            //2. 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.valueOf(result);
            //3. 扣减库存
            if (inventoryNumber > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
                System.out.println(retMessage);
            }else {
                retMessage = "商品卖完了";
            }
        }finally {
            String luaScript =
                    "if (redis.call('get', KEYS[1]) == ARGV[1]) then " +
                        "return redis.call('del',KEYS[1]) " +
                    "else " +
                        "return 0 " +
                    "end";
            redisTemplate.execute(new DefaultRedisScript<>(luaScript, Boolean.class), Arrays.asList(key), uuidValue);
        }
        return retMessage + "\t" + "服务端口号: " + port;
    }
}

可重入锁+设计模式(version 7)

  • 问题

while判断并自旋重试获取锁+setnx含自然过期时间+Lua脚本官网删除锁命令
如何兼顾锁的可重入性问题?
在这里插入图片描述

  • redis哪个数据类型可以代替

在这里插入图片描述
hset key field value
hset redis锁名字(zzyyRedisLock) 某个请求线程的UUID+ThreadID 加锁的次数
setnx,只能解决有无的问题
hset,不但解决有无,还解决可重入问题

  • lua脚本

redis命令过程分析
在这里插入图片描述

加锁lua脚本lock

  • 先判断redis分布式锁这个key是否存在
    exists key
    在这里插入图片描述
    返回0说明不存在,hset新建当前线程属于自己的锁by uuid:threadId
    在这里插入图片描述
    返回壹说明已经有锁,需进一步判断是不是当前线程自己的
    HEXISTS key uuid:ThreadlD
    在这里插入图片描述
    返回壹说明是自己的锁,自增1次表示重入
    hincrby key field increment
    在这里插入图片描述
    返回零说明不是自己的
    加锁Lua脚本lock
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
keyvalue过期时间值
KEYS[1]ARGV[1]ARGV[2]
zzyyRedisLock2f586ae740a94736894ab9d51880ed9d:130 秒

在这里插入图片描述

解锁lua脚本unlock

  • 设计思路
    hexists key uuid:threadId
    返回0,表示没有锁,程序返回nil
    不是0,说明有锁且还是自己的锁,直接hincrby -1表示每次减一,解锁一次,直到它变成0表示可以删除改key,del锁key
    在这里插入图片描述
  • lua脚本
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
  • 测试
    在这里插入图片描述

整合lua脚本到java程序中

  1. 新建RedisDistributedLock类并实现JUC里面的Lock接口
  2. 满足JUC里面AQS对Lock锁的接口规范定义来进行实现落地代码
  3. 通过实现JUC里面的Lock接口,实现Redis分布式锁RedisDistributedLock
public class RedisDistributedLock implements Lock {
    private StringRedisTemplate redisTemplate;

    private String lockName;// KEYS[1]
    private String uuidValue;// ARGV[1]
    private long expireTime;// ARGV[2]

    public RedisDistributedLock(StringRedisTemplate redisTemplate, String lockName) {
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        this.expireTime = 40L;
    }

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

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

    /**
     * 实现加锁功能,实现这一个就可以了,其他的加锁方法都调用这个
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (time != -1) {
            this.expireTime = 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";
        System.out.println("script: " + script);
        System.out.println("lockName: " + lockName);
        System.out.println("uuidValue: " + uuidValue);
        System.out.println("expireTime: " + expireTime);

        while (!redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),
                Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
            TimeUnit.MILLISECONDS.sleep(50);
        }
        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";
        // nil = false 1 = true 0 = false
        System.out.println("lockName: "+lockName);
        System.out.println("uuidValue: "+uuidValue);
        System.out.println("expireTime: "+expireTime);
        Long flag = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
                Arrays.asList(lockName), uuidValue);
        if (flag == null) {
            throw new RuntimeException("This lock doesn't EXIST");
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public Condition newCondition() {
        return null;
    }
}
  1. 修改service代码
public String sale() {
        Lock lock = new RedisDistributedLock(redisTemplate, "redisLock");
        String retMessage = "";
        lock.lock();
        // 业务逻辑代码(扣减库存)
        try {
            TimeUnit.SECONDS.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            //1. 查看库存信息
            String result = redisTemplate.opsForValue().get("inventory001");
            //2. 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.valueOf(result);
            //3. 扣减库存
            if (inventoryNumber > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
                System.out.println(retMessage);
            }else {
                retMessage = "商品卖完了";
            }
        }finally {
            lock.unlock();
        }
        return retMessage + "\t" + "服务端口号: " + port;
    }
  • 引入工厂模式

考虑扩展,本次是redis实现分布式锁,以后zookeeper、mysql实现那

DistributedLockFactory

@Component
public class DistributedLockFactory {
    @Autowired
    private StringRedisTemplate redisTemplate;
    private String lockName;
    private String uuidValue;

    public static final String REDIS_LOCK = "redis";
    public static final String ZOOKEEPER_LOCK = "zookeeper";
    public static final String MYSQL_LOCK = "mysql";

    public DistributedLockFactory() {
        this.uuidValue = IdUtil.simpleUUID();
    }

    public Lock getDistributedLock(String lockType) {
        if (lockType == null) return null;
        if (lockType.equalsIgnoreCase(REDIS_LOCK)) {
            lockName = "redisLock";
            return new RedisDistributedLock(redisTemplate, lockName, uuidValue);
        }else if (lockType.equalsIgnoreCase(ZOOKEEPER_LOCK)) {
            //TODO zookeeper版本的分布式锁实现
        }else if (lockType.equalsIgnoreCase(MYSQL_LOCK)) {
            //TODO mysql版本的分布式锁实现
        }
        return null;
    }
}

RedisDistributedLock(主要更改构造方法)

public RedisDistributedLock(StringRedisTemplate redisTemplate, String lockName, String uuidValue) {
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.uuidValue = uuidValue + ":" + Thread.currentThread().getId();
        this.expireTime = 30L;
    }

service

@Service
@Slf4j
public class InventoryService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Value("${server.port}")
    private String port;

    @Autowired
    private DistributedLockFactory lockFactory;

    public String sale() {

        String retMessage = "";
        Lock redisLock = lockFactory.getDistributedLock(DistributedLockFactory.REDIS_LOCK);
        redisLock.lock();
        // 业务逻辑代码(扣减库存)
        try {
            //1. 查看库存信息
            String result = redisTemplate.opsForValue().get("inventory001");
            //2. 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.valueOf(result);
            //3. 扣减库存
            if (inventoryNumber > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
                System.out.println(retMessage);
            }else {
                retMessage = "商品卖完了";
            }
        }finally {
            redisLock.unlock();
        }
        return retMessage + "\t" + "服务端口号: " + port;
    }
}

自动续期(version 8)

确保redisLock过期时间大于业务执行时间的问题

  • 加个种,lua脚本
hset redisLock 111122223333:11 3
EXPIRE redisLock 30
ttl redisLock
#========================================
eval "if rediscall('hexists',KEYS[1],ARGV[1]) == 1 then return redis.call('expire', KEYS[1],ARGV[2]) else return 0 end" 1 redisLock 111122223333:11 30



#===============================自动续期
if redis.call('hexists',KEYS[1],ARGV[1]) == 1 then 
	return redis.call('expire',KEYS[1],ARGV[2])
else 
	return 0
end
  • 修改RedisDistributeLock
public class RedisDistributedLock implements Lock {
    private StringRedisTemplate redisTemplate;

    private String lockName;// KEYS[1]
    private String uuidValue;// ARGV[1]
    private long expireTime;// ARGV[2]

    public RedisDistributedLock(StringRedisTemplate redisTemplate, String lockName, String uuidValue) {
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.uuidValue = uuidValue + ":" + Thread.currentThread().getId();
        this.expireTime = 30L;
    }

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

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

    /**
     * 实现加锁功能,实现这一个就可以了,其他的加锁方法都调用这个
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (time != -1) {
            this.expireTime = 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";
        System.out.println("script: " + script);
        System.out.println("lockName: " + lockName);
        System.out.println("uuidValue: " + uuidValue);
        System.out.println("expireTime: " + expireTime);

        while (!redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),
                Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
            TimeUnit.MILLISECONDS.sleep(50);
        }
        this.renewExpire();
        return true;
    }

    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), uuidValue, String.valueOf(expireTime))) {
                    renewExpire();
                }
            }
        },(this.expireTime * 1000) / 3);
    }

    /**
     * 实现解锁功能
     */
    @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";
        // nil = false 1 = true 0 = false
        System.out.println("lockName: "+lockName);
        System.out.println("uuidValue: "+uuidValue);
        System.out.println("expireTime: "+expireTime);
        Long flag = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
                Arrays.asList(lockName), uuidValue);
        if (flag == null) {
            throw new RuntimeException("This lock doesn't EXIST");
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public Condition newCondition() {
        return null;
    }
}
  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
手写Redis分布式锁一个常见方法是使用Redis的SETNX命令和EXPIRE命令。SETNX用于设置一个键值对,只有在键不存在的情况下才能设置成功,用于表示获取锁的操作。EXPIRE用于设置键的过期时间,确保在获取锁的客户端崩溃或网络故障的情况下,锁最终会被释放。 下面是一个用C语言手写Redis分布式锁简单示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/time.h> #include <sys/wait.h> #include <signal.h> #include <errno.h> #include <hiredis/hiredis.h> #define REDIS_HOST "localhost" #define REDIS_PORT 6379 #define LOCK_KEY "my_lock" #define LOCK_EXPIRE_TIME 10 int main() { pid_t child_pid; int status; // 创建子进程 child_pid = fork(); if (child_pid == 0) { // 子进程获取锁 redisContext *redis_conn = redisConnect(REDIS_HOST, REDIS_PORT); if (redis_conn == NULL || redis_conn->err) { printf("连接Redis失败\n"); exit(1); } // 设置锁 redisReply *reply = redisCommand(redis_conn, "SETNX %s 1", LOCK_KEY); if (reply == NULL || reply->type == REDIS_REPLY_ERROR || reply->integer != 1) { printf("获取锁失败\n"); exit(1); } // 设置锁的过期时间 reply = redisCommand(redis_conn, "EXPIRE %s %d", LOCK_KEY, LOCK_EXPIRE_TIME); if (reply == NULL || reply->type == REDIS_REPLY_ERROR || reply->integer != 1) { printf("设置锁的过期时间失败\n"); exit(1); } printf("获取锁成功\n"); // 模拟业务操作 sleep(5); // 释放锁 reply = redisCommand(redis_conn, "DEL %s", LOCK_KEY); if (reply == NULL || reply->type == REDIS_REPLY_ERROR || reply->integer != 1) { printf("释放锁失败\n"); exit(1); } printf("释放锁成功\n"); // 关闭Redis连接 redisFree(redis_conn); exit(0); } else if (child_pid > 0) { // 等待子进程结束 waitpid(child_pid, &status, 0); if (WIFEXITED(status)) { printf("子进程正常结束\n"); } else if (WIFSIGNALED(status)) { printf("子进程异常结束\n"); } } else { printf("创建子进程失败\n"); exit(1); } return 0; } ``` 在这个示例中,使用了 hiredis 库来连接 Redis,并通过 SETNX 和 EXPIRE 命令实现分布式锁的获取和释放。主进程创建一个子进程,子进程尝试获取锁并进行业务操作,然后释放锁。主进程等待子进程的结束并打印相应信息。 注意:这只是一个简单的示例,实际应用中可能需要考虑更多的场景,比如锁的重入、超时处理等。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [分布式锁_Redis分布式锁+Redisson分布式锁+Zookeeper分布式锁+Mysql分布式锁(原版)](https://blog.csdn.net/guan1843036360/article/details/127827270)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值