Redis知识详解(超具体)

打印 上一主题 下一主题

主题 642|帖子 642|积分 1926

点击下载《Redis知识详解(超具体)》
1. 配景

Redis是由意大利人Antirez(Salvatore Sanfilippo)在2009年创造的开源内存数据布局存储系统。Redis的名字来自意大利语“Repubblica di Redis”,意思是“基于字典的共和国”。它是一个基于内存的键值对存储系统,具有快速、稳定、可靠的特点。
Redis的诞生配景是互联网技术的快速发展,尤其是Web 2.0和移动互联网的鼓起。这些技术需要处理大量的用户天生内容,而传统的关系型数据库在处理这些数据时存在性能瓶颈。Redis的出现,办理了关系型数据库在处理大量数据时的性能题目,成为了一种高效、可扩展的办理方案。
2. 特点


  • 内存存储:Redis将所有数据存储在内存中,这使得读写操作非常快速。内存访问速率远高于磁盘访问速率,因此Redis可以提供非常高的读写性能。同时,内存存储也意味着Redis实用于需要高速访问的应用场景,如实时分析、实时游戏等。
  • 数据布局丰富:Redis支持多种数据布局,如字符串、哈希、列表、集合和有序集合等。这些数据布局可以满足不同类型的应用需求,如列表可以用于实现队列、栈等数据布局,哈希可以用于实现关联表等。同时,Redis还支持对这些数据布局举行丰富的操作,如插入、删除、查找等。
  • 高性能:由于Redis将所有数据存储在内存中,读写操作非常快速,因此Redis具有非常高的性能。在处理大量数据时,Redis可以提供非常高的读写速率和并发处理能力。同时,Redis还支持多种数据布局和丰富的操作,可以满足不同类型的应用需求。
  • 持久化:固然Redis将所有数据存储在内存中,但它也支持将数据持久化到磁盘上。这样可以在系统瓦解或重启时保证数据的完整性。Redis支持多种持久化方式,如RDB和AOF等。RDB通过天生数据快照的方式举行持久化,而AOF则通过记载操作日记的方式举行持久化。
  • 主从复制和集群:Redis支持主从复制和集群摆设,可以实现数据的高可用性和扩展性。主从复制可以将数据从一个Redis实例复制到多个从实例,当主实例出现故障时,可以从实例可以接管数据。集群摆设可以将多个Redis实例组成一个集群,实现数据的分布式存储和访问。
3. 数据布局

3.1 String

字符串 string 是 Redis 最简单的数据布局。Redis 所有的数据布局都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数据。不同类型的数据布局的差异就在于 value 的布局不一样。Redis 的 string 可以包罗任何数据,比如 jpg图片或者序列化的对象(java 中对象序列化函数 serialize)。
String 采取预分配冗余空间的方式来减少内存的频繁分配,内部为当前字符串现实分配的空间 capacity 一般要高于现实字符串长度 len。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果凌驾 1M,扩容时一次只会多扩 1M 的空间。需要留意的是字符串最大长度为 512M。
字符串是由多个字节组成,每个字节又是由 8 个 bit 组成,云云便可以将一个字符串看成很多 bit 的组合,这便是 bitmap「位图」数据布局。
3.2 List

Redis 的列表相称于 Java 语言里面的 LinkedList,留意它是链表而不是数组,而且是双向链表。这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为O(n),这点让人非常不测。
当列表弹出了最后一个元素之后,该数据布局自动被删除,内存被采取。
Redis 的列表布局常用来做异队伍列使用。将需要延后处理的任务布局体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据举行处理。
3.3 Hash

hash的底层存储有两种数据布局
**ziplist:**如果hash对象生存的键和值字符串长度都小于64字节且hash对象生存的键值对数量小于512,则采取这种。
dict(字典):其他情况采取这种数据布局。
hash 布局也可以用来存储用户信息,不同于字符串一次性需要全部序列化整个对象,hash 以对用户布局中的每个字段单独存储。这样当我们需要获取用户信息时可以举行部分获取。而以整个符串的情势去生存用户信息的话就只能一次性全部读取,这样就会比较浪费网络流量。
hash 也有缺点,hash 布局的存储消耗要高于单个字符串,到底该使用 hash 照旧字符串,需要根据现实情况再三衡量。
3.4 Set

Set 是一个无序的、自动去重的集合数据类型,Set 底层用两种数据布局存储。
**intset:**如果元素个数少于默认值512且元素可以用整型,则用这种数据布局。
**dict(字典):**其他情况采取这种数据布局。
当集合中最后一个元素移除之后,数据布局自动删除,内存被采取。 set 布局可以用来存储运动中奖的用户 ID,由于有去重功能,可以保证同一个用户不会中奖两次。
3.5 ZSet(有序集合)

zset 大概是 Redis 提供的最为特色的数据布局,它也是在面试中面试官最爱问的数据布局。zset为有序(有限score排序,score相同则元素字典排序),自动去重的集合数据类型,其底层实现为 字典(dict) + 跳表(skiplist),当数据比较少的时候用 ziplist 编码布局存储。
**ziplist :**如果有序集合生存的所有元素的长度小于默认值64字节且有序集合生存的元素数量小于默认值128个,则采取这种数据布局
**字典(dict) + 跳表(skiplist):**其他情况采取这种数据布局。
4. 过期计谋

Redis 所有的数据布局都可以设置过期时间,时间一到,就会自动删除。你可以想象 Redis 内部有一个死神,时刻盯着所有设置了过期时间的 key,寿命一到就会立刻收割。
你还可以进一步站在死神的角度思索,会不会由于同一时间太多的 key 过期,以至于忙不过来。同时由于 Redis 是单线程的,收割的时间也会占用线程的处理时间,如果收割的太过于繁忙,会不会导致线上读写指令出现卡顿。在过期这件事上,Redis 非常小心。
4.1 过期 key 集合

redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。除了定时遍历之外,它还会使用惰性计谋来删除过期的 key,所谓惰性计谋就是在客户端访问这个 key 的时候,redis 对 key 的过期时间举行查抄,如果过期了就立刻删除。定时删除是集中处理,惰性删除是零散处理。
4.2 定时扫描计谋

Redis 默认会每秒举行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是
采取了一种简单的贪婪计谋。
1、从过期字典中随机 20 个 key;
2、删除这 20 个 key 中已经过期的 key;
3、如果过期的 key 比率凌驾 1/4,那就重复步骤 1;
同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会凌驾 25ms。
设想一个大型的 Redis 实例中所有的 key 在同一时间过期了,会出现怎样的结果?
毫无疑问,Redis 会持续扫描过期字典 (循环多次),直到过期字典中过期的 key 变得希奇,才会停止 (循环次数明显下降)。这就会导致这期间线上读写 QPS 下降明显。还有另外一种原因是内存管理器需要频繁采取内存页,这也会产生一定的 CPU 消耗。
这里剖析一下,如果单台 Redis 读写哀求 QPS 是 10w,也就是每个哀求需要 0.00001s 来完成,每秒执行十次过期扫描,每次过期扫描都达到上限 25ms,那么每秒过期扫描总花费 0.25s,相称于 QPS 降低了 2.5W。
以是业务开辟职员一定要留意过期时间,如果有大批量的 key 过期,要给过期时间设置一个随机范围,而不能全部在同一时间过期。
4.3 从库的过期计谋

从库不会举行过期扫描,从库对过期的处理是被动的。主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key。
由于指令同步是异步举行的,以是主库过期的 key 的 del 指令没有及时同步到从库的话,会出现主从数据的不同等,主库没有的数据在从库里还存在。
5. Redis 内存淘汰机制

Redis 数据库可以通过设置文件来设置最大缓存,当写入的数据发现没有充足的内存可用的时候,Redis 会触发内存淘汰机制。Redis 为了满足多样化场景,提供了八种计谋,可以在 redis.config 文件中设置。
volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集中恣意选择数据淘汰
volatile-lfu:从已设置过期时间的数据集中挑选使用频率最低的数据淘汰
allkeys-lru:从所有数据集中挑选最近最少使用的数据淘汰
allkeys-lfu:从所有数据集中挑选使用频率最低的数据淘汰
allkeys-random:从所有数据集中恣意选择数据淘汰
no-enviction:不采取任何数据,返回一个写操作的错误信息。这也是默认计谋。
特别说明:
LRU(最近离前次使用时间最长):一定时间内,根据使用顺序排队,最前面为刚使用过的,排在最背面的优先淘汰。
LFU(最近访问次数最少):一定时间内,根据使用次数排序,次数最少的优先淘汰。
6. Redis 持久化

Redis 的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证 Redis 的数据不会由于故障而丢失,这种机制就是 Redis 的持久化机制。
Redis 的持久化机制有两种,第一种是快照,第二种是 AOF 日记。快照是一次全量备份,AOF 日记是一连的增量备份。快照是内存数据的二进制序列化情势,在存储上非常紧凑,而 AOF 日记记载的是内存数据修改的指令记载文本。AOF 日记在长期的运行过程中会变的无比庞大,数据库重启时需要加载 AOF 日记举行指令重放,这个时间就会无比漫长。以是需要定期举行 AOF 重写,给 AOF 日记举行瘦身。
6.1 RDB(快照)原理

我们知道 Redis 是单线程程序,这个线程要同时负责多个客户端套接字的并发读写操作和内存数据布局的逻辑读写。
在服务线上哀求的同时,Redis 还需要举行内存快照,内存快照要求 Redis 必须举行文件 IO 操作,可文件 IO 操作是不能使用多路复用 API。
这意味着单线程同时在服务线上的哀求还要举行文件 IO 操作,文件 IO 操作会严峻拖垮服务器哀求的性能。还有个重要的题目是为了不阻塞线上的业务,就需要边持久化边相应客户端哀求。持久化的同时,内存数据布局还在改变,比如一个大型的 hash 字典正在持久化,结果一个哀求过来把它给删掉了,还没持久化完呢,这要怎么搞?
Redis 使用操作系统的多历程 COW(Copy On Write) 机制来实现快照持久化,这个机制很有意思,也很少人知道。多历程 COW 也是判定程序员知识广度的一个重要指标。
fork( 多历程)
Redis 在持久化时会调用 glibc 的函数 fork 产生一个子历程,快照持久化完全交给子历程来处理,父历程继续处理客户端哀求。子历程刚刚产生时,它和父历程共享内存里面的代码段和数据段。这是 Linux 操作系统的机制,为了节约内存资源,以是尽大概让它们共享起来。在历程分离的一瞬间,内存的增长险些没有明显变革。
子历程做数据持久化,它不会修改现有的内存数据布局,它只是对数据布局举行遍历读取,然后序列化写到磁盘中。但是父历程不一样,它必须持续服务客户端哀求,然后对内存数据布局举行不间断的修改。
这个时候就会使用操作系统的 COW 机制来举行数据段页面的分离。数据段是由很多操作系统的页面组合而成,当父历程对此中一个页面的数据举行修改时,会将被共享的页面复制一份分离出来,然后对这个复制的页面举行修改。这时子历程相应的页面是没有变革的,照旧历程产生时那一瞬间的数据。
随着父历程修改操作的持续举行,越来越多的共享页面被分离出来,内存就会持续增长。但是也不会凌驾原有数据内存的 2 倍大小。另外一个 Redis 实例里冷数据占的比例每每是比较高的,以是很少会出现所有的页面都会被分离,被分离的每每只有此中一部分页面。每个页面的大小只有 4K,一个 Redis 实例里面一般都会有成千上万的页面。
子历程由于数据没有变革,它能看到的内存里的数据在历程产生的一瞬间就凝固了,再也不会改变,这也是为什么 Redis 的持久化叫「快照」的原因。接下来子历程就可以非常安心的遍历数据了举行序列化写磁盘了。
6.2 AOF 原理

AOF 日记存储的是 Redis 服务器的顺序指令序列,AOF 日记只记载对内存举行修改的指令记载。
假设 AOF 日记记载了自 Redis 实例创建以来所有的修改性指令序列,那么就可以通过对一个空的 Redis 实例顺序执行所有的指令,也就是「重放」,来恢复 Redis 当前实例的内存数据布局的状态。
Redis 会在收到客户端修改指令后,先举行参数校验,如果没题目,就立刻将该指令存储到 AOF 日记缓存中,AOF 日记缓存 copy 到 内核缓存,但还没有刷到磁盘,也就是先写日记,然后再执行指令。这样即使遇到突发宕机,已经存储到 AOF 日记的指令举行重放一下就可以恢复到宕机前的状态。
Redis 在长期运行的过程中,AOF 的日记会越变越长。如果实例宕机重启,重放整个AOF 日记会非常耗时,导致长时间 Redis 无法对外提供服务。以是需要对 AOF 日记瘦身。
6.3 AOF 重写

Redis 提供了 bgrewriteaof 指令用于对 AOF 日记举行瘦身。其原理就是开辟一个子历程对内存举行遍历转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日记文件中。序列化完毕后再将操作期间发生的增量 AOF 日记追加到这个新的 AOF 日记文件中,追加完毕后就立刻替代旧的 AOF 日记文件了,瘦身工作就完成了。
6.4 fsync

AOF 日记是以文件的情势存在的,当程序对 AOF 日记文件举行写操作时,现实上是将内容写到了内核为文件描述符分配的一个内存缓存中 OS buffer,然后内核会异步将脏数据刷回到磁盘的。
这就意味着如果机器突然宕机,AOF 日记内容大概还没有来得及完全刷到磁盘中,这个时候就会出现日记丢失。那该怎么办?
Linux 的 glibc 提供了 fsync(int fd)函数可以将指定文件的内容强制从内核缓存刷到磁盘。只要 Redis 历程实时调用 fsync 函数就可以保证 aof 日记不丢失。但是 fsync 是一个磁盘 IO 操作,它很慢!如果 Redis 执行一条指令就要 fsync 一次,那么 Redis 高性能的职位就不保了。
以是在生产环境的服务器中,Redis 通常是每隔 1s 左右执行一次 fsync 操作,周期 1s是可以设置的。这是在数据安全性和性能之间做了一个折中,在保持高性能的同时,尽大概使得数据少丢失。
Redis 同样也提供了另外两种计谋,一个是永不 fsync——让操作系统来决定符合同步磁盘,很不安全,另一个是来一个指令就 fsync 一次——非常慢。但是在生产环境根本不会使用,了解一下即可。
6.5 运维

快照是通过开启子历程的方式举行的,它是一个比较耗资源的操作。遍历整个内存,大块写磁盘会加重系统负载,
AOF 的 fsync 是一个耗时的 IO 操作,它会降低 Redis 性能,同时也会增加系统 IO 负担,以是通常 Redis 的主节点是不会举行持久化操作,持久化操作重要在从节点举行。从节点是备份节点,没有来自客户端哀求的压力,它的操作系统资源每每比较充沛。
但是如果出现网络分区,从节点长期连不上主节点,就会出现数据不同等的题目,特别是在网络分区出现的情况下又不小心主节点宕机了,那么数据就会丢失,以是在生产环境要做好实时监控工作,保证网络流通或者能快速修复。另外还应该再增加一个从节点以降低网络分区的概率,只要有一个从节点数据同步正常,数据也就不会轻易丢失。
6.6 肴杂持久化

重启 Redis 时,我们很少使用 rdb 来恢复内存状态,由于会丢失大量数据。我们通常使用 AOF 日记重放,但是重放 AOF 日记性能相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。
Redis 4.0 为了办理这个题目,带来了一个新的持久化选项——肴杂持久化。将 rdb 文件的内容和增量的 AOF 日记文件存在一起。这里的 AOF 日记不再是全量的日记,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日记,通常这部分 AOF 日记很小。
7. Redis集群

7.1 Sentinel

现在我们讲的 Redis 还只是主从方案,最终同等性。以是我们必须有一个高可用方案来抵抗单点故障,当故障发生时可以自动举行从主切换,程序可以不用重启,仿佛什么事也没发生一样。Redis 官方提供了这样一种方案 —— Redis Sentinel(哨兵)。

我们可以将 Redis Sentinel 集群看成是一个 ZooKeeper 集群,它是集群高可用的心脏,它一般是由 3~5 个节点组成,这样挂了个别节点集群还可以正常运转。
它负责持续监控主从节点的健康,当主节点挂掉时,自动选择一个最优的从节点切换为主节点。客户端来毗连集群时,会起首毗连 sentinel,通过 sentinel 来查询主节点的地址,然后再去毗连主节点举行数据交互。当主节点发生故障时,客户端会重新向 sentinel 要地址,sentinel 会将最新的主节点地址告诉客户端。云云应用程序将无需重启即可自动完成节点切换。比如上图的主节点挂掉后,集群将大概自动调整为下图所示布局。

此时原先挂掉的主节点现在酿成了从节点,从新的主节点那里创建复制关系。

7.1.2 主从同步设置-消息丢失

Redis 主从采取异步复制,意味着当主节点挂掉时,从节点大概没有收到全部的同步消息,这部分未同步的消息就丢失了。如果主从延迟特别大,那么丢失的数据就大概会特别多。Sentinel 无法保证消息完全不丢失,但是也尽大概保证消息少丢失。它有两个参数可以限定主从延迟过大。
  1. min-slaves-to-write 1
  2. min-slaves-max-lag 10
复制代码
第一个参数表示主节点必须至少有一个从节点在举行正常复制,否则就停止对外写哀求,丧失可用性。
作甚正常复制,作甚非常复制?这个就是由第二个参数控制的,它的单元是秒,表示如果 10s 没有收到从节点的反馈,就意味着从节点同步不正常,是谓非常复制。
7.1.3 Sentinel 根本使用

接下来我们看看客户端如何使用 sentinel,标准的流程应该是客户端可以通过 sentinel 发现主从节点的地址,然后在通过这些地址创建相应的毗连来举行数据存取操作。
sentinel 的默认端口是 26379,不同于 Redis 的默认端口 6379,通过 sentinel 对象的 discover_xxx 方法可以发现主从地址,主地址只有一个,从地址可以有多个。
通过 xxx_for 方法可以从毗连池中拿出一个毗连来使用,由于从地址有多个,redis 客户端对从地址采取轮询方案,也就是 RoundRobin 轮着来。
有个题目是,但 sentinel 举行主从切换时,客户端如何知道地址变更了 ? 通太过析源码,我发现 redis-py 在创建毗连的时候举行了主库地址变更判断。
毗连池创建新毗连时,会去查询主库地址,然后跟内存中的主库地址举行比对,如果变更了,就断开所有毗连,重新使用新地址创建新毗连。如果是旧的主库挂掉了,那么所有正在使用的毗连都会被关闭,然后在重连时就会用上新地址。
主从切换后,之前的主库被降级到从库,所有的修改性的指令都会抛出 ReadonlyError。如果没有修改性指令,固然毗连不会得到切换,但是数据不会被粉碎,以是即使不切换也没关系。
7.2 Cluster

RedisCluster 是 Redis 作者自己提供的 Redis 集群化方案。
相对于 Codis 的不同,它是去中心化的,如图所示,该集群有三个 Redis 节点组成,每个节点负责整个集群的一部分数据,每个节点负责的数据多少大概不一样。这三个节点相互毗连组成一个对等的集群,它们之间通过一种特别的二进制协议相互交互集群信息。

Redis Cluster 将所有数据分别为 16384 的 slots,它比 Codis 的 1024 个槽分别的更为精致,每个节点负责此中一部分槽位。槽位的信息存储于每个节点中,它不像 Codis,它不需要另外的分布式存储来存储节点槽位信息。
当 Redis Cluster 的客户端来毗连集群时,它也会得到一份集群的槽位设置信息。这样当客户端要查找某个 key 时,可以直接定位到目标节点。
这点不同于 Codis,Codis 需要通过 Proxy 来定位目标节点,RedisCluster 是直接定位。客户端为了可以直接定位某个具体的 key 地点的节点,它就需要缓存槽位相关信息,这样才可以准确快速地定位到相应的节点。同时由于槽位的信息大概会存在客户端与服务器不同等的情况,还需要纠正机制来实现槽位信息的校验调整。
另外,RedisCluster 的每个节点会将集群的设置信息持久化到设置文件中,以是必须确保设置文件是可写的,而且尽量不要依靠人工修改设置文件。
7.2.1 槽位定位算法

Cluster 默认会对 key 值使用 crc32 算法举行 hash 得到一个整数值,然后用这个整数值对 16384 举行取模来得到具体槽位。
Cluster 还答应用户强制某个 key 挂在特定槽位上,通过在 key 字符串里面嵌入 tag 标记,这就可以强制 key 所挂在的槽位即是 tag 地点的槽位。
7.2.2 跳转

当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 地点的槽位并不归自己管理,这时它会向客户端发送一个特别的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。
7.2.3 迁徙

Redis Cluster 提供了工具 redis-trib 可以让运维职员手动调整槽位的分配情况,它使用 Ruby 语言举行开辟,通过组合各种原生的 Redis Cluster 指令来实现。这点 Codis 做的更加人性化,它不但提供了 UI 界面可以让我们方便的迁徙,还提供了自动化均衡槽位工具,无需人工干预就可以均衡集群负载。不过 Redis 官方向来的计谋就是提供最小可用的工具,其它都交由社区完成。
7.2.4 大概下线 (PFAIL-Possibly Fail) 与确定下线 (Fail)

由于 Redis Cluster 是去中心化的,一个节点以为某个节点失联了并不代表所有的节点都以为它失联了。以是集群还得经过一次协商的过程,只有当大多数节点都认定了某个节点失联了,集群才以为该节点需要举行主从切换来容错。
Redis 集群节点采取 Gossip 协议来广播自己的状态以及自己对整个集群认知的改变。比如一个节点发现某个节点失联了 (PFail),它会将这条信息向整个集群广播,其它节点也就可以收到这点失联信息。如果一个节点收到了某个节点失联的数量 (PFail Count) 已经达到了集群的大多数,就可以标记该节点为确定下线状态 (Fail),然后向整个集群广播,逼迫其它节点也接收该节点已经下线的事实,并立刻对该失联节点举行主从切换。
8. Redis 经典运用

8.1 分布式锁

分布式应用举行逻辑处理时经常会遇到并发题目。比如一个操作要修改用户的状态,修改状态需要先读出用户的状态,在内存里举行修改,改完了再存归去。如果这样的操作同时举行了,就会出现并发题目,由于读取和生存状态这两个操作不是原子的。(Wiki 表明:所谓 原子操作是指不会被线程调理机制打断的操作;这种操作一旦开始,就一直运行到结束,中心不会有任何 context switch 线程切换。)
这个时候就要使用到分布式锁来限定程序的并发执行。
分布式锁本质上要实现的目标就像是在一间房上挂上一把锁,当别的人进入房间时,发现门已经锁上了,就只好放弃或者稍后再试。
一般是使用 setnx(set if not exists) 指令,只答应被一个客户端使用。先来先占, 用完了,再调用 del 指令开释。
8.2 锁开释题目 setnx + expire

这大概会有个题目,如果逻辑执行到中心出现非常了,大概会导致 del 指令没有被调用, 这样就会陷入死锁,锁永久得不到开释。
于是我们在拿到锁之后,再给锁加上一个过期时间,比如 5s,这样即使中心出现非常也可以保证 5 秒之后锁会自动开释。
但是以上逻辑还有题目。如果在 setnx 和 expire 之间服务器历程突然挂掉了,大概是由于机器掉电或者是被人为杀掉的,就会导致 expire 得不到执行,也会造成死锁。
这种题目的根源就在于 setnx 和 expire 是两条指令而不是原子指令。如果这两条指令可以一起执行就不会出现题目。 大概你会想到用 Redis 事件来办理。 但是这里不行, 由于 expire是依赖于 setnx 的执行结果的, 如果 setnx 没抢到锁, expire 是不应该执行的。 事件里没有 if-else 分支逻辑,事件的特点是一口吻执行,要么全部执行要么一个都不执行。
为了办理这个疑难,Redis 开源社区涌现了一堆分布式锁的 library,专门用来办理这个题目。 实现方法极为复杂, 小白用户一般要费很大的精力才可以搞懂。 如果你需要使用分布式锁,意味着你不能仅仅使用 Jedis 或者 redis-py 就行了,还得引入分布式锁的 library。
为了治理这个乱象,Redis 2.8 版本中作者到场了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行,彻底办理了分布式锁的乱象。
8.3 锁超时题目 redisson

Redis 的分布式锁不能办理超时题目,如果在加锁和开释锁之间的逻辑执行的太长,以至于超出了锁的超时限定, 就会出现题目。 由于这时候锁过期了, 第二个线程重新持有了这把锁,但是紧接着第一个线程执行完了业务逻辑,就把锁给开释了。这样就会陷入无限循环中。
为了避免这个题目,Redis 分布式锁不要用于较长时间的任务。如果真的偶然出现了,数据出现的小波错乱大概需要人工参与办理。
有一个更加安全的方案使用 Redisson 客户端。
原理:redisson 内部提供了一个监控锁的看门狗,可以设置 lockWatchdogTimeout(监控锁的看门狗超时时间,默认是 30s),在监控锁被主动关闭前,会不断的延伸监控锁的有效期,如果 redisson 客户端节点(也就是我们的服务器)宕机,那么监控锁时间到后自动关闭。
redisson使用守护线程来举行锁的续期,(守护线程的作用:当用户线程烧毁,会和用户线程一起烧毁。)防止程序宕机后,线程依旧不断续命,造成死锁!如果服务器宕机,锁会在 6s 后自动开释。
redisson 守护线程的续命机制是依靠 netty 中的定时机制 HashedWheelTimer(时间轮)来完成的。
留意:
redisson 锁在集群环境下,也是有缺陷的,它不是绝对安全的。比如在 Sentinel 集群中,主节点挂掉时,从节点会取而代之,客户端上却并没有明显感知。原先第一个客户端在主节点中申请成功了一把锁,但是这把锁还没有来得及同步到从节点,主节点突然挂掉了。然后从节点酿成了主节点,这个新的节点内部没有这个锁,以是当另一个客户端过来哀求加锁时,立刻就批准了。这样就会导致系统中同样一把锁被两个客户端同时持有,不安全性由此产生。
不过这种不安全也仅仅是在主从发生 failover 的情况下才会产生,而且持续时间极短,业务系统多数情况下可以容忍。
8.4 Redlock 算法

它的流程比较复杂,不过已经有了很多开源的 library 做了良好的封装,用户可以拿来即用,比如 redlock-py。
为了使用 Redlock,需要提供多个 Redis 实例。同很多分布式算法一样,redlock 也使用「大多数机制」。
加锁时,它会向过半节点发送 set(key, value, nx=True, ex=xxx) 指令,只要过半节点 set 成功,那就以为加锁成功。开释锁时,需要向所有节点发送 del 指令。不过 Redlock 算法还需要考虑出错重试、时钟漂移等很多细节题目,同时由于 Redlock 需要向多个节点举行读写,意味着相比单实例 Redis 性能会下降一些。
Redlock 使用场景:
如果你很在乎高可用性,希望挂了一台 redis 完全不受影响,那就应该考虑 redlock。不过代价也是有的,需要更多的 redis 实例,性能也下降了,代码上还需要引入额外的library,运维上也需要特别对待,这些都是需要考虑的本钱,使用前请再三斟酌。
9. 缓存各种题目

9.1 缓存处理流程

前台哀求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。留意,缓存是这种处理方式为条件。
9.2 缓存穿透

描述:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起哀求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很大概是攻击者,攻击会导致数据库压力过大。
办理方案:
1、接口层增加校验,如用户鉴权校验,id做根本校验,id<=0的直接拦截;
2、从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。
9.3 缓存击穿

描述:缓存击穿是指缓存中没有但数据库中有的单条数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
办理方案:
1、设置热点数据永久不过期。
2、加互斥锁。读数据库时需要获取锁,一条哀求拿到锁之后读取数据并更新缓存,为了防止那些抢锁失败线程重新获取到锁后又举行读数据库操作,这里采取双重检验锁方式。
9.4 缓存雪崩

描述:缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至 down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
办理方案:
1、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2、设置热点数据永久不过期。
9.5 缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户哀求的时候,先查询数据库,然后再将数据缓存的题目!用户直接查询事先被预热的缓存数据!
办理方案:
1、直接写个缓存革新页面,上线时手工操作一下;
2、数据量不大,可以在项目启动的时候自动举行加载;
3、定时革新缓存;
9.6 缓存降级

当访问量剧增、服务出现题目(如相应时间慢或不相应)或非核心服务影响到核心流程的性能时,仍然需要保证服务照旧可用的,即使是有损服务。系统可以根据一些关键数据举行自动降级,也可以设置开关实现人工降级。
缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如到场购物车、结算)。
在举行降级之前要对系统举行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日记级别设置预案:
1、一般:比如有些服务偶然由于网络抖动或者服务正在上线而超时,可以自动降级;
2、告诫:有些服务在一段时间内成功率有颠簸(如在95~100%之间),可以自动降级或人工降级,并发送告警;
3、错误:比如可用率低于90%,或者数据库毗连池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
4、严峻错误:比如由于特别原因数据错误了,此时需要紧急人工降级。
服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩题目。因此,对于不重要的缓存数据,可以采取服务降级计谋,例如一个比较常见的做法就是,Redis出现题目,不去数据库查询,而是直接返回默认值给用户。
点击下载《Redis知识详解(超具体)》

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

天空闲话

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表