redis高阶篇

1、Redis为什么这么快?

1、基于内存存储实现

我们都知道内存读写是比在磁盘读写快很多的,Redis基于内存存储实现的数据库,相对于数据存在磁盘的Mysql数据库,省去磁盘I/O的消耗。

2、高效的数据结构

redis的数据结构是专门设计的,这些简单的数据的查找和操作的复杂度大部分都是O(1),因此性能快。

3、单线程省去了线程切换上下文带来的时间开销(Redis6.0的多线程是用来处理网络I/O这部分,充分利用CPU资源,减少网络I/O阻塞带来的性能损耗。)

4、IO多路复用

redis用IO多路复用功能监听多个socket连接客户端,多个 Socket 可能会产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个Socket,将Socket产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。

2、什么是缓存击穿、缓存穿透、缓存雪崩、缓存预热?

1、缓存穿透问题

先来看一个常见的缓存使用方式:读请求来了,先查下缓存,缓存有值命中,就直接返回;缓存没命中,就去查数据库,然后把数据库的值更新到缓存中,再返回。

img

缓存穿透问题:指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库中查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库中去查询,进而给数据库带来压力。

通俗点说,就是读请求访问是,缓存和数据库都没有某个值,这样就会导致每次对这个值的查询请求都会穿透到数据库,这就是缓存穿透。

缓存穿透一般都是这几种情况产生:

  • 业务设计不合理,比如大多数用户都没有开守护,但是你的每个请求都去缓存,查询某个userId查询有没有守护。

  • 业务/开发/运维操作失误,比如缓存和数据库的数据都被误删除了。

  • 黑客非法请求攻击,比如黑客故意捏造大量非法请求,以读取不存在的业务数据。

如何避免缓存穿透呢?一般有三种方法。

1、如果是非法请求,我们再api入口,对参数进行校验,过滤非法值。

2、如果查询数据库为空,我们可以给缓存设置个空值,或者默认值。但是如有写请求进来的话,需要更新缓存,以保证缓存一致性,同时,最后给缓存设置适当的过期时间。

3、使用布隆过滤器快速判断数据是否存在,即一个查询请求过来时,先通过布隆顾虑器判断值是否存在,存在才继续查下去。

布隆过滤器原理:它由初始值为0的位图数组和N个哈希函数组成。一个对一个可以进行N个hash算法获取N个值,在比特数组中将这N个值散列设定为1,让后查的时候如果特定的这几个位置都为1,那么布隆过滤器判断该key存在。

2、缓存雪崩问题

**缓存雪崩:**指缓存中数据大批量到过期时间,而查询数据量巨大,请求都直接访问数据库,引起数据压力过大机制宕机。

解决方式:

1、缓存雪崩一般是由于大量数据同时过期造成的,对于这个原因,可通过均匀设置过期时间解决,即让过期时间相对离散一点。如采用一个较大固定值+一个较小的随机值,5小时+ 0 到 1800秒这样子。

2、redis故障宕机也可能引起缓存雪崩,这就需要构造Redis高可用集群了。

3、缓存击穿问题

缓存击穿:指热点key在某个时间点过期的时候,而恰好在这个时间点对这个key有大量的并发请求过来,从而大量的请求达到db。

缓存击穿看着有点像缓存雪崩,其实它两区别是,缓存雪崩是数据库压力过大甚至宕机,而缓存击穿只是大量并发请求到了DB数据库层面。可以认为击穿是缓存雪崩的一个子集吧。还有一个区别在于击穿针对某一热点key缓存,雪崩则是很多key。

解决方案就有两种:

  • 使用互斥锁方案。缓存失效时,不是立即去加载db数据,而是先使用某些带成功返回的原子操作命令,如(redis的setnx)去操作,成功的时候,再去加载db数据库数据和设置缓存。否则就去重试获取缓存。
  • **热key永不过期。**是指没有设置过期时间,但是热点数据快要过期时,异步线程去更新和设置过期时间。

4、缓存预热

缓存预热是指系统上线后,提前将相关的缓存数据直接加载到缓存系统中,避免在用户请求的时候,先查询数据库,然后再将数据缓存问题,减少对数据库的压力。

缓存预热如何解决:

1、直接写个缓存刷新页面,上线时手工操作一下。

2、使用@PostConstruct初始化白名单数据。

3、定时刷新缓存。

3、什么是热Key问题,如何解决热Key问题?

什么是热Key呢?在Redis中,我们吧访问频率高的key,称为热点key。

如果某一热点key的请求到服务器主机时,由于请求量特别大,可能会导致主机资源不足,甚至宕机,从而影响正常的服务。

img

而热点Key是怎么产生的呢?主要原因有两个:

  • 用户消费的数据远大于生产的数据,如秒杀、热点新闻等读多写少的场景。
  • 请求分片集中,超过单Redis服务器的性能,比如固定名称Key,Hash落入同一条服务器,瞬间访问量极大,超过及其瓶颈,产生热点Key问题。

那么在日常开发中,如何识别到热点Key呢?

  • 凭经验判断哪些是热Key
  • 客户端统计上报
  • 服务代理层上报

如何解决热Key问题?

  • Redis集群扩容,增加分片副本,均衡读流量
  • 将热Key分散到不同的服务器中
  • 使用二级缓存,即JVM本地缓存,减少Redis的读请求

4、Redis过期策略和内存淘汰策略

img

1、Redis的过期策略

我们在set key的时候,可以给它设置一个过期时间,比如expire key 60.指定这个key60s后过期,60s后,redis是如何处理的呢?我们先来介绍几种过期策略:

1.1、定时过期

每个设置过期时间的key都需要创建一个定时器,到过期时间就会自己对key进行清除。该策略可以立即清除过期的数据,对内存很友好,但是会占用大量的CPU资源去处理过期的数据,从而影响到缓存的响应时间和吞吐量。

1.2、惰性过期

只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

1.3、定期过期

每隔一段时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中的方案。通过调整定时扫描间隔和每次扫描的限定耗时,可以在不同情况下是的CPU和内存资源达到最优的平衡效果。expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key时指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。

Redis中同时使用了惰性过期和定期过期两种过期策略。

  • 假设Redis当前存放30万个key,并且都设置了过期时间,如果你每隔100ms就去检查这全部的key,CPU负载就会特别高,最后可能会挂掉。
  • 因此,redis采取的是定期过期,每隔100ms就随机抽取一定数量的key来检查删除的
  • 但是呢,最后坑你会有很多已经过期的key没被删除,这时候,redis采用惰性过期,在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且已经过期了,此时就会被删除掉。

但是呀,如果定期删除漏掉了很多过期的key,然后也没走惰性删除,就会有很多过期key积在内存中,直接会导致内存爆满。或者有些时候,业务量大起来了,redis的key被大量使用,内存直接不够了,运维小哥也忘记加大内存了。难道redis就直接这样挂掉了不成?不会的!redis会用8中内存淘汰策略保护自己。

2、Redis内存淘汰策略

  • volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的key中使用LRU(最近最少使用)算法进行淘汰。
  • allkeys-lru:当内存不足以容纳新写入数据时,从所有key中使用LRU(最近最少使用)算法进行淘汰。
  • volatile-lfu: 4.0版本新增,当内存不足以容纳新写入数据时,在过期的key中,使用LFU算法进行删除key。
  • allkeys-lfu:4.0版本新增,当内存不足以容纳新写入数据时,从所有key中使用LFU算法进行淘汰
  • volatile-random:当内存不足以容纳新写入数据时,从设置了过期时间的key中,随机淘汰数据。
  • allkeys-random:当内存不足以容纳新写入数据时,从所有key中随机淘汰数据。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的key中,根据过期时间进行淘汰,越早过期的优先被淘汰。
  • noevication:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。

5、Redis常用应用场景

1、缓存

2、排行榜

3、计数器应用

4、共享Session

5、分布式锁

6、社交网络

7、消息队列

8、位操作

6、Redis的持久化机制有哪些?

Redis是基于内存的非关系型K-V数据库,既然它是基于内存的,如果Redis服务器挂了,数据就会丢失。为了避免数据丢失,Redis提供了持久化,即把数据保存到磁盘中。

Redis提供了RDB和AOF两种持久化机制,它持久化文件加载流程如下:

img

1、RDB

RDB就是把内存数据以快照的形式保存到磁盘上

什么是快照?可以这样理解,给当前时刻的数据,拍一张照片,然后保存下来。

RDB持久化,是指在指定的时间间隔内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的持久化方式。执行完操作后,在指定目录下回生成一个dump.rdb文件,Redis重启的时候,通过加载dump.rdb文件来恢复数据。RDB触发机制主要有以下几种:

img

RDB的优点:

  • 适合大规模的数据恢复场景,如备份,全量复制等。

缺点:

  • 没办法做到实时持久化、秒级持久化,容易造成数据丢失问题。
  • 新老版本存在RDB格式兼容问题

2、AOF

AOF持久化,采用日志的形式记录每个写操作,追加到文件中,重启的时候再重新执行AOF文件中的命令来恢复数据。它主要解决数据持久化的实时性问题。默认是不开启的

img

aof将写命令写入aof文件的三种写回操作:

  • always 同步写入,访问磁盘io次数多,影响效率。但数据量完整。
  • everysec 先把redis写操作写入aof缓冲区,让后每隔一秒将缓存区内容写入到磁盘中,性能适中。
  • no 先把redis写操作写入aof缓冲区,由操作系统决定何时将缓冲区内容写入磁盘中,容易造成数据丢失问题。

AOF优点:

  • 数据一致性和完整性更高

缺点:

  • AOF记录的内容越多,文件越大,数据恢复变慢。

3、RDB和AOF混合持久化

rdb+aof混合持久化的方式,rdb做全量持久化,aof做增量持久化。混合持久化也是只加载aof文件,它会将rdb持久化生成的文件,写入到aof文件中。

优缺点:

优点:混合持久化结合了 RDB 和 AOF 持久化的优点,开头为 RDB 的格式,使得 Redis 可以更快的启动,同时结合 AOF 的优点,有减低了大量数据丢失的风险。

缺点:1、AOF 文件中添加了 RDB 格式的内容,使得 AOF 文件的可读性变得很差;

​ 2、兼容性差,如果开启混合持久化,那么此混合持久化 AOF 文件,就不能用在 Redis 4.0 之前版本了。

7、redis的主从复制

redis主从复制是指在redis集群中master节点和slave节点数据同步的机制。将写入redis服务器的数据复制到其他redis服务器里面实现数据同步。master节点负责写操作,slave节点负责读操作。

主从复制原理

slave节点会向master发送一个SYNC的命令,master收到命令之后会生成数据快照。第二步把数据快照发送给slave节点,slave节点收到数据以后丢弃旧的数据并重新载入新的数据,并对外提供服务。

redis提供了全量复制增量复制两种模式。

在Redis2.8以前,从节点向主节点发送sync命令请求同步数据,此时的同步方式是全量复制;在Redis2.8及以后,从节点可以发送psync命令请求同步数据,此时根据主从节点当前状态的不同,同步方式可能是全量复制或部分复制。

全量复制:用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。

增量复制: 用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效,需要注意的是,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行地写命令,则无法进行部分复制,仍使用全量复制。

8、哨兵模式

1、哨兵模式概述

​ 主从切换技术:当主机宕机后,需要手动把一台从(slave)服务器切换为主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用,所以推荐哨兵架构(Sentinel)来解决这个问题。
​ 哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
redis高阶篇_第1张图片

这里哨兵模式有两个作用:

  • 哨兵节点会以每秒一次的频率对每个 Redis 节点发送PING命令,并通过 Redis 节点的回复来判断其运行状态。
  • 当哨兵监测到主服务器发生故障时,会自动在从节点中选择一台将机器,并其提升为主服务器,然后使用 PubSub 发布订阅模式,通知其他的从节点,修改配置文件,跟随新的主服务器。

当一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此可以使用哨兵进行监控, 各个哨兵之间还会进行监控,这就形成了多哨兵模式。

redis高阶篇_第2张图片

1、假设主服务器宕机,哨兵1先检测到结果,但是系统并不会马上进行failover(故障转移)过程,仅仅是哨兵1主观认为主服务器不可以用,这个现象称为主观下线

2、当后面的哨兵也检测到主服务器不可用,并且超过半数以上的节点也发现该主服务器也宕机,这一过程称为 客观下线。

3、投票选举,所有 哨兵 节点会通过投票机制,按照谁先发现谁处理的原则(Raft算法),选举 哨兵 1 为领头节点去做 Failover(故障转移)操作。哨兵 1 节点则按照一定的规则在所有从节点中选择一个最优的作为主服务器,然后通过发布订功能通知其余的从节点(slave)更改配置文件,跟随新上任的主服务器(master)。至此就完成了主从切换的操作。

Raft算法如下图所示:
redis高阶篇_第3张图片

4、故障转移

当哨兵节点通过raft算法选举出哨兵leader之后,该leader会组织故障转移操作,从salve节点中选出新的master节点,来完成主从复制架构。主要分三步:

(1)所有的salve节点参与比较选举。先比较他们的priority权重,权重越高的成为新master。

(2)要是权重相同比较复制偏移量,所谓复制偏移量就是从节点接收到主节点复制过来的数据量大小,偏移量越大,说明该salve节点主从传输时更稳定,选举偏移量大的为新master。

(3)要是权重和偏移量都相同的话,比较从节点的Run ID,(字典顺序,ascii码值)选举最小的为新master。如下图所示。

redis高阶篇_第4张图片

当选举出新master之后,该哨兵leader会对该节点执行salveof no one命令,让其成为新master。其他salve节点绑定该master节点。如果此时原来的master节点恢复,也会绑定新的master,成为其的salve从节点。

9、Bigkey问题

1、什么是bigKey?

​ 通俗易懂的讲,BigKey就是某个key对应的value值很大,占用的redis空间很大。在操作Bigkey时,通常比较耗时,容易造成redis阻塞,从而降低redis性能。

  • 字符串类型:它的big体现在单个value值很大,一般认为超过10KB就是bigkey。
  • 非字符串类型:哈希、列表、集合、有序集合,它们的big体现在元素个数太多。我们自己公司认为超过了5000个算bigkey。

2、bigKey的发现

(1)BigKeys命令分析

​ 可以使用Redis官方客户端redis-cli加上–bigkeys参数,可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大key。

​ 优点是可以在线扫描,不阻塞服务;缺点是信息较少,内容不够精确,只能找到每种类型的最大key。

(2)rdb-tools开源工具

​ 这种方式是在redis实例上执行bgsave,bgsave会触发redis的快照备份,生成rdb持久化文件,然后对dump出来的rdb文件进行分析,找到其中的大key。

​ 优点在于获取的key信息详细、可选参数多、支持定制化需求,结果信息可选择json或csv格式,后续处理方便,其缺点是需要离线操作,获取结果时间较长。

(3)memory usage

​ memory usage后面跟具体某个key,计算某个key的所占的字节数。

3、如何解决BigKey问题

(1)对大key进行拆分

​ 将一个Big Key拆分为多个key-value这样的小Key,并确保每个key的成员数量或者大小在合理范围内,然后再进行存储

(2)对大key进行清理

​ 对Redis中的大Key进行清理,从Redis中删除此类数据。Redis自4.0起提供了UNLINK命令,该命令能够以非阻塞的方式缓慢逐步的清理传入的Key,通过UNLINK,你可以安全的删除大Key甚至特大Key。

  • String:一般用del,特别大的时候用unlink删除
  • hash:先用hscan获取少量key,再用hdel渐进式删除
  • list:使用itrim渐进式逐步删除,直到全部删除完成
  • set:使用sscan每次获取部分key,再用srem命令删除每个元素。
  • zset:使用zscan每次获取部分元素,再使用zrem range by rank命令删除每个元素。

10、moreKey问题

在大量数据下,禁止使用keys *来查询key,这样数据量过大,容易造成redis服务阻塞,进而宕机。可以使用scan来迭代查取部分数据。

11、数据库和缓存双写一致性问题

概述: 读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存 (Redis) 和数据库(MySQL)间的数据一致性问题。

解决方案:

  • 先写数据库,再写入缓存

    ​ 如果出现了写缓存失败的场景,必然导致缓存中的数据为脏数据,和先写数据库,再写缓存方案一样,需要等到下一次缓存更新才能恢复到正常状态。

    redis高阶篇_第5张图片

  • 先写入缓存,再写入数据库

    ​ 该方案是问题最大的模式,该模式下,先更新缓存,再写数据库,一旦出现写数据库异常(网络延迟、数据库宕机等)情况,将导致缓存中的数据变为脏数据,这个状况将一直持续到该条数据被正确写回数据库,造成的影响无疑是巨大的。

    redis高阶篇_第6张图片

  • 先删除缓存,再写入数据库

    在用户的写操作中,先执行删除缓存操作,再去写数据库。这套方案,可以是可以,但当并发量一旦上升就容易出现问题。

    redis高阶篇_第7张图片

    但是在高并发的情况下,有可能更新到缓存的值,是原来的数据库的旧值,新值在写入数据库的过程中因卡顿了一会导致旧值更新到缓存中。造成数据不一致问题。这时候就可以使用延迟双删来解决数据不一致问题。

    redis高阶篇_第8张图片

    这就是我们所说的延时双删,即在写数据库之前删除一次,写完数据库后,间隔一段时间再删除一次。该方案有个非常关键的地方是:第二次删除缓存,并非立马就删,而是要在一定的时间间隔之后。

  • 先写入数据库,再删除缓存

    (1)请求e先写数据库,由于网络原因卡顿了一下,没有来得及删除缓存。
    ​ (2)请求f查询缓存,发现缓存中有数据,直接返回该数据。
    ​ (3)请求e删除缓存。

redis高阶篇_第9张图片

12、布隆过滤器

一、概述:

什么是布隆过滤器,布隆过滤器是1970年布隆提出的,它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

二、布隆过滤器优缺点:

  • 优点:时间复杂度低,新增和查询的时间复杂度为o(N),保密性强,布隆过滤器不存储元素本身。存储空间小。
  • 缺点:有一定的误判率,但是可以通过调整参数来降低,无法获取元素本身,很难删除元素。

三、布隆过滤器原理:

布隆过滤器是由一个固定大小的二进制向量和一系列映射函数组成的。在初始状态时,对于长度为m的二进制位数组,它的所有位都被置为0,如下图所示:

在这里插入图片描述

当有变量加入集合中时,通过K个映射函数将这个变量映射成位图中的K个点,把它们置为1。

在这里插入图片描述

查询某个变量的时候我们只要看看这些点是不是都是1就可以大概率知道集合中有没有它了。

  • 如果这些点只要有一个0,则被查询变量一定不存在。
  • 如果都是1,则被查询变量很可能存在。为什么说可能存在,而不是一定存在呢?那是因为映射函数本身就是个散列函数,散列函数是会有碰撞的。

误判率:布隆过滤器的误判是指多个输入经过哈希之后在相同的bit位置1了,这样就无法判断是哪个输入产生的,布隆过滤器的每一个bit位并不是独占的,很有可能多个元素共享了某一位。如果直接删除这一位的话,会影响其他元素。

特性:

  • 一个元素如果判断结果为存在的时候元素不一定存在,但是判断结果为不存在的时候则一定不存在。
  • 布隆过滤器可以添加元素,但是不能删除元素。因为删掉元素会导致误判率增加。

添加元素步骤

  • 将要添加的元素给 k 个哈希函数
  • 得到对应于位数组上的 k 个位置
  • 将这k个位置设为 1

查询元素步骤

  • 将要查询的元素给k个哈希函数
  • 得到对应于位数组上的k个位置
  • 如果k个位置有一个为 0,则肯定不在集合中
  • 如果k个位置全部为 1,则可能在集合中

拓展:

为了解决布隆过滤器不能删除元素的问题,布谷鸟过滤器横空出世。布谷鸟过滤器用更低的空间开销解决了布隆过滤器不能删除元素的问题。

13、分布式锁

一、概述:

​ 分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。

二、redis分布式锁实现方式

1、基于setnx分布式锁

// 加锁
SETNX lock_key 1
// 业务逻辑
DO THINGS
// 释放锁
DEL lock_key

但是,以上实现存在一个很大的问题,如果当拿到锁的进程意外宕机,来不急释放锁,就会造成死锁现象。现在就有人说可以加上过期时间来解决这个问题。但如果是在redis2.6之前 set、expire不是原子操作,还是会容易造成死锁,而在redis2.6之后,可以直接使用setnx ex这个是原子操作。

SET lock_key 1 EX 10 NX

与此同时带来的是另外一个问题,如果获取锁的进程再操作共享资源的时间很久,超出了设置了过期时间,这时候就会自动释放锁,导致其他进程也获取到共享资源,造成问题。无论是基于单节点模式,还是集群模式,都不推荐使用setnx实现分布式锁。

2、基于RedLock的分布式锁

为了避免Redis实例故障而导致的锁无法工作的问题,Redis的开发者 Antirez提出了分布式锁算法Redlock。Redlock算法的基本思路,是让客户端向多个独立的redis实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获的了分布式锁,否则获取锁失败。这样一来,即使有单个redis实例发生故障,因为锁变量在其他实例上也有保存,所以,客户端仍然可以正常地进行锁操作,锁变量并不会丢失。

Redlock算法的实现要求Redis采用集群部署模式,无哨兵节点,需要有N个独立的Redis实例(官方推荐至少5个实例)。接下来,我们可以分成3步来完成加锁操作。

img

第一步是,客户端获取当前时间。
第二步是,客户端按顺序依次向N个Redis实例执行加锁操作。

这里的加锁操作和在单实例上执行的加锁操作一样,使用SET命令,带上NX、EX/PX选项,以及带上客户端的唯一标识。当然,如果某个Redis实例发生故障了,为了保证在这种情况下,Redlock算法能够继续运行,我们需要给加锁操作设置一个超时时间。如果客户端在和一个Redis实例请求加锁时,一直到超时都没有成功,那么此时,客户端会和下一个Redis实例继续请求加锁。加锁操作的超时时间需要远远地小于锁的有效时间,一般也就是设置为几十毫秒。

第三步是,一旦客户端完成了和所有Redis实例的加锁操作,客户端就要计算整个加锁过程的总耗时。

客户端只有在满足两个条件时,才能认为是加锁成功,条件一是客户端从超过半数(大于等于 N/2+1)的Redis实例上成功获取到了锁;条件二是客户端获取锁的总耗时没有超过锁的有效时间。

为什么大多数实例加锁成功才能算成功呢?多个Redis实例一起来用,其实就组成了一个分布式系统。在分布式系统中总会出现异常节点,所以在谈论分布式系统时,需要考虑异常节点达到多少个,也依旧不影响整个系统的正确运行。这是一个分布式系统的容错问题,这个问题的结论是:如果只存在故障节点,只要大多数节点正常,那么整个系统依旧可以提供正确服务。

在满足了这两个条件后,我们需要重新计算这把锁的有效时间,计算的结果是锁的最初有效时间减去客户端为获取锁的总耗时。如果锁的有效时间已经来不及完成共享数据的操作了,我们可以释放锁,以免出现还没完成共享资源操作,锁就过期了的情况。

当然,如果客户端在和所有实例执行完加锁操作后,没能同时满足这两个条件,那么,客户端就要向所有Redis节点发起释放锁的操作。为什么释放锁,要操作所有的节点呢,不能只操作那些加锁成功的节点吗?因为在某一个Redis节点加锁时,可能因为网络原因导致加锁失败,例如一个客户端在一个Redis实例上加锁成功,但在读取响应结果时由于网络问题导致读取失败,那这把锁其实已经在Redis上加锁成功了。所以释放锁时,不管之前有没有加锁成功,需要释放所有节点上的锁以保证清理节点上的残留的锁。

在Redlock算法中,释放锁的操作和在单实例上释放锁的操作一样,只要执行释放锁的 Lua脚本就可以了。这样一来,只要N个Redis实例中的半数以上实例能正常工作,就能保证分布式锁的正常工作了。所以,在实际的业务应用中,如果你想要提升分布式锁的可靠性,就可以通过Redlock算法来实现。

3、基于redisson看门狗机制的分布式锁

redisson原理是redisson在获取锁之后,会维护一个看门狗线程,当锁即将过期还没有释放时,不断的延长锁key的生存时间。

watch dog自动延期机制:

看门狗启动后,对整体性能也会有一定影响,默认情况下看门狗线程是不启动的。如果使用redisson进行加锁的同时设置了锁的过期时间,也会导致看门狗机制失效。

redisson在获取锁之后,会维护一个看门狗线程,在每一个锁设置的过期时间的1/3处,如果线程还没执行完任务,则不断延长锁的有效期。看门狗的检查锁超时时间默认是30秒,可以通过 lockWactchdogTimeout 参数来改变。

加锁的时间默认是30秒,如果加锁的业务没有执行完,那么每隔 30 ÷ 3 = 10秒,就会进行一次续期,把锁重置成30秒,保证解锁前锁不会自动失效。

那万一业务的机器宕机了呢?如果宕机了,那看门狗线程就执行不了了,就续不了期,那自然30秒之后锁就解开了呗。

redisson总结:

a. 对key不设置过期时间,由Redisson在加锁成功后给维护一个watchdog看门狗,watchdog负责定时监听并处理,在锁没有被释放且快要过期的时候自动对锁进行续期,保证解锁前锁不会自动失效

b. 通过Lua脚本实现了加锁和解锁的原子操作

c. 通过记录获取锁的客户端id,每次加锁时判断是否是当前客户端已经获得锁,实现了可重入锁。

详细看这两篇,参考的这两篇写的。

https://blog.csdn.net/a745233700/article/details/88084219

https://blog.csdn.net/fuzhongmin05/article/details/119251590

你可能感兴趣的:(redis,数据库,缓存)