Redis 面试问题集合 III - 副本,Cluster,高可用方案,数据丢失,哨兵,分布式锁

Author: Xcourse   2023-Mar-21 00:31   Reads: 975

欢迎加入微信工作内部分享群,每天发布新的精选高薪工作。

官方邮箱:enquiry@xcourse.sg

微信分享群:@新加坡工作内部分享群

WhatsApp群:@Singapore Jobs & Internships

Telegram中文群:@新加坡工作内部分享群

Telegram英文群:@Singapore Jobs

------------------------------------------------------------------------------------------------------

 

42. 介绍下Redis单副本             

Redis单副本,采用单个Redis节点部署架构,没有备用节点实时同步数据,不提供数据持久化和备份策略,适用于数据可靠性要求不高的纯缓存业务场景。

image-20210901210427115

优点:

  • 架构简单,部署方便;
  • 高性价比:缓存使用时无需备用节点(单实例可用性可以用supervisor或crontab保证),当然为了满足业务的高可用性,也可以牺牲一个备用节点,但同时刻只有一个实例对外提供服务;
  • 高性能。

缺点:

  • 不保证数据的可靠性;
  • 在缓存使用,进程重启后,数据丢失,即使有备用的节点解决高可用性,但是仍然不能解决缓存预热问题,因此不适用于数据可靠性要求高的业务;
  • 高性能受限于单核CPU的处理能力(Redis是单线程机制),CPU为主要瓶颈,所以适合操作命令简单,排序、计算较少的场景。也可以考虑用Memcached替代。

           

43. 介绍下Redis多副本(主从)    

Redis多副本,采用主从(replication)部署结构,相较于单副本而言最大的特点就是主从实例间数据实时同步,并且提供数据持久化和备份策略。主从实例部署在不同的物理服务器上,根据公司的基础环境配置,可以实现同时对外提供服务和读写分离策略。

image-20210829103327631

优点:

  • 高可靠性:一方面,采用双机主备架构,能够在主库出现故障时自动进行主备切换,从库提升为主库提供服务,保证服务平稳运行;另一方面,开启数据持久化功能和配置合理的备份策略,能有效的解决数据误操作和数据异常丢失的问题;
  • 读写分离策略:从节点可以扩展主库节点的读能力,有效应对大并发量的读操作。

缺点:

  • 故障恢复复杂,如果没有RedisHA系统(需要开发),当主库节点出现故障时,需要手动将一个从节点晋升为主节点,同时需要通知业务方变更配置,并且需要让其它从库节点去复制新主库节点,整个过程需要人为干预,比较繁琐;
  • 主库的写能力受到单机的限制,可以考虑分片;
  • 主库的存储能力受到单机的限制,可以考虑Pika;
  • 原生复制的弊端在早期的版本中也会比较突出,如:Redis复制中断后,Slave会发起psync,此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时可能会造成毫秒或秒级的卡顿;又由于COW机制,导致极端情况下的主库内存溢出,程序异常退出或宕机;主库节点生成备份文件导致服务器磁盘IO和CPU(压缩)资源消耗;发送数GB大小的备份文件导致服务器出口带宽暴增,阻塞请求,建议升级到最新版本。

                    

44. 介绍下Redis Sentinel(哨兵)          

主从模式下,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这种方式并不推荐,实际生产中,我们优先考虑哨兵模式。这种模式下,master 宕机,哨兵会自动选举 master 并将其他的 slave 指向新的 master。

Redis Sentinel是社区版本推出的原生高可用解决方案,其部署架构主要包括两部分:Redis Sentinel集群和Redis数据集群。

其中Redis Sentinel集群是由若干Sentinel节点组成的分布式集群,可以实现故障发现、故障自动转移、配置中心和客户端通知。Redis Sentinel的节点数量要满足2n+1(n>=1)的奇数个。

image-20210829103343110

优点:

  • Redis Sentinel集群部署简单;
  • 能够解决Redis主从模式下的高可用切换问题;
  • 很方便实现Redis数据节点的线形扩展,轻松突破Redis自身单线程瓶颈,可极大满足Redis大容量或高性能的业务需求;
  • 可以实现一套Sentinel监控一组Redis数据节点或多组数据节点。

缺点:

  • 部署相对Redis主从模式要复杂一些,原理理解更繁琐;
  • 资源浪费,Redis数据节点中slave节点作为备份节点不提供服务;
  • Redis Sentinel主要是针对Redis数据节点中的主节点的高可用切换,对Redis的数据节点做失败判定分为主观下线和客观下线两种,对于Redis的从节点有对节点做主观下线操作,并不执行故障转移。
  • 不能解决读写分离问题,实现起来相对复杂。

              

45. 介绍下Redis Cluster                   

Redis 的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台 Redis 服务器都存储相同的数据,很浪费内存,所以在 Redis3.0 上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,对数据进行分片,也就是说每台 Redis 节点上存储不同的内容。

Redis Cluster是社区版推出的Redis分布式集群解决方案,主要解决Redis分布式方面的需求,比如,当遇到单机内存,并发和流量等瓶颈的时候,Redis Cluster能起到很好的负载均衡的目的。

Redis Cluster集群节点最小配置6个节点以上(3主3从),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。

Redis Cluster采用虚拟槽分区,所有的键根据哈希函数映射到0~16383个整数槽内,每个节点负责维护一部分槽以及槽所印映射的键值数据。

image-20210829103444245

优点:

  • 无中心架构;
  • 数据按照slot存储分布在多个节点,节点间数据共享,可动态调整数据分布;
  • 可扩展性:可线性扩展到1000多个节点,节点可动态添加或删除;
  • 高可用性:部分节点不可用时,集群仍可用。通过增加Slave做standby数据副本,能够实现故障自动failover,节点之间通过gossip协议交换状态信息,用投票机制完成Slave到Master的角色提升;
  • 降低运维成本,提高系统的扩展性和可用性。

缺点:

  • Client实现复杂,驱动要求实现Smart Client,缓存slots mapping信息并及时更新,提高了开发难度,客户端的不成熟影响业务的稳定性。目前仅JedisCluster相对成熟,异常处理部分还不完善,比如常见的“max redirect exception”。
  • 节点会因为某些原因发生阻塞(阻塞时间大于clutser-node-timeout),被判断下线,这种failover是没有必要的。
  • 数据通过异步复制,不保证数据的强一致性。
  • 多个业务使用同一套集群时,无法根据统计区分冷热数据,资源隔离性较差,容易出现相互影响的情况。
  • Slave在集群中充当“冷备”,不能缓解读压力,当然可以通过SDK的合理设计来提高Slave资源的利用率。
  • Key批量操作限制,如使用mset、mget目前只支持具有相同slot值的Key执行批量操作。对于映射为不同slot值的Key由于Keys不支持跨slot查询,所以执行mset、mget、sunion等操作支持不友好。
  • Key事务操作支持有限,只支持多key在同一节点上的事务操作,当多个Key分布于不同的节点上时无法使用事务功能。
  • Key作为数据分区的最小粒度,不能将一个很大的键值对象如hash、list等映射到不同的节点。
  • 不支持多数据库空间,单机下的Redis可以支持到16个数据库,集群模式下只能使用1个数据库空间,即db 0。
  • 复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。
  • 避免产生hot-key,导致主库节点成为系统的短板。
  • 避免产生big-key,导致网卡撑爆、慢查询等。
  • 重试时间应该大于cluster-node-time时间。
  • Redis Cluster不建议使用pipeline和multi-keys操作,减少max redirect产生的场景。

 

46. 介绍下Redis自研     

Redis自研的高可用解决方案,主要体现在配置中心、故障探测和failover的处理机制上,通常需要根据企业业务的实际线上环境来定制化。

image-20210829103426922

优点:

  • 高可靠性、高可用性;
  • 自主可控性高;
  • 贴切业务实际需求,可缩性好,兼容性好。

缺点:

  • 实现复杂,开发成本高;
  • 需要建立配套的周边设施,如监控,域名服务,存储元数据信息的数据库等;
  • 维护成本高。

                   

47. Redis高可用方案具体怎么实施?         

使用官方推荐的哨兵(sentinel)机制就能实现,当主节点出现故障时,由Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。它有四个主要功能:

  • 集群监控,负责监控Redis master和slave进程是否正常工作。
  • 消息通知,如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
  • 故障转移,如果master node挂掉了,会自动转移到slave node上。
  • 配置中心,如果故障转移发生了,通知client客户端新的master地址。

               

48. 了解主从复制的原理吗?             

1、主从架构的核心原理

当启动一个slave node的时候,它会发送一个PSYNC命令给master node

如果这是slave node重新连接master node,那么master node仅仅会复制给slave部分缺少的数据; 否则如果是slave node第一次连接master node,那么会触发一次full resynchronization

开始full resynchronization的时候,master会启动一个后台线程,开始生成一份RDB快照文件,同时还会将从客户端收到的所有写命令缓存在内存中。RDB文件生成完毕之后,master会将这个RDB发送给slave,slave会先写入本地磁盘,然后再从本地磁盘加载到内存中。然后master会将内存中缓存的写命令发送给slave,slave也会同步这些数据。

slave node如果跟master node有网络故障,断开了连接,会自动重连。master如果发现有多个slave node都来重新连接,仅仅会启动一个rdb save操作,用一份数据服务所有slave node。

2、主从复制的断点续传

从Redis 2.8开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份

master node会在内存中常见一个backlog,master和slave都会保存一个replica offset还有一个master id,offset就是保存在backlog中的。如果master和slave网络连接断掉了,slave会让master从上次的replica offset开始继续复制

但是如果没有找到对应的offset,那么就会执行一次resynchronization

3、无磁盘化复制

master在内存中直接创建rdb,然后发送给slave,不会在自己本地落地磁盘了

repl-diskless-sync repl-diskless-sync-delay,等待一定时长再开始复制,因为要等更多slave重新连接过来

4、过期key处理

slave不会过期key,只会等待master过期key。如果master过期了一个key,或者通过LRU淘汰了一个key,那么会模拟一条del命令发送给slave。

           

49. 由于主从延迟导致读取到过期数据怎么处理?                   

1、通过scan命令扫库:当Redis中的key被scan的时候,相当于访问了该key,同样也会做过期检测,充分发挥Redis惰性删除的策略。这个方法能大大降低了脏数据读取的概率,但缺点也比较明显,会造成一定的数据库压力,否则影响线上业务的效率。

2、Redis加入了一个新特性来解决主从不一致导致读取到过期数据问题,增加了key是否过期以及对主从库的判断,如果key已过期,当前访问的master则返回null;当前访问的是从库,且执行的是只读命令也返回null。

     

50. 主从复制的过程中如果因为网络原因停止复制了会怎么样?                

如果出现网络故障断开连接了,会自动重连的,从Redis 2.8开始,就支持主从复制的断点续传,可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。

master如果发现有多个slave node都来重新连接,仅仅会启动一个rdb save操作,用一份数据服务所有slave node。

master node会在内存中创建一个backlog,master和slave都会保存一个replica offset,还有一个master id,offset就是保存在backlog中的。如果master和slave网络连接断掉了,slave会让master从上次的replica offset开始继续复制。

但是如果没有找到对应的offset,那么就会执行一次resynchronization全量复制。

        

51. Redis主从架构数据会丢失吗,为什么?        

有两种数据丢失的情况:

1、异步复制导致的数据丢失:因为master -> slave的复制是异步的,所以可能有部分数据还没复制到slave,master就宕机了,此时这些部分数据就丢失了。

2、脑裂导致的数据丢失:某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着,此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master。这个时候,集群里就会有两个master,也就是所谓的脑裂。此时虽然某个slave被切换成了master,但是可能client还没来得及切换到新的master,还继续写向旧master的数据可能也丢失了。因此旧master再次恢复的时候,会被作为一个slave挂到新的master上去,自己的数据会清空,重新从新的master复制数据。

                

52. 如何解决主从架构数据丢失的问题?                 

数据丢失的问题是不可避免的,但是我们可以尽量减少。

在Redis的配置文件里设置参数

min-slaves-to-write 1

min-slaves-max-lag 10

min-slaves-to-write默认情况下是0,min-slaves-max-lag默认情况下是10。

上面的配置的意思是要求至少有1个slave,数据复制和同步的延迟不能超过10秒。如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master就不会再接收任何请求了。

减小min-slaves-max-lag参数的值,这样就可以避免在发生故障时大量的数据丢失,一旦发现延迟超过了该值就不会往master中写入数据。

那么对于client,我们可以采取降级措施,将数据暂时写入本地缓存和磁盘中,在一段时间后重新写入master来保证数据不丢失;也可以将数据写入kafka消息队列,隔一段时间去消费kafka中的数据。

       

53. Redis哨兵是怎么工作的?               

  1. 每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令。
  2. 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被当前 Sentinel 标记为主观下线。
  3. 如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
  4. 当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。
  5. 当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 (在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令 )。
  6. 若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会变成主观下线。若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
  7. sentinel节点会与其他sentinel节点进行“沟通”,投票选举一个sentinel节点进行故障处理,在从节点中选取一个主节点,其他从节点挂载到新的主节点上自动复制新主节点的数据。

                

54. 故障转移时会从剩下的slave选举一个新的master,被选举为master的标准是什么?        

如果一个master被认为odown了,而且majority哨兵都允许了主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个slave来,会考虑slave的一些信息。

1、跟master断开连接的时长。

如果一个slave跟master断开连接已经超过了down-after-milliseconds的10倍,外加master宕机的时长,那么slave就被认为不适合选举为master.

( down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state

 

2、slave优先级。

按照slave优先级进行排序,slave priority越低,优先级就越高

3、复制offset。

如果slave priority相同,那么看replica offset,哪个slave复制了越多的数据,offset越靠后,优先级就越高

4、run id

如果上面两个条件都相同,那么选择一个run id比较小的那个slave。

                

55. 同步配置的时候其他哨兵根据什么更新自己的配置呢?                    

执行切换的那个哨兵,会从要切换到的新master(salve->master)那里得到一个configuration epoch,这就是一个version号,每次切换的version号都必须是唯一的。

如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待failover-timeout时间,然后接替继续执行切换,此时会重新获取一个新的configuration epoch 作为新的version号。

这个version号就很重要了,因为各种消息都是通过一个channel去发布和监听的,所以一个哨兵完成一次新的切换之后,新的master配置是跟着新的version号的,其他的哨兵都是根据版本号的大小来更新自己的master配置的。

    

56. 为什么Redis哨兵集群只有2个节点无法正常工作?            

哨兵集群必须部署2个以上节点。

如果两个哨兵实例,即两个Redis实例,一主一从的模式。

则Redis的配置quorum=1,表示一个哨兵认为master宕机即可认为master已宕机。

但是如果是机器1宕机了,那哨兵1和master都宕机了,虽然哨兵2知道master宕机了,但是这个时候,需要majority,也就是大多数哨兵都是运行的,2个哨兵的majority就是2(2的majority=2,3的majority=2,5的majority=3,4的majority=2),2个哨兵都运行着,就可以允许执行故障转移。

但此时哨兵1没了就只有1个哨兵了了,此时就没有majority来允许执行故障转移,所以故障转移不会执行。

            

57. Redis cluster中是如何实现数据分布的?这种方式有什么优点?     

Redis cluster有固定的16384个hash slot(哈希槽),对每个key计算CRC16值,然后对16384取模,可以获取key对应的hash slot。

Redis cluster中每个master都会持有部分slot(槽),比如有3个master,那么可能每个master持有5000多个hash slot。

hash slot让node的增加和移除很简单,增加一个master,就将其他master的hash slot移动部分过去,减少一个master,就将它的hash slot移动到其他master上去。每次增加或减少master节点都是对16384取模,而不是根据master数量,这样原本在老的master上的数据不会因master的新增或减少而找不到。并且增加或减少master时Redis cluster移动hash slot的成本是非常低的。

                   

58. Redis cluster节点间通信是什么机制?                   

Redis cluster节点间采取gossip协议进行通信,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,则改节点会把数据不断地发送给其他节点让其他节点进行数据变更。通过节点互相之间不断通信来保持整个集群所有节点的数据是完整的。

主要交换故障信息、节点的增加和移除、hash slot信息等。

这种机制的好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力;

缺点则是元数据更新有延时,可能导致集群的一些操作会有一些滞后。

     

59. 什么是分布式锁?为什么用分布式锁?            

锁在程序中的作用就是同步工具,保证共享资源在同一时刻只能被一个线程访问,Java中的锁我们都很熟悉了,像synchronized 、Lock都是我们经常使用的,但是Java的锁只能保证单机的时候有效,分布式集群环境就无能为力了,这个时候我们就需要用到分布式锁。

分布式锁,顾名思义,就是分布式项目开发中用到的锁,可以用来控制分布式系统之间同步访问共享资源。

思路是:在整个系统提供一个全局、唯一的获取锁的“东西”,然后每个系统在需要加锁时,都去问这个“东西”拿到一把锁,这样不同的系统拿到的就可以认为是同一把锁。至于这个“东西”,可以是Redis、Zookeeper,也可以是数据库。

  • 一般来说,分布式锁需要满足的特性有这么几点:

1、互斥性:在任何时刻,对于同一条数据,只有一台应用可以获取到分布式锁;

2、高可用性:在分布式场景下,一小部分服务器宕机不影响正常使用,这种情况就需要将提供分布式锁的服务以集群的方式部署;

3、防止锁超时:如果客户端没有主动释放锁,服务器会在一段时间之后自动释放锁,防止客户端宕机或者网络不可达时产生死锁;

4、独占性:加锁解锁必须由同一台服务器进行,也就是锁的持有者才可以释放锁,不能出现你加的锁,别人给你解锁了。

            

60. 常见的分布式锁有哪些解决方案?          

实现分布式锁目前有三种流行方案,即基于关系型数据库、Redis、ZooKeeper 的方案

1、基于关系型数据库,如MySQL

基于关系型数据库实现分布式锁,是依赖数据库的唯一性来实现资源锁定,比如主键和唯一索引等。

  • 缺点:

这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。

这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。

这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。

这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。

2、基于Redis实现

  • 优点:

Redis 锁实现简单,理解逻辑简单,性能好,可以支撑高并发的获取、释放锁操作。

  • 缺点:

Redis 容易单点故障,集群部署,并不是强一致性的,锁的不够健壮;

key 的过期时间设置多少不明确,只能根据实际情况调整;

需要自己不断去尝试获取锁,比较消耗性能。

3、基于zookeeper

  • 优点:

zookeeper 天生设计定位就是分布式协调,强一致性,锁很健壮。如果获取不到锁,只需要添加一个监听器就可以了,不用一直轮询,性能消耗较小。

  • 缺点:

在高请求高并发下,系统疯狂的加锁释放锁,最后 zk 承受不住这么大的压力可能会存在宕机的风险。

              

61. Redis实现分布式锁            

分布式锁的三个核心要素

1、加锁

使用setnx来加锁。key是锁的唯一标识,按业务来决定命名,value这里设置为test。

setx key test

 

当一个线程执行setnx返回1,说明key原本不存在,该线程成功得到了锁;当一个线程执行setnx返回0,说明key已经存在,该线程抢锁失败;

 

2、解锁

有加锁就得有解锁。当得到的锁的线程执行完任务,需要释放锁,以便其他线程可以进入。释放锁的最简单方式就是执行del指令。

del key

 

释放锁之后,其他线程就可以继续执行setnx命令来获得锁。

 

3、锁超时

锁超时知道的是:如果一个得到锁的线程在执行任务的过程中挂掉,来不及显式地释放锁,这块资源将会永远被锁住,别的线程北向进来。

所以,setnx的key必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一段时间后自动释放。setnx不支持超时参数,所以需要额外指令,

expire key 30

 

上述分布式锁存在的问题

通过上述setnx 、del和expire实现的分布式锁还是存在着一些问题。

  • 1、SETNX 和 EXPIRE 非原子性

假设一个场景中,某一个线程刚执行setnx,成功得到了锁。此时setnx刚执行成功,还未来得及执行expire命令,节点就挂掉了。此时这把锁就没有设置过期时间,别的线程就再也无法获得该锁。

解决措施:

由于setnx指令本身是不支持传入超时时间的,而在Redis2.6.12版本上为set指令增加了可选参数, 用法如下:

SET key value [EX seconds][PX milliseconds] [NX|XX]

 

EX second: 设置键的过期时间为second秒;

PX millisecond:设置键的过期时间为millisecond毫秒;

NX:只在键不存在时,才对键进行设置操作;

XX:只在键已经存在时,才对键进行设置操作;

SET操作完成时,返回OK,否则返回nil。

  • 2、锁误解除

如果线程 A 成功获取到了锁,并且设置了过期时间 30 秒,但线程 A 执行时间超过了 30 秒,锁过期自动释放,此时线程 B 获取到了锁;随后 A 执行完成,线程 A 使用 DEL 命令来释放锁,但此时线程 B 加的锁还没有执行完成,线程 A 实际释放的线程 B 加的锁。

解决办法:

在del释放锁之前加一个判断,验证当前的锁是不是自己加的锁。

具体在加锁的时候把当前线程的id当做value,可生成一个 UUID 标识当前线程,在删除之前验证key对应的value是不是自己线程的id。

还可以使用 lua 脚本做验证标识和解锁操作。

  • 3、超时解锁导致并发

如果线程 A 成功获取锁并设置过期时间 30 秒,但线程 A 执行时间超过了 30 秒,锁过期自动释放,此时线程 B 获取到了锁,线程 A 和线程 B 并发执行。

A、B 两个线程发生并发显然是不被允许的,一般有两种方式解决该问题:

将过期时间设置足够长,确保代码逻辑在锁释放之前能够执行完成。

为获取锁的线程增加守护线程,为将要过期但未释放的锁增加有效时间。

  • 4、不可重入

当线程在持有锁的情况下再次请求加锁,如果一个锁支持一个线程多次加锁,那么这个锁就是可重入的。如果一个不可重入锁被再次加锁,由于该锁已经被持有,再次加锁会失败。Redis 可通过对锁进行重入计数,加锁时加 1,解锁时减 1,当计数归 0 时释放锁。

  • 5、无法等待锁释放

上述命令执行都是立即返回的,如果客户端可以等待锁释放就无法使用。

可以通过客户端轮询的方式解决该问题,当未获取到锁时,等待一段时间重新获取锁,直到成功获取锁或等待超时。这种方式比较消耗服务器资源,当并发量比较大时,会影响服务器的效率。

另一种方式是使用 Redis 的发布订阅功能,当获取锁失败时,订阅锁释放消息,获取锁成功后释放时,发送锁释放消息。

具体实现参考:https://xiaomi-info.github.io/2019/12/17/Redis-distributed-lock/

 

62. RedLock的原理            

假设有5个完全独立的Redis主服务器

1、获取当前时间戳

2、client尝试按照顺序使用相同的key,value获取所有Redis服务的锁,在获取锁的过程中的获取时间比锁过期时间短很多,这是为了不要过长时间等待已经关闭的Redis服务。并且试着获取下一个Redis实例。

比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁

3、client通过获取所有能获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个Redis实例成功获取锁,才算真正的获取锁成功

4、如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s(其实应该再减去时钟漂移);

5、如果客户端由于某些原因获取锁失败,便会开始解锁所有Redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁

算法示意图如下:

image-20210829131128229

 

 

 

 


Tags: interview redis backend

Topics: 面经