Redis实现分布式锁原理(面试重点)

这篇具有很好参考价值的文章主要介绍了Redis实现分布式锁原理(面试重点)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、为什么使用分布式锁?

>本地锁的局限性(synchronized):

本地锁只能锁住当前服务,只能保证自己的服务,只有一个线程可以访问,但是在服务众多的分布式环境下,其实是有多个线程同时访问的同一个数据,这显然是不符合要求的。

·>分布式锁的概念:

分布式锁指的是,所有服务中的所有线程都去获得同一把锁,但只有一个线程可以成功的获得锁,其他没有获得锁的线程必须全部等待,等到获得锁的线程释放掉锁之后获得了锁才能进行操作。Redis官网中,set key value有个带有NX参数的命令,这是一个原子性加锁的命令,指的是此key没有被lock是,当前线程才能加锁,如果已经被占用,就不能加锁。

redis实现分布式锁的原理?

1.抢占分布式锁:

Java代码中的实现:

Boolean lock = redisTemplate.opsForValue().setIfAbsent( "lock","111");
·如果加锁成功(lock = true)**,就先执行相应的业务,
 然后释放掉锁:redisTemplate .delete(key: "lock" );
·如果加锁失败(lock = false)**,就通过自旋的方式进行重试(比如递归调用当前方法)。

注意:
为了防止在执行删锁操作之前,程序因为出现异常导致在还没有执行到删锁命令之前,程序就直接抛出异常退出,导致锁没有释放造成最终死锁的问题。(可能会有人想到,把删锁操作放在finally里以保证删锁操作一定被执行到,但是万一在执行删锁操作的过程中,电脑死机了呢!结果锁还是没有被成功的释放掉,依然会出现死锁现象。)于是,初步想到的解决方式就是在加锁的时候,就给这个锁设置一个过期时间。这样的话,即使我们由于各种原因没有成功的释放锁,redis也会根据过期时间,自动的帮助我们释放掉锁。
 

2.加锁的同时设置过期时间:

在成功获取到锁之后,执行删锁操作之前,给锁lock设置一个过期时间,例如30秒。

redisTemplate.expire( "lock" , 30, TimeUnit.SECONDS);

这样一来,即使我们自己没有删除掉锁,到到了过期时间后,redis也会帮我们自动删除掉。

注意:
由于加锁和设置锁的过期时间这两步操作不是原子性的,所以可能会在这之间出现问题,导致还没来得及设置锁的过期时间,程序就中断了。所以,需要加锁和设置过期时间这两步必须是原子性不可分割的操作。
Redis中的原子性命令,set lock 111 EX 30 NX ,表示key为lock,值为111,有效时间是30秒,是个NX的原子性加锁操作,可以保证加锁和过期时间这两个操作要么同时成功,要么同时失败。
Java中的代码是:

Boolean lock = redisTemp1ate.opsForValue().setIfAbsent("lock" , "111",30,TimeUnit.SECONDS);

二、模拟分布式锁的实现

(模拟抢票系统来实现分布式锁的实现)

  2.1、创建数据库

Redis实现分布式锁原理(面试重点)

2.2、导入对应的依赖文件

<!--        数据库-->
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.0</version>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.26</version>
    </dependency>
    <dependency>    
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.8</version>
    </dependency>

    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.8</version>
    </dependency>

    <!--        引入redission-->
    <dependency>
    <groupId>com.github.hiwepy</groupId>
    <artifactId>redisson-plus-spring-boot-starter</artifactId>
    <version>2.0.0.RELEASE</version>
    </dependency>

 2.3、配置Yml文件信息

server:
  port: 81

spring:
  datasource:
    url: jdbc:mysql:///db3
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver

  redis:
    host: 192.168.247.130
    port: 6379

    jedis:
      pool:
        max-idle: 5
        max-active: 10
        max-wait: 5000
#模拟抢票线程
winnum: 1

#开启第二个进程服务
---
server:
  port: 82

winnum: 2

spring:
  profiles: win2

2.4、创建对应的pojo

@Data
public class Ticket {
    private Integer id;
    private Integer count;
    private String identifier;
    private String from1;
    private String to1;
}

2.5、Mapper继承BaseMapper实现dao层的代码信息

public interface TicketMapper extends BaseMapper<Ticket> {
}

2.6、创建接口来测试分布式锁的实现

@RestController
@RequestMapping("/ticket")
public class TicketController {

    @Autowired
    private TicketMapper ticketMapper;

    @Value("${winnum}")
    private Integer winnum;

    @Autowired
    private StringRedisTemplate redisTemplate;

    private Object lock = new Object();

    private static final String LOCK_PREFIX = "ticket:lock:";
    private static final String LOCK_VALUE_PREFIX = "TICKET:VALUE:";

    @GetMapping("/sell/{id}")
    public void sell(@PathVariable("id") Integer id) throws InterruptedException {

        while (true){
            //加一个分布式锁:(买不同票,不冲突,买相同票才会加锁)
            //设置成功,加锁成功,设置失败,加锁失败(这个方法内部使用的是setnx指令)
            //问题二:当业务没有执行完成,锁超时释放了--解决问题的方式,是给这个锁超时时间续时(开启一个守护线程,当程序中所有线程都是守护线程时,会自动退出)

            //看门狗
            Thread thread = new Thread(()->{
                //续时
                while (true) {
                    Long expire = redisTemplate.getExpire(LOCK_PREFIX + id);
                    //在续时时,要判断,当前业务是否由我负责
                    //获取当前获取锁的窗口,如果当前获取锁的窗口就是我们当前守护线程所在窗口,续时
                    String value = redisTemplate.opsForValue().get(LOCK_PREFIX + id);
                    if (expire != null) {
                        if (expire <= 2 && (LOCK_VALUE_PREFIX + winnum).equals(value)) {
                            redisTemplate.expire(LOCK_PREFIX + id, 3, TimeUnit.SECONDS);
                        }
                    }
                }
            });
            //设置当前线程为守护线程
            thread.setDaemon(true);
            thread.start();

            //问题一:添加过期时间,防止进程非正常退出,锁对象无法释放的问题  (setnx实现的分布式锁,是不可重入的 -- 实现可重入锁,需要使用hash结构)
            //先获取key对应的值,判断值是否是当前进程拿到锁,是将这个value+1 -- lua脚本 (Redisson)
            Boolean isLock = redisTemplate.opsForValue().setIfAbsent(LOCK_PREFIX + id, LOCK_VALUE_PREFIX+winnum,3, TimeUnit.SECONDS);
            if (isLock) {
                Ticket ticket = ticketMapper.selectById(id);
                try {
                    //查询是否有票
                    if (ticket.getCount() > 0) {
                        //有票
                        System.out.println(winnum + "窗口正在卖出第" + ticket.getCount() + "张票");

                        //模拟卖票耗时
                        Thread.sleep(5000);

                        ticket.setCount(ticket.getCount() - 1);

                        //将新的票数设置到数据库
                        ticketMapper.updateById(ticket);

                        System.out.println(winnum + "窗口卖出票后,剩余票数为: " + ticket.getCount());
                    } else {
                        //没票
                        break;
                    }
                }finally {
                    //模拟进程挂掉,让锁无法释放
                    if (winnum == 1 && ticket.getCount()<95){
                        int i = 1/0;
                    }

                    //释放锁
                    redisTemplate.delete(LOCK_PREFIX+id);
                }
            }
        }

    }
}

上面这个锁还有一个问题,不可重入的。如果我们要实现可重入锁,那么需要使用hash结构。redisson就是使用的hash结构实现可重入锁。但是原理和上面讲的一样。

三、衍生出创建分布式锁的整个流程

问题一:传统单进程,synchronized来实现加锁,当分布式进程如何实现加锁

        答:采用redis在外部给程序进行上锁

                redisTemplate.opsForValue().setIfAbsent();方法,返回布尔类型

                如果已经存在值,返回flase,如果不存在,返回true

问题二:才锁redis的setifAbsent上锁之后,如何解锁

        答:两种方式实现

                方式一:

                业务需要try finally 当业务完成时,在finally里面删除对应的key值

                方式二:

                设置锁的过期时间,来防止进程出错导致无法释放锁

问题三:业务时间大于key过期时间,如果处理

        答:加上看门狗

                当业务没有执行完成,锁超时释放了--解决问题的方式,是给这个锁超时时间续时(开启一个守护线程,当程序中所有线程都是守护线程时,会自动退出,推出后,就不会在给时间续时)文章来源地址https://www.toymoban.com/news/detail-497705.html

到了这里,关于Redis实现分布式锁原理(面试重点)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis实战案例14-分布式锁的基本原理、不同实现方法对比以及基于Redis进行实现思路

    基于数据库的分布式锁:这种方式使用数据库的特性来实现分布式锁。具体流程如下: 获取锁:当一个节点需要获得锁时,它尝试在数据库中插入一个特定的唯一键值(如唯一约束的主键),如果插入成功,则表示获得了锁。 释放锁:当节点完成任务后,通过删除该唯一键

    2024年02月13日
    浏览(52)
  • 为什么会有分布式锁?分布式锁实现方案

    分布式锁是控制分布式系统之间同步访问共享资源的一种方式。分布式环境下会出现资源竞争的地方都需要分布式锁的协调。 分布式锁的作用:在整个系统提供一个全局、唯一的锁,在分布式系统中每个系统在进行相关操作的时候需要获取到该锁,才能执行相应操作。 服务

    2024年02月08日
    浏览(50)
  • Redis集群(分布式缓存):详解持久化、主从同步原理、哨兵机制、Cluster分片集群,实现高并发高可用

            单机式Redis存在以下问题,因此需要Redis集群化来解决这些问题        Redis数据快照,简单来说就是 把内存中的所有数据都记录到磁盘中 。当Redis实例故障重启后,从 磁盘读取快照文件,恢复数据 。快照文件称为RDB文件,默认是保存在当前运行目录。     (1)

    2024年02月08日
    浏览(58)
  • 分布式系统面试全集通第一篇(dubbo+redis+zookeeper----分布式+CAP+BASE+分布式事务+分布式锁)

    什么是分布式 一个系统各组件分别部署在不同服务器。彼此通过网络通信和协调的系统。 也可以指多个不同组件分布在网络上互相协作,比如说电商网站 也可以一个组件的多个副本组成集群,互相协作如同一个组件,比如数据存储服务中为了数据不丢失而采取的多个服务备

    2024年04月11日
    浏览(50)
  • (快手一面)分布式系统是什么?为什么要分布式系统?分布式环境下会有哪些问题?分布式系统是如何实现事务的?

    《分布式系统原理与泛型》中这么定义分布式系统: “ 分布式系统是若干独立计算机的集合, 这些计算机对于用户来说就像单个相关系统 ”, 分布式系统(distributed system)是建立在网络之上的软件系统。 就比如:用户在使用京东这个分布式系统的时候,会感觉是在使用一

    2024年02月08日
    浏览(70)
  • Redis7实战加面试题-高阶篇(手写Redis分布式锁)

    面试题: 1.Redis除了拿来做缓存,你还见过基于Redis的什么用法? 数据共享,分布式session分布式锁 全局ID 计算器、点赞位统计 购物车 轻量级消息队列(list,stream) 抽奖 点赞、签到、打卡 差集交集并集,用户关注、可能认识的人,推荐模型 热点新闻、热搜排行榜 2.Redis做分

    2024年02月07日
    浏览(37)
  • 【面试题24】你是如何使用Redis分布式锁的

    本文已收录于PHP全栈系列专栏:PHP面试专区。 计划将全覆盖PHP开发领域所有的面试题, 对标资深工程师/架构师序列 ,欢迎大家提前关注锁定。 Redis分布式锁是一种利用Redis实现的分布式锁机制。它通过在共享的Redis实例上设置一个特定的键值对来实现对资源的互斥访问。今

    2024年02月11日
    浏览(38)
  • 【Java程序员面试专栏 分布式中间件】Redis 核心面试指引

    关于Redis部分的核心知识进行一网打尽,包括Redis的基本概念,基本架构,工作流程,存储机制等,通过一篇文章串联面试重点,并且帮助加强日常基础知识的理解,全局思维导图如下所示 明确redis的特性、应用场景和数据结构 Redis是一个 开源的、内存中的数据结构存储系统

    2024年02月20日
    浏览(46)
  • Redis学习(三)分布式缓存、多级缓存、Redis实战经验、Redis底层原理

    单节点Redis存在着: 数据丢失问题:单节点宕机,数据就丢失了。 并发能力和存储能力问题:单节点能够满足的并发量、能够存储的数据量有限。 故障恢复问题:如果Redis宕机,服务不可用,需要一种自动的故障恢复手段。 RDB持久化 RDB(Redis database backup file,Redis数据库备份

    2024年02月16日
    浏览(41)
  • 缓存面试解析:穿透、击穿、雪崩,一致性、分布式锁、Redis过期,海量数据查找

    在程序内部使用缓存,比如使用map等数据结构作为内部缓存,可以快速获取对象。通过将经常使用的数据存储在缓存中,可以减少对数据库的频繁访问,从而提高系统的响应速度和性能。缓存可以将数据保存在内存中,读取速度更快,能够大大缩短数据访问的时间,提升用户

    2024年02月14日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包