分布式锁的理解与项目

分布式锁

1、分布式锁在项目中有哪些应用场景

  1. 系统是一个分布式系统,集群,java的锁已经锁不住了。
  2. 操作共享资源,比如库里唯一的用户数据。
  3. 同步访问,即多个进程同时操作共享资源

2、Redis作为分布式锁用什么命令

SETNX
格式:setnx key value 将key的值社会value,当前仅当key不存在。
若key已经存在,则SETNX不做任何动作

SETNX是【set if not exists】如果不存在,则set
一般要设置过期时间,一般是大于此完成的业务的时间
set key value nx ex 10s

3、 Redis做分布式锁死锁的情况如何解决?

  1. 加锁,没有释放锁。需要加释放锁的操作,delete key
  2. 加锁后,程序还没有执行释放锁,程序挂了,没有delete key 。需要使用key的过期机制。

4、Redis如何做分布式锁

假设有两个服务A,B都希望获得锁,执行过程大致如下:

  1. 服务A为了获得锁,向Redis发起如下命令:set ProductId:lock 0ASDASDAS NX EX 30000 ,其中,productId是自己定义,与业务有关的id,“0ASDASDAS ”是一串随机值,必须保证全局唯一,“NX”指的是当且仅当次key不存在的时候,返回执行成功,否则执行失败。"EX 30000"指的是30秒后,key会自动删除。执行命令返回成功,表明服务已经获取到了锁。
  2. 服务B为了获取锁,向Redis获取同样的命令:set ProductId:lock 00111 NX EX 30000,由于Redis中已经存在同名的key,且并未过期,因此执行失败,服务B未能获取锁。服务B进入循环请求状态,比如每隔一秒(自行设置)向redis发送请求,直到执行成功并获取锁。
  3. 服务A的业务代码执行时长超过了30秒,导致key超时,因此redis自动删除了key。此时服务B再次发送命令执行完毕,假设本次请求的value为00002222,此时需要在服务A中对key进行续期。
  4. 服务A执行完毕后,为了释放锁,服务A会主动向Redis发起删除key请求。注意:在删除key之前,一定要判断服务A持有的value与Redis内存储的value是否一致。比如在这个场景之下,Redis的锁已经不是A持有的那一把,而是服务B创建的,如果贸然使用服务A持有的key来删除锁,会导致服务B的锁释放掉。此外,由于删除时涉及一系列判断逻辑。

5、基于Zookeeper的分布式锁的实现原理是什么?

顺序节点特性:
使用Zookeeper的顺序节点特性,假如我们在/lock/目录下创建3个节点,ZK集群会按照发起创建的顺序来创建节点,节点分别为 /lock/00000001 , /lock/00000002 , /lock/00000003,最后一位数是依次递增的,节点名由zk完成。

临时节点的特性:
ZK中还有一种名为临时节点的节点,临时节点由某个客户端创建,当客户端与ZK集群断开连接,则该节点自动被删除。EPHEMERAL_SEQUENTIAL为临时顺序节点。
根据ZK中节点是否存在,可以作为分布式锁的锁状态,依次来实现一个分布式锁,下面是分布式锁的基本逻辑:

  1. 客户端1调用create()方法创建名为"/业务ID/lock"的临时顺序节点
  2. 客户端1调用getChildren(“业务ID”)方法来获取所有已经创建的子节点。
  3. 客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点是所有节点中序号最小的,就是看自己创建的序列号是否排第一,那么就认为这个客户端1获得了锁,在它前面没有别的客户端拿到了锁
  4. 如果创建的节点不是节点中需要最小的,那么则监视比自己创建节点序列号小的最大的节点,进入等待。知道下次监视的子节点变更的时候,再进行子节点获取,判断是否获取锁。

6、Redis分布式锁与ZK分布式锁的区别

Redis:

  1. Redis只是保证最终一致性,副本间的数据复制是异步进行的(Set是写,Get是读,Redis的集群一般是读写分离架构,存在主从延迟的情况),主从切换后可以有部分数据没有复制过去可能会丢失锁的情况,故强调数据一致性的时候不推荐使用Redis,推荐使用Zk
  2. Redis的集群各方法响应时间均为最低。随着并发量和业务数量的提升其响应时间会有明显上升(公网集群影响因素偏大),但是极限的qps可以达到最大且基本无异常

Zookeeper:

  1. 使用Zookeeper集群,锁原理是使用Zookeeper的临时顺序节点,临时顺序节点的生命周期在Clinet与集群的Session结束时结束。因此如果某个Clinet节点存在网络问题,与Zookeeper集群断开连接,Session超时同样会导致锁被错误释放(导致被其他线程错误的持有),因此Zookeeper也无法保证完全一致。
  2. ZK有较好的稳定性;响应时间抖动很少,没有出现异常情况。但是随着并发和业务数量提升其响应时间qps会明显下降。

总结:

  1. Zk每次操作锁都要创建若干个节点,完成后释放节点,会浪费很多时间
  2. 而Redis只是简单的数据操作,没有这个问题

7、Redis分布式锁模拟代码

(1)导入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

配置

spring.redis.port=6379
spring.redis.host=localhost

编写util获取锁与释放锁

package com.ycz.unils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.Collections;

/**
 * @Description:
 * @Author: Alex
 * @Date 2022-07-01-21:00
 * @Version: V1.0
 **/
@Component
public class RedisLockUtils {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final Long LOCK_REDIS_TIMEOUT =100L;
    private static final Long LOCK_REDIS_WAIT =500L;

    public Boolean getLock(String key ,String value){
        return this.redisTemplate.opsForValue().setIfAbsent(key , value , Duration.ofSeconds(LOCK_REDIS_TIMEOUT));
    }

    public Long releaseLock(String key , String value){
        String luaString  = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> longDefaultRedisScript = new DefaultRedisScript<>(luaString, Long.class);
        Long execute = redisTemplate.execute(longDefaultRedisScript, Collections.singletonList(key), value);
        return execute;
    }
}

controller

package com.ycz.controller;

import com.ycz.unils.RedisLockUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

/**
 * @Description:
 * @Author: Alex
 * @Date 2022-07-01-21:12
 * @Version: V1.0
 **/
@RestController
@Slf4j
public class RedisController {

    @Autowired
    private RedisLockUtils lockUtils;

    //对这个订单的id相关的进行修改,所以要加分布式锁
    @RequestMapping("/order/update/{orderId}")
    public Object orderUpdate(@PathVariable String orderId) throws InterruptedException {//一般是传实体,这个不在意这个细节,到时候可以改成entity.getId
        String key = orderId+":lock";
        String value = UUID.randomUUID().toString();
        Boolean lock = lockUtils.getLock(key, value);
        if (lock){
            log.info("获取到了锁");
            //休息10s,假装为业务逻辑代码,长一点显示效果
            Thread.sleep(10000);
            lockUtils.releaseLock(key , value);
            return "执行成功";
        }else{
            return "请稍后再试";
        }


    }

}

启动

http://localhost:8080/order/update/1

(1)假设其orderId是相同的

第一个获取到了锁
在这里插入图片描述
第二个在等待第一个锁执行完成,再发出请求
在这里插入图片描述

(2)假设他们的执行的orderId是不相同的

在这里插入图片描述
在这里插入图片描述

都会进行执行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值