【java常见的面试题】Redis分布式锁如何实现 ?

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: Java基础的面试题【Redis篇】

Redis分布式锁主要依靠一个SETNX指令实现的 , 这条命令的含义就是“SET if Not Exists”,即不存在的时候才会设置值。

只有在key不存在的情况下,将键key的值设置为value。如果key已经存在,则SETNX命令不做任何操作。

这个命令的返回值如下。

  • 命令在设置成功时返回1。
  • 命令在设置失败时返回0。

假设此时有线程A和线程B同时访问临界区代码,假设线程A首先执行了SETNX命令,并返回结果1,继续向下执行。而此时线程B再次执行SETNX命令时,返回的结果为0,则线程B不能继续向下执行。只有当线程A执行DELETE命令将设置的锁状态删除时,线程B才会成功执行SETNX命令设置加锁状态后继续向下执行

Boolean isLocked = stringRedisTemplate.opsForValue().setIfAbsent(PRODUCT_ID, "binghe");

当然我们在使用分布式锁的时候也不能这么简单, 会考虑到一些实际场景下的问题 , 例如 :

  1. 死锁问题

    在使用分布式锁的时候, 如果因为一些原因导致系统宕机, 锁资源没有被释放, 就会产生死锁

    解决的方案 : 上锁的时候设置锁的超时时间

    Boolean isLocked = stringRedisTemplate.opsForValue().setIfAbsent(PRODUCT_ID, "binghe", 30, TimeUnit.SECONDS);
    
  2. 锁超时问题

    如果业务执行需要的时间, 超过的锁的超时时间 , 这个时候业务还没有执行完成, 锁就已经自动被删除了

    其他请求就能获取锁, 操作这个资源 , 这个时候就会出现并发问题 , 解决的方案 :

    1. 引入Redis的watch dog机制, 自动为锁续期
    2. 开启子线程 , 每隔20S运行一次, 重新设置锁的超时时间
  3. 归一问题

    如果一个线程获取了分布式锁, 但是这个线程业务没有执行完成之前 , 锁被其他的线程删掉了 , 又会出现线程并发问题 , 这个时候就需要考虑归一化问题

    就是一个线程执行了加锁操作后,后续必须由这个线程执行解锁操作,加锁和解锁操作由同一个线程来完成。

    为了解决只有加锁的线程才能进行相应的解锁操作的问题,那么,我们就需要将加锁和解锁操作绑定到同一个线程中,可以使用ThreadLocal来解决这个问题 , 加锁的时候生成唯一标识保存到ThreadLocal , 并且设置到锁的值中 , 释放锁的时候, 判断线程中的唯一标识和锁的唯一标识是否相同, 只有相同才会释放

    public class RedisLockImpl implements RedisLock{
          
          
     @Autowired
     private StringRedisTemplate stringRedisTemplate;
    
     private ThreadLocal<String> threadLocal = new ThreadLocal<String>();
    
     @Override
     public boolean tryLock(String key, long timeout, TimeUnit unit){
          
          
         String uuid = UUID.randomUUID().toString();
         threadLocal.set(uuid);
         return stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);
     }
     @Override
     public void releaseLock(String key){
          
          
         //当前线程中绑定的uuid与Redis中的uuid相同时,再执行删除锁的操作
         if(threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))){
          
          
           stringRedisTemplate.delete(key);   
         }
     }
    }
    
  4. 可重入问题

    当一个线程成功设置了锁标志位后,其他的线程再设置锁标志位时,就会返回失败。

    还有一种场景就是在一个业务中, 有个操作都需要获取到锁, 这个时候第二个操作就无法获取锁了 , 操作会失败

    例如 : 下单业务中, 扣减商品库存会给商品加锁, 增加商品销量也需要给商品加锁 , 这个时候需要获取二次锁

    第二次获取商品锁就会失败 , 这就需要我们的分布式锁能够实现可重入

    实现可重入锁最简单的方式就是使用计数器 , 加锁成功之后计数器 + 1 , 取消锁之后计数器 -1 , 计数器减为0 , 真正从Redis删除锁

    public class RedisLockImpl implements RedisLock{
          
          
     @Autowired
     private StringRedisTemplate stringRedisTemplate;
    
     private ThreadLocal<String> threadLocal = new ThreadLocal<String>();
    
     private ThreadLocal<Integer> threadLocalInteger = new ThreadLocal<Integer>();
    
     @Override
     public boolean tryLock(String key, long timeout, TimeUnit unit){
          
          
         Boolean isLocked = false;
         if(threadLocal.get() == null){
          
          
             String uuid = UUID.randomUUID().toString();
          threadLocal.set(uuid);
             isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);
         }else{
          
          
             isLocked = true;   
         }
         //加锁成功后将计数器加1
         if(isLocked){
          
          
             Integer count = threadLocalInteger.get() == null ? 0 : threadLocalInteger.get();
             threadLocalInteger.set(count++);
         }
         return isLocked;
     }
    
     @Override
     public void releaseLock(String key){
          
          
         //当前线程中绑定的uuid与Redis中的uuid相同时,再执行删除锁的操作
         if(threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))){
          
          
             Integer count = threadLocalInteger.get();
             //计数器减为0时释放锁
             if(count == null || --count <= 0){
          
          
               stringRedisTemplate.delete(key);      
             }
         }
     }
    }
    
  5. 阻塞与非阻塞问题

    在使用分布式锁的时候 , 如果当前需要操作的资源已经加了锁, 这个时候会获取锁失败, 直接向用户返回失败信息 , 用户的体验非常不好 , 所以我们在实现分布式锁的时候, 我们可以将后续的请求进行阻塞,直到当前请求释放锁后,再唤醒阻塞的请求获得分布式锁来执行方法。

    具体的实现就是参考自旋锁的思想, 获取锁失败自选获取锁, 直到成功为止 , 当然为了防止多条线程自旋带来的系统资料消耗, 可以设置一个自旋的超时时间 , 超过时间之后, 自动终止线程 , 返回失败信息

    image-20220527110835621.png

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
21天前
|
缓存 Java 关系型数据库
【Java面试题汇总】ElasticSearch篇(2023版)
倒排索引、MySQL和ES一致性、ES近实时、ES集群的节点、分片、搭建、脑裂、调优。
【Java面试题汇总】ElasticSearch篇(2023版)
|
21天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
175 37
|
11天前
|
NoSQL Java Redis
面试官:项目中如何实现分布式锁?
面试官:项目中如何实现分布式锁?
39 6
面试官:项目中如何实现分布式锁?
|
21天前
|
设计模式 安全 算法
【Java面试题汇总】设计模式篇(2023版)
谈谈你对设计模式的理解、七大原则、单例模式、工厂模式、代理模式、模板模式、观察者模式、JDK中用到的设计模式、Spring中用到的设计模式
【Java面试题汇总】设计模式篇(2023版)
|
21天前
|
存储 关系型数据库 MySQL
【Java面试题汇总】MySQL数据库篇(2023版)
聚簇索引和非聚簇索引、索引的底层数据结构、B树和B+树、MySQL为什么不用红黑树而用B+树、数据库引擎有哪些、InnoDB的MVCC、乐观锁和悲观锁、ACID、事务隔离级别、MySQL主从同步、MySQL调优
【Java面试题汇总】MySQL数据库篇(2023版)
|
21天前
|
存储 缓存 NoSQL
【Java面试题汇总】Redis篇(2023版)
Redis的数据类型、zset底层实现、持久化策略、分布式锁、缓存穿透、击穿、雪崩的区别、双写一致性、主从同步机制、单线程架构、高可用、缓存淘汰策略、Redis事务是否满足ACID、如何排查Redis中的慢查询
【Java面试题汇总】Redis篇(2023版)
|
8天前
|
JSON NoSQL Java
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
这篇文章介绍了在Java中使用Redis客户端的几种方法,包括Jedis、SpringDataRedis和SpringBoot整合Redis的操作。文章详细解释了Jedis的基本使用步骤,Jedis连接池的创建和使用,以及在SpringBoot项目中如何配置和使用RedisTemplate和StringRedisTemplate。此外,还探讨了RedisTemplate序列化的两种实践方案,包括默认的JDK序列化和自定义的JSON序列化,以及StringRedisTemplate的使用,它要求键和值都必须是String类型。
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
|
5天前
|
NoSQL 安全 关系型数据库
20)用 Redis 实现分布式锁
20)用 Redis 实现分布式锁
15 0
|
10天前
|
消息中间件 NoSQL Java
Java知识要点及面试题
该文档涵盖Java后端开发的关键知识点,包括Java基础、JVM、多线程、MySQL、Redis、Spring框架、Spring Cloud、Kafka及分布式系统设计。针对每个主题,文档列举了重要概念及面试常问问题,帮助读者全面掌握相关技术并准备面试。例如,Java基础部分涉及面向对象编程、数据类型、异常处理等;JVM部分则讲解内存结构、类加载机制及垃圾回收算法。此外,还介绍了多线程的生命周期、同步机制及线程池使用,数据库设计与优化,以及分布式系统中的微服务、RPC调用和负载均衡等。
|
存储 缓存 运维
史上最全Redis面试题及答案。
花了大量时间整理了这套Redis面试题 首发50题,绝无仅有,从入门到精通 从基础,高级知识点,再到集群,运维,方案… 弄明白了这些题可以说可以成为面霸了 面试官都得折服,Redis学得怎么样,都来检验下吧
史上最全Redis面试题及答案。
下一篇
无影云桌面