小白学redis

本文详细介绍了Redis中的数据结构,包括String、Hash、List、Set和Sorted Set,以及它们在实际应用中的场景。此外,探讨了Redis的持久化方式RDB和AOF,以及如何处理缓存和数据库数据的一致性问题。接着,讨论了Redis的高可用解决方案,如主从复制、哨兵系统和集群,以及如何应对缓存击穿、热点key等问题。最后,提到了Redis的事务和Lua脚本的使用,以及如何利用它们实现分布式锁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Redis是⼀种基于键值对(key-value)的NoSQL数据库。

⽐⼀般键值对数据库强⼤的地⽅,Redis中的value⽀持string(字符串)、hash(哈希)、 list
(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、 HyperLogLogGEO(地理
信息定位)等多种数据结构,因此 Redis可以满⾜很多的应⽤场景。
⽽且因为Redis会将所有数据都存放在内存中,所以它的读写性能⾮常出⾊。
不仅如此,Redis还可以将内存的数据利⽤快照和⽇志的形式保存到硬盘上,这样在发⽣类似
断电或者机器故障的时候,内存中的数据不会丢失
除了上述功能以外,Redis还提供了键过期、发布订阅、事务、流⽔线、Lua脚本等附加功能。
总之,Redis是⼀款强⼤的性能利器。

 

Redis有五种基本数据结构。
string
字符串最基础的数据结构。字符串类型的值实际可以是字符串(简单的字符串、复杂的字符
串(例如JSONXML))、数字 (整数、浮点数),甚⾄是⼆进制(图⽚、⾳频、视
频),但是值最⼤不能超过512MB
字符串主要有以下⼏个典型使⽤场景:
缓存功能
计数
共享Session
限速
hash
哈希类型是指键值本⾝又是⼀个键值对结构。
哈希主要有以下典型应⽤场景:
缓存⽤户信息
缓存对象
list
列表(list)类型是⽤来存储多个有序的字符串。列表是⼀种⽐较灵活的数据结构,它可以充
当栈和队列的⾓⾊
列表主要有以下⼏种使⽤场景:
消息队列
⽂章列表
set
集合(set)类型也是⽤来保存多个的字符串元素,但和列表类型不⼀ 样的是,集合中不允许
有重复元素,并且集合中的元素是⽆序的。
集合主要有如下使⽤场景:
标签(tag
共同关注
sorted set
有序集合中的元素可以排序。但是它和列表使⽤索引下标作为排序依据不同的是,它给每个
元素设置⼀个权重(score)作为排序的依据。
有序集合主要应⽤场景:
⽤户点赞统计
⽤户排序
4.Redis为什么快呢?
Redis的速度⾮常的快,单机的Redis就可以⽀撑每秒⼗⼏万的并发,相对于MySQL来说,性能
MySQL的⼏⼗倍。速度快的原因主要有⼏点:
1. 完全基于内存操作
2. 使⽤单线程,避免了线程切换和竞态产⽣的消耗
3. 基于⾮阻塞的IO多路复⽤机制
4. C语⾔实现,优化过的数据结构,基于⼏种基础的数据结构,redis做了⼤量的优化,性能
极⾼

 

Linux系统有三种⽅式实现IO多路复⽤:selectpollepoll
例如epoll⽅式是将⽤户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到
达,这样就避免了⼤量的⽆⽤操作。此时的socket应该采⽤⾮阻塞模式。
这样,整个过程只在进⾏selectpollepoll这些调⽤的时候才会阻塞,收发客户消息是不会阻
塞的,整个进程或者线程就被充分利⽤起来,这就是事件驱动,所谓的reactor模式。

 

 
6. Redis为什么早期选择单线程?
官⽅FAQ表⽰,因为Redis是基于内存的操作,CPU成为Redis的瓶颈的情况很少见,Redis的瓶
颈最有可能是内存的⼤⼩或者⽹络限制。
如果想要最⼤程度利⽤CPU,可以在⼀台机器上启动多个Redis实例。
PS:⽹上有这样的回答,吐槽官⽅的解释有些敷衍,其实就是历史原因,开发者嫌多线程⿇
烦,后来这个CPU的利⽤问题就被抛给了使⽤者。
同时FAQ⾥还提到了, Redis 4.0 之后开始变成多线程,除了主线程外,它也有后台线程在处
理⼀些较为缓慢的操作,例如清理脏数据、⽆⽤连接的释放、⼤ Key 的删除等等。

 

7.Redis6.0使⽤多线程是怎么回事?
Redis不是说⽤单线程的吗?怎么6.0成了多线程的?
Redis6.0的多线程是⽤多线程来处理数据的读写和协议解析,但是Redis执⾏命令还是单线程
的。

这样做的⽬的是因为Redis的性能瓶颈在于⽹络IO⽽⾮CPU,使⽤多线程能提升IO读写的效
率,从⽽整体提⾼Redis的性能。

 

RDB
RDB持久化是把当前进程数据⽣成快照保存到硬盘的过程,触发RDB持久化过程分为⼿动触
发和⾃动触发。
RDB⽂件是⼀个压缩的⼆进制⽂件,通过它可以还原某个时刻数据库的状态。由于RDB⽂件
是保存在硬盘上的,所以即使Redis崩溃或者退出,只要RDB⽂件存在,就可以⽤它来恢复还
原数据库的状态。
⼿动触发分别对应savebgsave

 

 

save命令:阻塞当前Redis服务器,直到RDB过程完成为⽌,对于内存⽐较⼤的实例会造
成长时间阻塞,线上环境不建议使⽤。
bgsave命令:Redis进程执⾏fork操作创建⼦进程,RDB持久化过程由⼦进程负责,完成后
⾃动结束。阻塞只发⽣在fork阶段,⼀般时间很短。
以下场景会⾃动触发RDB持久化:
使⽤save相关配置,如“save m n”。表⽰m秒内数据集存在n次修改时,⾃动触发bgsave
如果从节点执⾏全量复制操作,主节点⾃动执⾏bgsave⽣成RDB⽂件并发送给从节点
执⾏debug reload命令重新加载Redis时,也会⾃动触发save操作
默认情况下执⾏shutdown命令时,如果没有开启AOF持久化功能则⾃动执⾏bgsave
AOF
AOFappend only file)持久化:以独⽴⽇志的⽅式记录每次写命令, 重启时再重新执⾏
AOF⽂件中的命令达到恢复数据的⽬的。AOF的主要作⽤是解决了数据持久化的实时性,⽬
前已经是Redis持久化的主流⽅式。

AOF的⼯作流程操作:命令写⼊ (append)、⽂件同步(sync)、⽂件重写(rewrite)、重启
加载 (load

流程如下:
1)所有的写⼊命令会追加到aof_buf(缓冲区)中。
2AOF缓冲区根据对应的策略向硬盘做同步操作。
3)随着AOF⽂件越来越⼤,需要定期对AOF⽂件进⾏重写,达到压缩 的⽬的。
4)当Redis服务器重启时,可以加载AOF⽂件进⾏数据恢复。

 

9.RDB AOF 各⾃有什么优缺点?
RDB | 优点
1. 只有⼀个紧凑的⼆进制⽂件 dump.rdb ,⾮常适合备份、全量复制的场景。
2. 容灾性好 ,可以把RDB⽂件拷贝道远程机器或者⽂件系统张,⽤于容灾恢复。
3. 恢复速度快 ,RDB恢复数据的速度远远快于AOF的⽅式
RDB | 缺点
1. 实时性低 ,RDB 是间隔⼀段时间进⾏持久化,没法做到实时持久化/秒级持久化。如果在
这⼀间隔事件发⽣故障,数据会丢失。
2. 存在兼容问题 ,Redis演进过程存在多个格式的RDB版本,存在⽼版本Redis⽆法兼容新版
RDB的问题。
AOF | 优点
1. 实时性好 ,aof 持久化可以配置 appendfsync 属性,有 always ,每进⾏⼀次命令操
作就记录到 aof ⽂件中⼀次。
2. 通过 append 模式写⽂件,即使中途服务器宕机,可以通过 redis-check-aof ⼯具解决数据
⼀致性问题。
AOF | 缺点
1. AOF ⽂件⽐ RDB ⽂件⼤ ,且 恢复速度慢 。
2. 数据集⼤ 的时候,⽐ RDB 启动效率低 。
10.RDBAOF如何选择?
⼀般来说, 如果想达到⾜以媲美数据库的 数据安全性 ,应该 同时使⽤两种持久化功
能 。在这种情况下,当 Redis 重启的时候会优先载⼊ AOF ⽂件来恢复原始的数据,因为
在通常情况下 AOF ⽂件保存的数据集要⽐ RDB ⽂件保存的数据集要完整。
如果 可以接受数分钟以内的数据丢失 ,那么可以 只使⽤ RDB 持久化 。
有很多⽤户都只使⽤ AOF 持久化,但并不推荐这种⽅式,因为定时⽣成 RDB 快照
snapshot)⾮常便于进⾏数据备份, 并且 RDB 恢复数据集的速度也要⽐ AOF 恢复的速
度要快,除此之外,使⽤ RDB 还可以避免 AOF 程序的 bug
如果只需要数据在服务器运⾏的时候存在,也可以不使⽤任何持久化⽅式。
11.Redis的数据恢复?
Redis发⽣了故障,可以从RDB或者AOF中恢复数据。
恢复的过程也很简单,把RDB或者AOF⽂件拷贝到Redis的数据⽬录下,如果使⽤AOF恢复,
配置⽂件开启AOF,然后启动redis-server即可。
Redis 启动时加载数据的流程:
1. AOF持久化开启且存在AOF⽂件时,优先加载AOF⽂件。
2. AOF关闭或者AOF⽂件不存在时,加载RDB⽂件。
3. 加载AOF/RDB⽂件成功后,Redis启动成功。
4. AOF/RDB⽂件存在错误时,Redis启动失败并打印错误信息。
12.Redis 4.0 的混合持久化了解吗?
重启 Redis 时,我们很少使⽤ RDB 来恢复内存状态,因为会丢失⼤量数据。我们通常使⽤
AOF ⽇志重放,但是重放 AOF ⽇志性能相对 RDB 来说要慢很多,这样在 Redis 实例很⼤的
情况下,启动需要花费很长的时间。

Redis 4.0 为了解决这个问题,带来了⼀个新的持久化选项——混合持久化。将 rdb ⽂件的
内容和增量的 AOF ⽇志⽂件存在⼀起。这⾥的 AOF ⽇志不再是全量的⽇志,⽽是 ⾃持久化
开始到持久化结束 的这段时间发⽣的增量 AOF ⽇志,通常这部分 AOF ⽇志很⼩:

 

于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF ⽇志就可以完全
替代之前的 AOF 全量⽂件重放,重启效率因此⼤幅得到提升。
⾼可⽤
Redis保证⾼可⽤主要有三种⽅式:主从、哨兵、集群。
13.主从复制了解吗?
主从复制,是指将⼀台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为 主节点
(master),后者称为 从节点(slave)。且数据的复制是 单向 的,只能由主节点到从节点。
Redis 主从复制⽀持 主从同步 从从同步 两种,后者是 Redis 后续版本新增的功能,以减
轻主节点的同步负担。
主从复制主要的作⽤?
数据冗余: 主从复制实现了数据的热备份,是持久化之外的⼀种数据冗余⽅式。
故障恢复: 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复 (实际
上是⼀种服务的冗余)
负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点
提供读服务 (即写 Redis 数据时应⽤连接主节点,读 Redis 数据时应⽤连接从节点) ,
分担服务器负载。尤其是在写少读多的场景下,通过多个从节点分担读负载,可以⼤⼤提
Redis 服务器的并发量。
⾼可⽤基⽯: 除了上述作⽤以外,主从复制还是哨兵和集群能够实施的 基础 ,因此说
主从复制是 Redis ⾼可⽤的基础。

 

14.Redis主从有⼏种常见的拓扑结构?
Redis的复制拓扑结构可以⽀持单层或多层复制关系,根据拓扑复杂性可以分为以下三种:⼀
主⼀从、⼀主多从、树状主从结构。
1.⼀主⼀从结构
⼀主⼀从结构是最简单的复制拓扑结构,⽤于主节点出现宕机时从节点提供故障转移⽀持。

2.⼀主多从结构
⼀主多从结构(又称为星形拓扑结构)使得应⽤端可以利⽤多个从节点实现读写分离(见图6-
5)。对于读占⽐较⼤的场景,可以把读命令发送到从节点来分担主节点压⼒。

树状主从结构(又称为树状拓扑结构)使得从节点不但可以复制主节点数据,同时可以作为
其他从节点的主节点继续向下层复制。通过引⼊复制中间层,可以有效降低主节点负载和需
要传送给从节点的数据量。

 

1. 保存主节点(master)信息
这⼀步只是保存主节点信息,保存主节点的ipport
2. 主从建⽴连接
从节点(slave)发现新的主节点后,会尝试和主节点建⽴⽹络连接。
3. 发送ping命令
连接建⽴成功后从节点发送ping请求进⾏⾸次通信,主要是检测主从之间⽹络套接字是否
可⽤、主节点当前是否可接受处理命令。
4. 权限验证
如果主节点要求密码验证,从节点必须正确的密码才能通过验证。
5. 同步数据集
主从复制连接正常通信后,主节点会把持有的数据全部发送给从节点。
6. 命令持续复制
接下来主节点会持续地把写命令发送给从节点,保证主从数据⼀致性。

全量复制
⼀般⽤于初次复制场景,Redis早期⽀持的复制功能只有全量复制,它会把主节点全部数据⼀
次性发送给从节点,当数据量较⼤时,会对主从节点和⽹络造成很⼤的开销。

 

1. 发送psync命令进⾏数据同步,由于是第⼀次进⾏复制,从节点没有复制偏移量和主节点
的运⾏ID,所以发送psync-1
2. 主节点根据psync-1解析出当前为全量复制,回复+FULLRESYNC响应。
3. 从节点接收主节点的响应数据保存运⾏ID和偏移量offset
4. 主节点执⾏bgsave保存RDB⽂件到本地
5. 主节点发送RDB⽂件给从节点,从节点把接收的RDB⽂件保存在本地并直接作为从节点
的数据⽂件
6. 对于从节点开始接收RDB快照到接收完成期间,主节点仍然响应读写命令,因此主节点会
把这期间写命令数据保存在复制客户端缓冲区内,当从节点加载完RDB⽂件后,主节点再
把缓冲区内的数据发送给从节点,保证主从之间数据⼀致性。
7. 从节点接收完主节点传送来的全部数据后会清空⾃⾝旧数据
8. 从节点清空数据后开始加载RDB⽂件
9. 从节点成功加载完RDB后,如果当前节点开启了AOF持久化功能, 它会⽴刻做
bgrewriteaof操作,为了保证全量复制后AOF持久化⽂件⽴刻可⽤。
部分复制
部分复制主要是Redis针对全量复制的过⾼开销做出的⼀种优化措施, 使⽤psync{runId}
{offset}命令实现。当从节点(slave)正在复制主节点 (master)时,如果出现⽹络闪断或者
命令丢失等异常情况时,从节点会向 主节点要求补发丢失的命令数据,如果主节点的复制积
压缓冲区内存在这部分数据则直接发送给从节点,这样就可以保持主从节点复制的⼀致性。

 

1. 当主从节点之间⽹络出现中断时,如果超过repl-timeout时间,主节点会认为从节点故障并
中断复制连接
2. 主从连接中断期间主节点依然响应命令,但因复制连接中断命令⽆法发送给从节点,不过
主节点内部存在的复制积压缓冲区,依然可以保存最近⼀段时间的写命令数据,默认最⼤
缓存1MB
3. 当主从节点⽹络恢复后,从节点会再次连上主节点
4. 当主从连接恢复后,由于从节点之前保存了⾃⾝已复制的偏移量和主节点的运⾏ID。因此
会把它们当作psync参数发送给主节点,要求进⾏部分复制操作。
5. 主节点接到psync命令后⾸先核对参数runId是否与⾃⾝⼀致,如果⼀ 致,说明之前复制的
是当前主节点;之后根据参数offset在⾃⾝复制积压缓冲区查找,如果偏移量之后的数据存
在缓冲区中,则对从节点发送+CONTINUE响应,表⽰可以进⾏部分复制。
6. 主节点根据偏移量把复制积压缓冲区⾥的数据发送给从节点,保证主从复制进⼊正常状

 

17.主从复制存在哪些问题呢?
主从复制虽好,但也存在⼀些问题:
⼀旦主节点出现故障,需要⼿动将⼀个从节点晋升为主节点,同时需要修改应⽤⽅的主节
点地址,还需要命令其他从节点去复制新的主节点,整个过程都需要⼈⼯⼲预。
主节点的写能⼒受到单机的限制。
主节点的存储能⼒受到单机的限制。
第⼀个问题是Redis的⾼可⽤问题,第⼆、三个问题属于Redis的分布式问题。

18.Redis Sentinel(哨兵)了解吗?
主从复制存在⼀个问题,没法完成⾃动故障转移。所以我们需要⼀个⽅案来完成⾃动故障转
移,它就是Redis Sentinel(哨兵)。
Redis Sentinel ,它由两部分组成,哨兵节点和数据节点:
哨兵节点: 哨兵系统由⼀个或多个哨兵节点组成,哨兵节点是特殊的 Redis 节点,不存
储数据,对数据节点进⾏监控。
数据节点: 主节点和从节点都是数据节点;
在复制的基础上,哨兵实现了 ⾃动化的故障恢复 功能,下⾯是官⽅对于哨兵功能的描述:
监控(Monitoring): 哨兵会不断地检查主节点和从节点是否运作正常。
⾃动故障转移(Automatic failover): 当 主节点 不能正常⼯作时,哨兵会开始 ⾃动故
障转移操作 ,它会将失效主节点的其中⼀个 从节点升级为新的主节点 ,并让其他从节点
改为复制新的主节点。
配置提供者(Configuration provider): 客户端在初始化时,通过连接哨兵来获得当前
Redis 服务的主节点地址。
通知(Notification): 哨兵可以将故障转移的结果发送给客户端。
其中,监控和⾃动故障转移功能,使得哨兵可以及时发现主节点故障并完成转移。⽽配置提
供者和通知功能,则需要在与客户端的交互中才能体现。

 

 
19.Redis Sentinel(哨兵)实现原理知道吗?
哨兵模式是通过哨兵节点完成对数据节点的监控、下线、故障转移。

 

 
Redis Sentinel通过三个定时监控任务完成对各个节点发现和监控:
1. 每隔10秒,每个Sentinel节点会向主节点和从节点发送info命令获取最新的拓扑结构
2. 每隔2秒,每个Sentinel节点会向Redis数据节点的 sentinel hello 频道上发送该Sentinel
节点对于主节点的判断以及当前Sentinel节点的信息
3. 每隔1秒,每个Sentinel节点会向主节点、从节点、其余Sentinel节点发送⼀条ping命令
做⼀次⼼跳检测,来确认这些节点当前是否可达
主观下线和客观下线
主观下线就是哨兵节点认为某个节点有问题,客观下线就是超过⼀定数量的哨兵节点认为
主节点有问题。
1. 主观下线
每个Sentinel节点会每隔1秒对主节点、从节点、其他Sentinel节点发送ping命令做⼼跳检
测,当这些节点超过 down-after-milliseconds没有进⾏有效回复,Sentinel节点就会对该节点
做失败判定,这个⾏为叫做主观下线。
2. 客观下线
Sentinel主观下线的节点是主节点时,该Sentinel节点会通过sentinel is- master-down-by
addr命令向其他Sentinel节点询问对主节点的判断,当超过 < quorum>个数,Sentinel节点
认为主节点确实有问题,这时该Sentinel节点会做出客观下线的决定
领导者Sentinel节点选举
Sentinel节点之间会做⼀个领导者选举的⼯作,选出⼀个Sentinel节点作为领导者进⾏故障
转移的⼯作。Redis使⽤了Raft算法实现领导者选举。
故障转移
领导者选举出的Sentinel节点负责故障转移,过程如下:

 

1. 在从节点列表中选出⼀个节点作为新的主节点,这⼀步是相对复杂⼀些的⼀步
2. Sentinel领导者节点会对第⼀步选出来的从节点执⾏slaveof no one命令让其成为主节点
3. Sentinel领导者节点会向剩余的从节点发送命令,让它们成为新主节点的从节点
4. Sentinel节点集合会将原来的主节点更新为从节点,并保持着对其关注,当其恢复后命
令它去复制新的主节点
20.领导者Sentinel节点选举了解吗?
Redis使⽤了Raft算法实 现领导者选举,⼤致流程如下:
1. 每个在线的Sentinel节点都有资格成为领导者,当它确认主节点主观 下线时候,会向其他
Sentinel节点发送sentinel is-master-down-by-addr命令, 要求将⾃⼰设置为领导者。
2. 收到命令的Sentinel节点,如果没有同意过其他Sentinel节点的sentinel is-master-down-by
addr命令,将同意该请求,否则拒绝。
3. 如果该Sentinel节点发现⾃⼰的票数已经⼤于等于maxquorum num
sentinels/2+1),那么它将成为领导者。
4. 如果此过程没有选举出领导者,将进⼊下⼀次选举。
21.新的主节点是怎样被挑选出来的?
选出新的主节点,⼤概分为这么⼏步:
1. 过滤:不健康(主观下线、断线)、5秒内没有回复过Sentinel节 点ping响应、与主节点
失联超过down-after-milliseconds*10秒。
2. 选择slave-priority(从节点优先级)最⾼的从节点列表,如果存在则返回,不存在则继
续。
3. 选择复制偏移量最⼤的从节点(复制的最完整),如果存在则返 回,不存在则继续。
4. 选择runid最⼩的从节
 
22.Redis 集群了解吗?
前⾯说到了主从存在⾼可⽤和分布式的问题,哨兵解决了⾼可⽤的问题,⽽集群就是终极⽅
案,⼀举解决⾼可⽤和分布式问题。
1. 数据分区: 数据分区 (或称数据分⽚) 是集群最核⼼的功能。集群将数据分散到多个节
点,⼀⽅⾯ 突破了 Redis 单机内存⼤⼩的限制, 存储容量⼤⼤增加 ; 另⼀⽅⾯ 每个主
节点都可以对外提供读服务和写服务, ⼤⼤提⾼了集群的响应能⼒ 。
2. ⾼可⽤: 集群⽀持主从复制和主节点的 ⾃动故障转移 (与哨兵类似) ,当任⼀节点
发⽣故障时,集群仍然可以对外提供服务。

 

 
⽅案⼀:节点取余分区
节点取余分区,⾮常好理解,使⽤特定的数据,⽐如Redis的键,或者⽤户ID之类,对响应的
hash值取余:hashkey%N,来确定数据映射到哪⼀个节点上。
不过该⽅案最⼤的问题是,当节点数量变化时,如扩容或收缩节点,数据节点映射关 系需要
重新计算,会导致数据的重新迁移。

 

⽅案⼆:⼀致性哈希分区
将整个 Hash 值空间组织成⼀个虚拟的圆环,然后将缓存节点的 IP 地址或者主机名做 Hash
值后,放置在这个圆环上。当我们需要确定某⼀个 Key 需 要存取到哪个节点上的时候,先对
这个 Key 做同样的 Hash 取值,确定在环上的位置,然后按照顺时针⽅向在环上⾏⾛,遇到
的第⼀个缓存节点就是要访问的节点。
⽐如说下⾯ 这张图⾥⾯,Key 1 Key 2 会落⼊到 Node 1 中,Key 3Key 4 会落⼊到 Node
2 中,Key 5 落⼊到 Node 3 中,Key 6 落⼊到 Node 4 中。

 

节点⽆影响。

但它还是存在问题:
缓存节点在圆环上分布不平均,会造成部分缓存节点的压⼒较⼤
当某个节点故障时,这个节点所要承担的所有访问都会被顺移到另⼀个节点上,会对后⾯
这个节点造成⼒。
⽅案三:虚拟槽分区
这个⽅案 ⼀致性哈希分区的基础上,引⼊了 虚拟节点 的概念。Redis 集群使⽤的便是该⽅
案,其中的虚拟节点称为 槽(slot。槽是介于数据和实际节点之间的虚拟概念,每个实际
节点包含⼀定数量的槽,每个槽包含哈希值在⼀定范围内的数据。

在使⽤了槽的⼀致性哈希分区中,槽是数据管理和迁移的基本单位。槽解耦了数据和实际节
点 之间的关系,增加或删除节点对系统的影响很⼩。仍以上图为例,系统中有 4 个实际节
点,假设为其分配 16 个槽(0-15)
0-3 位于 node14-7 位于 node2;以此类推....
如果此时删除 node2 ,只需要将槽 4-7 重新分配即可,例如槽 4-5 分配给 node1 ,槽 6
配给 node3 ,槽 7 分配给 node4 ,数据在其他节点的分布仍然较为均衡。

24.能说说Redis集群的原理吗?
Redis集群通过数据分区来实现数据的分布式存储,通过⾃动故障转移实现⾼可⽤。
集群创建数据分区是在集群创建的时候完成的。

 

节点握⼿
节点握⼿是指⼀批运⾏在集群模式下的节点通过Gossip协议彼此通信, 达到感知对⽅的过
程。节点握⼿是集群彼此通信的第⼀步,由客户端发起命 令:cluster meet{ip}{port}。完成节
点握⼿之后,⼀个个的Redis节点就组成了⼀个多节点的集群。
分配槽(slot
Redis集群把所有的数据映射到16384个槽中。每个节点对应若⼲个槽,只有当节点分配了槽,
才能响应和这些槽关联的键命令。通过 cluster addslots命令为节点分配槽。

故障转移
Redis集群的故障转移和哨兵的故障转移类似,但是Redis集群中所有的节点都要承担状态维护
的任务。
故障发现
Redis集群内节点通过ping/pong消息实现节点通信,集群中每个节点都会定期向其他节点发送
ping消息,接收节点回复pong 消息作为响应。如果在cluster-node-timeout时间内通信⼀直失
败,则发送节 点会认为接收节点存在故障,把接收节点标记为主观下线(pfail)状态。

 

当某个节点判断另⼀个节点主观下线后,相应的节点状态会跟随消息在集群内传播。通过
Gossip消息传播,集群内节点不断收集到故障节点的下线报告。当 半数以上持有槽的主节点
都标记某个节点是主观下线时。触发客观下线流程

 

故障恢复
故障节点变为客观下线后,如果下线节点是持有槽的主节点则需要在它 的从节点中选出⼀个
替换它,从⽽保证集群的⾼可⽤。
1. 资格检查
每个从节点都要检查最后与主节点断线时间,判断是否有资格替换故障 的主节点。
2. 准备选举时间
当从节点符合故障转移资格后,更新触发故障选举的时间,只有到达该 时间后才能执⾏
后续流程。
3. 发起选举
当从节点定时任务检测到达故障选举时间(failover_auth_time)到达后,发起选举流程。
4. 选举投票
持有槽的主节点处理故障选举消息。投票过程其实是⼀个领导者选举的过程,如集群内有
N个持有槽的主节 点代表有N张选票。由于在每个配置纪元内持有槽的主节点只能投票给
⼀个 从节点,因此只能有⼀个从节点获得N/2+1的选票,保证能够找出唯⼀的从节点

5. 替换主节点
当从节点收集到⾜够的选票之后,触发替换主节点操作。
部署Redis集群⾄少需要⼏个物理节点?
在投票选举的环节,故障主节点也算在投票数内,假设集群内节点规模是33从,其中有2
主节点部署在⼀台机器上,当这台机器宕机时,由于从节点⽆法收集到 3/2+1个主节点选票将
导致故障转移失败。这个问题也适⽤于故障发现环节。因此部署集群时所有主节点最少需要
部署在3台物理机上才能避免单点问题。

 

 
Redis集群提供了灵活的节点扩容和收缩⽅案,可以在不影响集群对外服务的情况下,为集群
添加节点进⾏扩容也可以下线部分节点进⾏缩容
 
群扩容和缩容的关键点,就在于槽和节点的对应关系,扩容和缩容就是将⼀部分 数据
移给新节点。
 
例如下⾯⼀个集群,每个节点对应若⼲个槽,每个槽对应⼀定的数据,如果希望加⼊1个节点
希望实现集群扩容时,需要通过相关命令把⼀部分槽和内容迁移给新节点。
缩容也是类似,先把槽和数据迁移到其它节点,再把对应的节点下线。
缓存击穿
⼀个并发访问量⽐较⼤的key在某个时间过期,导致所有的请求直接打在DB上。

解决⽅案:
1. 加锁更新,⽐如请求查询A,发现缓存中没有,对A这个key加锁,同时去数据库查询数
据,写⼊缓存,再返回给⽤户,这样后⾯的请求就可以从缓存中拿到数据了。

 

2. 将过期时间组合写在value中,通过异步的⽅式不断的刷新过期时间,防⽌此类现象。
缓存穿透
缓存穿透指的查询缓存和数据库中都不存在的数据,这样每次请求直接打到数据库,就好像
缓存不存在⼀样。

 

 

缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意
义。
缓存穿透可能会使后端存储负载加⼤,如果发现⼤量存储层空命中,可能就是出现了缓存穿
透问题。
缓存穿透可能有两种原因:
1. ⾃⾝业务代码问题
2. 恶意攻击,爬⾍造成空命中
它主要有两种解决办法:
缓存空值/默认值
⼀种⽅式是在数据库不命中之后,把⼀个空对象或者默认值保存到缓存,之后再访问这个数
据,就会从缓存中获取,这样就保护了数据库。

 

缓存空值有两⼤问题:
1. 空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间(如果是攻击,问题
更严重),⽐较有效的⽅法是针对这类数据设置⼀个较短的过期时间,让其⾃动剔除。
2. 缓存层和存储层的数据会有⼀段时间窗⼜的不⼀致,可能会对业务有⼀定影响。
例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存
层和存储层数据的不⼀致。
这时候可以利⽤消息队列或者其它异步⽅式清理缓存中的空对象。
布隆过滤器
除了缓存空对象,我们还可以在存储和缓存之前,加⼀个布隆过滤器,做⼀层过滤。
布隆过滤器⾥会保存数据是否存在,如果判断数据不不能再,就不会访问存储。

缓存雪崩
某⼀时刻发⽣⼤规模的缓存失效的情况,例如缓存服务宕机、⼤量key在同⼀时间过期,这样
的后果就是⼤量的请求进来直接打到DB上,可能导致整个系统的崩溃,称为雪崩。

缓存雪崩是三⼤缓存问题⾥最严重的⼀种,我们来看看怎么预防和处理。
提⾼缓存可⽤性
1. 集群部署:通过集群来提升缓存的可⽤性,可以利⽤Redis本⾝的Redis Cluster或者第三⽅
集群⽅案如Codis等。
2. 多级缓存:设置多级缓存,第⼀级缓存失效的基础上,访问⼆级缓存,每⼀级缓存的失效
时间都不同。
过期时间
1. 均匀过期:为了避免⼤量的缓存在同⼀时间过期,可以把不同的 key 过期时间随机⽣成,
避免过期时间太过集中。
2. 热点数据永不过期。
熔断降级
1. 服务熔断:当缓存服务器宕机或超时响应时,为了防⽌整个系统出现雪崩,暂时停⽌业务
服务访问缓存系统。
2. 服务降级:当出现⼤量缓存失效,⽽且处在⾼并发⾼负荷的情况下,在业务系统内部暂时
舍弃对⼀些⾮核⼼的接⼜和数据的请求,⽽直接返回⼀个提前准备好的 fallback(退路)
错误处理信息。

27.能说说布隆过滤器吗?

布隆过滤器,它是⼀个连续的数据结构,每个存储位存储都是⼀个 bit ,即 0 或者 1 , 来标识
数据是否存在。
存储数据的时时候,使⽤K个不同的哈希函数将这个变量映射为bit列表的的K个点,把它们置
1
我们判断缓存key是否存在,同样,K个哈希函数,映射到bit列表上的K个点,判断是不是1
如果全不是1,那么key不存在;
如果都是1,也只是表⽰key可能存在。

 

布隆过滤器也有⼀些缺点:
1. 它在判断元素是否在集合中时是有⼀定错误⼏率,因为哈希算法有⼀定的碰撞的概率。
2. 不⽀持删除元素。

28.如何保证缓存和数据库数据的⼀致性?
根据CAP理论,在保证可⽤性和分区容错性的前提下,⽆法保证⼀致性,所以缓存和数据库
的绝对⼀致是不可能实现的,只能尽可能保存缓存和数据库的最终⼀致性。
选择合适的缓存更新策略
1. 删除缓存⽽不是更新缓存
当⼀个线程对缓存的key进⾏写操作的时候,如果其它线程进来读数据库的时候,读到的就是
脏数据,产⽣了数据不⼀致问题。
相⽐较⽽⾔,删除缓存的速度⽐更新缓存的速度快很多,所⽤时间相对也少很多,读脏数据
的概率也⼩很多。

2. 先更数据,后删缓存

先更数据库还是先删缓存?这是⼀个问题。
更新数据,耗时可能在删除缓存的百倍以上。在缓存中不存在对应的key,数据库又没有完成
更新的时候,如果有线程进来读取数据,并写⼊到缓存,那么在更新成功之后,这个key就是
⼀个脏数据。
毫⽆疑问,先删缓存,再更数据库,缓存中key不存在的时间的时间更长,有更⼤的概率会产
⽣脏数据。

 

 

⽬前最流⾏的缓存读写策略cache-aside-pattern就是采⽤先更数据库,再删缓存的⽅式。
缓存不⼀致处理
如果不是并发特别⾼,对缓存依赖性很强,其实⼀定程序的不⼀致是可以接受的。
但是如果对⼀致性要求⽐较⾼,那就得想办法保证缓存和数据库中数据⼀致。
缓存和数据库数据不⼀致常见的两种原因:
缓存key删除失败
并发导致写⼊了脏数据

 

消息队列保证key被删除
可以引⼊消息队列,把要删除的key或者删除失败的key丢尽消息队列,利⽤消息队列的重试
机制,重试删除对应的key

这种⽅案看起来不错,缺点是对业务代码有⼀定的侵⼊性。
数据库订阅+消息队列保证key被删除
可以⽤⼀个服务(⽐如阿⾥的 canal)去监听数据库的binlog,获取需要操作的数据。
然后⽤⼀个公共的服务获取订阅程序传来的信息,进⾏缓存删除操作。

 

这种⽅式降低了对业务的侵⼊,但其实整个系统的复杂度是提升的,适合基建完善的⼤⼚。

延时双删防⽌脏数据
还有⼀种情况,是在缓存不存在的时候,写⼊了脏数据,这种情况在先删缓存,再更数据库
的缓存更新策略下发⽣的⽐较多,解决⽅案是延时双删。
简单说,就是在第⼀次删除缓存之后,过了⼀段时间之后,再次删除缓存。

这种⽅式的延时时间设置需要仔细考量和测试。
设置缓存过期时间兜底
这是⼀个朴素但是有⽤的办法,给缓存设置⼀个合理的过期时间,即使发⽣了缓存数据不⼀
致的问题,它也不会永远不⼀致下去,缓存过期的时候,⾃然又会恢复⼀致。

 

29.如何保证本地缓存和分布式缓存的⼀致?
PS:这道题⾯试很少问,但实际⼯作中很常见。
在⽇常的开发中,我们常常采⽤两级缓存:本地缓存+分布式缓存。
所谓本地缓存,就是对应服务器的内存缓存,⽐如Caffeine,分布式缓存基本就是采⽤Redis

 

Redis缓存,数据库发⽣更新,直接删除缓存的key即可,因为对于应⽤系统⽽⾔,它是⼀种中
⼼化的缓存。
但是本地缓存,它是⾮中⼼化的,散落在分布式服务的各个节点上,没法通过客户端的请求
删除本地缓存的key,所以得想办法通知集群所有节点,删除对应的本地缓存key

可以采⽤消息队列的⽅式:
1. 采⽤Redis本⾝的Pub/Sub机制,分布式集群的所有节点订阅删除本地缓存频道,删除Redis
缓存的节点,同事发布删除本地缓存消息,订阅者们订阅到消息后,删除对应的本地
key
但是Redis的发布订阅不是可靠的,不能保证⼀定删除成功。
2. 引⼊专业的消息队列,⽐如RocketMQ,保证消息的可靠性,但是增加了系统的复杂度。
3. 设置适当的过期时间兜底,本地缓存可以设置相对短⼀些的过期时间。

30.怎么处理热key
什么是热Key
所谓的热key,就是访问频率⽐较的key
⽐如,热门新闻事件或商品,这类key通常有⼤流量的访问,对存储这类信息的 Redis来说,
是不⼩的压⼒。
假如Redis集群部署,热key可能会造成整体流量的不均衡,个别节点出现OPS过⼤的情况,极
端情况下热点key甚⾄会超过 Redis本⾝能够承受的OPS

对热key的处理,最关键的是对热点key的监控,可以从这些端来监控热点key:
1. 客户端
客户端其实是距离key“最近的地⽅,因为Redis命令就是从客户端发出的,例如在客户端
设置全局字典(key和调⽤次数),每次调⽤Redis命令时,使⽤这个字典进⾏记录。
2. 代理端
TwemproxyCodis这些基于代理的Redis分布式架构,所有客户端的请求都是通过代理
端完成的,可以在代理端进⾏收集统计。
3. Redis服务端
使⽤monitor命令统计热点key是很多开发和运维⼈员⾸先想到,monitor命令可以监控到
Redis执⾏的所有命令。
只要监控到了热key,对热key的处理就简单了:
1. 把热key打散到不同的服务器,降低压⼒
2. 加⼊⼆级缓存,提前加载热key数据到内存中,如果redis宕机,⾛内存查询

 

31.缓存预热怎么做呢?
所谓缓存预热,就是提前把数据库⾥的数据刷到缓存⾥,通常有这些⽅法:
1、直接写个缓存刷新页⾯或者接⼜,上线时⼿动操作
2、数据量不⼤,可以在项⽬启动的时候⾃动进⾏加载
3、定时任务刷新缓存.

32.热点key重建?问题?解决?
开发的时候⼀般使⽤缓存+过期时间的策略,既可以加速数据读写,又保证数据的定期更
新,这种模式基本能够满⾜绝⼤部分需求。
但是有两个问题如果同时出现,可能就会出现⽐较⼤的问题:
当前key是⼀个热点key(例如⼀个热门的娱乐新闻),并发量⾮常⼤。
重建缓存不能在短时间完成,可能是⼀个复杂计算,例如复杂的 SQL、多次IO、多个依
赖等。 在缓存失效的瞬间,有⼤量线程来重建缓存,造成后端负载加⼤,甚⾄可能会让应
⽤崩溃。
怎么处理呢?
要解决这个问题也不是很复杂,解决问题的要点在于:
减少重建缓存的次数。
数据尽可能⼀致。
较少的潜在危险。
所以⼀般采⽤如下⽅式:
1. 互斥锁(mutex key
这种⽅法只允许⼀个线程重建缓存,其他线程等待重建缓存的线程执⾏完,重新从缓存获
取数据即可。
2. 永远不过期
永远不过期包含两层意思:
从缓存层⾯来看,确实没有设置过期时间,所以不会出现热点key过期后产⽣的问题,也
就是物理不过期。
从功能层⾯来看,为每个value设置⼀个逻辑过期时间,当发现超过逻辑过期时间后,会
使⽤单独的线程去构建缓存。

什么是⽆底洞问题?
2010年,FacebookMemcache节点已经达到了3000个,承载着TB级别的缓存数据。但开发和
运维⼈员发现了⼀个问题,为了满⾜业务要求添加了⼤量新Memcache节点,但是发现性能不
但没有好转反⽽下降了,当时将这 种现象称为缓存的⽆底洞现象。
那么为什么会产⽣这种现象呢?
通常来说添加节点使得Memcache集群 性能应该更强了,但事实并⾮如此。键值数据库由于通
常采⽤哈希函数将 key映射到各个节点上,造成key的分布与业务⽆关,但是由于数据量和访
问量的持续增长,造成需要添加⼤量节点做⽔平扩容,导致键值分布到更多的 节点上,所以
⽆论是Memcache还是Redis的分布式,批量操作通常需要从不同节点上获取,相⽐于单机批量
操作只涉及⼀次⽹络操作,分布式批量操作会涉及多次⽹络时间。
⽆底洞问题如何优化呢?
先分析⼀下⽆底洞问题:
客户端⼀次批量操作会涉及多次⽹络操作,也就意味着批量操作会随着节点的增多,耗时
会不断增⼤。
⽹络连接数变多,对节点的性能也有⼀定影响。
常见的优化思路如下:
命令本⾝的优化,例如优化操作语句等。
减少⽹络通信次数。
降低接⼊成本,例如客户端使⽤长连/连接池、NIO等。

34.Redis报内存不⾜怎么处理?
Redis 内存不⾜有这么⼏种处理⽅式:
修改配置⽂件 redis.conf maxmemory 参数,增加 Redis 可⽤内存
也可以通过命令set maxmemory动态设置内存上限
修改内存淘汰策略,及时释放内存空间
使⽤ Redis 集群模式,进⾏横向扩容。

35.Redis的过期数据回收策略有哪些?
Redis主要有2种过期数据回收策略:

惰性删除
惰性删除指的是当我们查询key的时候才对key进⾏检测,如果已经达到过期时间,则删除。
显然,他有⼀个缺点就是如果这些过期的key没有被访问,那么他就⼀直⽆法被删除,⽽且⼀
直占⽤内存。
定期删除
定期删除指的是Redis每隔⼀段时间对数据库做⼀次检查,删除⾥⾯的过期key。由于不可能对
所有key去做轮询来删除,所以Redis会每次随机取⼀些key去做检查和删除。

36.Redis有哪些内存溢出控制/内存淘汰策略?
Redis所⽤内存达到maxmemory上限时会触发相应的溢出控制策略,Redis⽀持六种策略:

1. noeviction:默认策略,不会删除任何数据,拒绝所有写⼊操作并返 回客户端错误信息,
此 时Redis只响应读操作。
2. volatile-lru:根据LRU算法删除设置了超时属性(expire)的键,直 到腾出⾜够空间为
⽌。如果没有可删除的键对象,回退到noeviction策略。
3. allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性, 直到腾出⾜够空间为
⽌。
4. allkeys-random:随机删除所有键,直到腾出⾜够空间为⽌。
5. volatile-random:随机删除过期键,直到腾出⾜够空间为⽌。
6. volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果 没有,回退到
noeviction策略。

 

API或数据结构使⽤不合理
通常Redis执⾏命令速度⾮常快,但是不合理地使⽤命令,可能会导致执⾏速度很慢,导
致阻塞,对于⾼并发的场景,应该尽量避免在⼤对象上执⾏算法复杂 度超过On)的命
令。
对慢查询的处理分为两步:
1. 发现慢查询: slowlog get{n}命令可以获取最近 的n条慢查询命令;
2. 发现慢查询后,可以从两个⽅向去优化慢查询:
1)修改为低算法复杂度的命令,如hgetall改为hmget等,禁⽤keyssort等命 令
2)调整⼤对象:缩减⼤对象数据或把⼤对象拆分为多个⼩对象,防⽌⼀次命令操作过
多的数据。
CPU饱和的问题
单线程的Redis处理命令时只能使⽤⼀个CPU。⽽CPU饱和是指Redis单核CPU使⽤率跑到
接近100%
针对这种情况,处理步骤⼀般如下:
1. 判断当前Redis并发量是否已经达到极限,可以使⽤统计命令redis-cli-h{ip}-p{port}--
stat获取当前 Redis使⽤情况
2. 如果Redis的请求⼏万+,那么⼤概就是RedisOPS已经到了极限,应该做集群化⽔品
扩展来分摊OPS压⼒
3. 如果只有⼏百⼏千,那么就得排查命令和内存的使⽤
持久化相关的阻塞
对于开启了持久化功能的Redis节点,需要排查是否是持久化导致的阻塞。
1. fork阻塞
fork操作发⽣在RDBAOF重写时,Redis主线程调⽤fork操作产⽣共享 内存的⼦进
程,由⼦进程完成持久化⽂件重写⼯作。如果fork操作本⾝耗时过长,必然会导致主线
程的阻塞。
2. AOF刷盘阻塞
当我们开启AOF持久化功能时,⽂件刷盘的⽅式⼀般采⽤每秒⼀次,后台线程每秒对
AOF⽂件做fsync操作。当硬盘压⼒过⼤时,fsync操作需要等 待,直到写⼊完成。如果
主线程发现距离上⼀次的fsync成功超过2秒,为了 数据安全性它会阻塞直到后台线程
执⾏fsync操作完成。
3. HugePage写操作阻塞
对于开启Transparent HugePages的 操作系统,每次写命令引起的复制内存页单位由4K
变为2MB,放⼤了512 倍,会拖慢写操作的执⾏时间,导致⼤量写操作慢查询。

 

38.key问题了解吗?
Redis使⽤过程中,有时候会出现⼤key的情况, ⽐如:
单个简单的key存储的value很⼤,size超过10KB
hash setzsetlist 中存储过多的元素(以万为单位)
key会造成什么问题呢?
客户端耗时增加,甚⾄超时
对⼤key进⾏IO操作时,会严重占⽤带宽和CPU
造成Redis集群中数据倾斜
主动删除、被动删等,可能会导致阻塞
如何找到⼤key?
bigkeys命令:使⽤bigkeys命令以遍历的⽅式分析Redis实例中的所有Key,并返回整体统
计信息与每个数据类型中Top1的⼤Key
redis-rdb-toolsredis-rdb-tools是由Python写的⽤来分析Redisrdb快照⽂件⽤的⼯具,它
可以把rdb快照⽂件⽣成json⽂件或者⽣成报表⽤来分析Redis的使⽤详情。

删除⼤key
Redis版本⼤于4.0时,可使⽤UNLINK命令安全地删除⼤Key,该命令能够以⾮阻塞
的⽅式,逐步地清理传⼊的Key
Redis版本⼩于4.0时,避免使⽤阻塞式命令KEYS,⽽是建议通过SCAN命令执⾏增
量迭代扫描key,然后判断进⾏删除。
压缩和拆分key
vaulestring时,⽐较难拆分,则使⽤序列化、压缩算法将key的⼤⼩控制在合理范
围内,但是序列化和反序列化都会带来更多时间上的消耗。
valuestring,压缩之后仍然是⼤key,则需要进⾏拆分,⼀个⼤key分为不同的部
分,记录每个部分的key,使⽤multiget等操作实现事务读取。
valuelist/set等集合类型时,根据预估的数据规模来进⾏分⽚,不同的元素计算后
分到不同的⽚。

 

39.Redis常见性能问题和解决⽅案?
1. Master 最好不要做任何持久化⼯作,包括内存快照和 AOF ⽇志⽂件,特别是不要启⽤内
存快照做持久化。
2. 如果数据⽐较关键,某个 Slave 开启 AOF 备份数据,策略为每秒同步⼀次。
3. 为了主从复制的速度和连接的稳定性,Slave Master 最好在同⼀个局域⽹内。
4. 尽量避免在压⼒较⼤的主库上增加从库。
5. Master 调⽤ BGREWRITEAOF 重写 AOF ⽂件,AOF 在重写的时候会占⼤量的 CPU 和内
存资源,导致服务 load 过⾼,出现短暂服务暂停现象。
6. 为了 Master 的稳定性,主从复制不要⽤图状结构,⽤单向链表结构更稳定,即主从关
为:Master<–Slave1<–Slave2<–Slave3…,这样的结构也⽅便解决单点故障问题,实现
Slave Master 的替换,也即,如果 Master 挂了,可以⽴马启⽤ Slave1 Master,其他不
变。

Redis应⽤
40.使⽤Redis 如何实现异步队列?
我们知道redis⽀持很多种结构的数据,那么如何使⽤redis作为异步队列使⽤呢?
⼀般有以下⼏种⽅式:
使⽤list作为队列,lpush⽣产消息,rpop消费消息
这种⽅式,消费者死循环rpop从队列中消费消息。但是这样,即使队列⾥没有消息,也会进⾏
rpop,会导致Redis CPU的消耗

可以通过让消费者休眠的⽅式的⽅式来处理,但是这样又会又消息的延迟问题。
-使⽤list作为队列,lpush⽣产消息,brpop消费消息
brpoprpop的阻塞版本,list为空的时候,它会⼀直阻塞,直到list中有值或者超时。
这种⽅式只能实现⼀对⼀的消息队列。
使⽤Redispub/sub来进⾏消息的发布/订阅
发布/订阅模式可以1N的消息发布/订阅。发布者将消息发布到指定的频道频道
channel),订阅相应频道的客户端都能收到消息。

 

42.Redis ⽀持事务吗?
Redis提供了简单的事务,但它对事务ACID的⽀持并不完备。
multi命令代表事务开始,exec命令代表事务结束,它们之间的命令是原⼦顺序执⾏的:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:a:follow user:b
QUEUED
127.0.0.1:6379> sadd user:b:fans user:a
QUEUED
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 0
127.0.0.1:6379> exec 1) (integer) 1
2) (integer) 1
Redis事务的原理,是所有的指令在 exec 之前不执⾏,⽽是缓存在
服务器的⼀个事务队列中,服务器⼀旦收到 exec 指令,才开执⾏整个事务队列,执⾏完毕后
⼀次性返回所有指令的运⾏结果。
因为Redis执⾏命令是单线程的,所以这组命令顺序执⾏,⽽且不会被其它线程打断。
Redis事务的注意点有哪些?
需要注意的点有:
Redis 事务是不⽀持回滚的,不像 MySQL 的事务⼀样,要么都执⾏要么都不执⾏;
Redis 服务端在执⾏事务的过程中,不会被其他客户端发送来的命令请求打断。直到事务
命令全部执⾏完毕才会执⾏其他客户端的命令。
Redis 事务为什么不⽀持回滚?
Redis 的事务不⽀持回滚。
如果执⾏的命令有语法错误,Redis 会执⾏失败,这些问题可以从程序层⾯捕获并解决。但是
如果出现其他问题,则依然会继续执⾏余下的命令。
这样做的原因是因为回滚需要增加很多⼯作,⽽不⽀持回滚则可以保持简单、快速的特性

 

43.RedisLua脚本的使⽤了解吗?
Redis的事务功能⽐较简单,平时的开发中,可以利⽤Lua脚本来增强Redis的命令。
Lua脚本能给开发⼈员带来这些好处:
Lua脚本在Redis中是原⼦执⾏的,执⾏过程中间不会插⼊其他命令。
Lua脚本可以帮助开发和运维⼈员创造出⾃⼰定制的命令,并可以将这 些命令常驻在
Redis内存中,实现复⽤的效果。
Lua脚本可以将多条命令⼀次性打包,有效地减少⽹络开销。
⽐如这⼀段很(烂)经(⼤)典(街)的秒杀系统利⽤lua扣减Redis库存的脚本

 

45.Redis实现分布式锁了解吗?
Redis是分布式锁本质上要实现的⽬标就是在 Redis ⾥⾯占⼀个茅坑,当别的进程也要来占
时,发现已经有⼈蹲在那⾥了,就只好放弃或者稍后再试。
V1setnx命令
占坑⼀般是使⽤ setnx(set if not exists) 指令,只允许被⼀个客户端占坑。先来先占, ⽤完了,
再调⽤ del 指令释放茅坑。

但是有个问题,如果逻辑执⾏到中间出现异常了,可能会导致 del 指令没有被调⽤,这样就会
陷⼊死锁,锁永远得不到释放。
V2:锁超时释放
所以在拿到锁之后,再给锁加上⼀个过期时间,⽐如 5s,这样即使中间出现异常也可以保证
5 秒之后锁会⾃动释放
但是以上逻辑还有问题。如果在 setnx expire 之间服务器进程突然挂掉了,可能是因为机器
掉电或者是被⼈为杀掉的,就会导致 expire 得不到执⾏,也会造成死锁。
这种问题的根源就在于 setnx expire 是两条指令⽽不是原⼦指令。如果这两条指令可以⼀起
执⾏就不会出现问题。
V3:set指令
这个问题在Redis 2.8 版本中得到了解决,这个版本加⼊了 set 指令的扩展参数,使得 setnx
expire 指令可以⼀起执⾏

上⾯这个指令就是 setnx expire 组合在⼀起的原⼦指令,这个就算是⽐较完善的分布式锁
了。
当然实际的开发,没⼈会去⾃⼰写分布式锁的命令,因为有专业的轮⼦——Redisson

 

1. 字符串redis没有直接使⽤C语⾔传统的字符串表⽰,⽽是⾃⼰实现的叫做简单动态字符
SDS的抽象类型。
C语⾔的字符串不记录⾃⾝的度信息,⽽SDS则保存了度信息,这样将获取字符串
度的时间由O(N)降低到了O(1),同时可以避免缓冲区溢出和减少修改字符串度时所需的
内存重分配次数。

2. 链表linkedlist redis链表是⼀个双向⽆环链表结构,很多发布订阅、慢查询、监视器功能
都是使⽤到了链表来实现,每个链表的节点由⼀个listNode结构来表⽰,每个节点都有指
向前置节点和后置节点的指针,同时表头节点的前置和后置节点都指向NULL

3. 字典dict :⽤于保存键值对的抽象数据结构。Redis使⽤hash表作为底层实现,⼀个哈希表
⾥可以有多个哈希表节点,⽽每个哈希表节点就保存了字典⾥中的⼀个键值对。
每个字典带有两个hash表,供平时使⽤和rehash时使⽤,hash表使⽤链地址法来解决键冲
突,被分配到同⼀个索引位置的多个键值对会形成⼀个单向链表,在对hash表进⾏扩容或
者缩容的时候,为了服务的可⽤性,rehash的过程不是⼀次性完成的,⽽是渐进式的。

4. 跳跃表skiplist :跳跃表是有序集合的底层实现之⼀,Redis中在实现有序集合键和集群节
点的内部结构中都是⽤到了跳跃表。Redis跳跃表由zskiplistzskiplistNode组成,zskiplist
⽤于保存跳跃表信息(表头、表尾节点、度等),zskiplistNode⽤于表⽰表跳跃节点,
每个跳跃表节点的层⾼都是1-32的随机数,在同⼀个跳跃表中,多个节点可以包含相同的
分值,但是每个节点的成员对象必须是唯⼀的,节点按照分值⼤⼩排序,如果分值相同,
则按照成员对象的⼤⼩排序。

 

 

 

49.跳跃表是如何实现的?原理?
PS:跳跃表是⽐较常问的⼀种结构。
跳跃表(skiplist)是⼀种有序数据结构,它通过在每个节点中维持多个指向其它节点的指
针,从⽽达到快速访问节点的⽬的。

为什么使⽤跳跃表?
⾸先,因为 zset 要⽀持随机的插⼊和删除,所以它 不宜使⽤数组来实现,关于排序问题,我
们也很容易就想到 红⿊树/ 平衡树 这样的树形结构,为什么 Redis 不使⽤这样⼀些结构呢?
1. 性能考虑: 在⾼并发的情况下,树形结构需要执⾏⼀些类似于 rebalance 这样的可能涉及
整棵树的操作,相对来说跳跃表的变化只涉及局部;
2. 实现考虑: 在复杂度与红⿊树相同的情况下,跳跃表实现起来更简单,看起来也更加直
观;
基于以上的⼀些考虑,Redis 基于 William Pugh 的论⽂做出⼀些改进后采⽤了 跳跃表 这样
的结构。
本质是解决查找问题。
跳跃表是怎么实现的?
跳跃表的节点⾥有这些元素:
跳跃表节点的level数组可以包含多个元素,每个元素都包含⼀个指向其它节点的指针,程
序可以通过这些层来加快访问其它节点的速度,⼀般来说,层的数量⽉多,访问其它节点
的速度就越快。
每次创建⼀个新的跳跃表节点的时候,程序都根据幂次定律,随机⽣成⼀个介于132
间的值作为level数组的⼤⼩,这个⼤⼩就是层的⾼度
前进指针
每个层都有⼀个指向表尾的前进指针(level[i].forward属性),⽤于从表头向表尾⽅向访
问节点。
我们看⼀下跳跃表从表头到表尾,遍历所有节点的路径:

 

跨度
层的跨度⽤于记录两个节点之间的距离。跨度是⽤来计算排位(rank)的:在查找某个节
点的过程中,将沿途访问过的所有层的跨度累计起来,得到的结果就是⽬标节点在跳跃表
中的排位。
例如查找,分值为3.0、成员对象为o3的节点时,沿途经历的层:查找的过程只经过了⼀
个层,并且层的跨度为3,所以⽬标节点在跳跃表中的排位为3

分值和成员
节点的分值(score属性)是⼀个double类型的浮点数,跳跃表中所有的节点都按分值从⼩
到⼤来排序。
节点的成员对象(obj属性)是⼀个指针,它指向⼀个字符串对象,⽽字符串对象则保存
这⼀个SDS值。

50.压缩列表了解吗?
压缩列表是 Redis 为了节约内存 ⽽使⽤的⼀种数据结构,是由⼀系列特殊编码的连续内存快
组成的顺序型数据结构。
⼀个压缩列表可以包含任意多个节点(entry),每个节点可以保存⼀个字节数组或者⼀个整
数值。
压缩列表由这么⼏部分组成:
zlbyttes :记录整个压缩列表占⽤的内存字节数
zltail :记录压缩列表表尾节点距离压缩列表的起始地址有多少字节
zllen :记录压缩列表包含的节点数量
entryX :列表节点
zlend :⽤于标记压缩列表的末
51.快速列表 quicklist 了解吗?
Redis 早期版本存储 list 列表数据结构使⽤的是压缩列表 ziplist 和普通的双向链表 linkedlist
也就是说当元素少时使⽤ ziplist,当元素多时⽤ linkedlist
但考虑到链表的附加空间相对较⾼, prev next 指针就要占去 16 个字节(64 位操作系
统占⽤ 8 个字节),另外每个节点的内存都是单独分配,会家具内存的碎⽚化,影响内存管
理效率。
后来 Redis 新版本(3.2)对列表数据结构进⾏了改造,使⽤ quicklist 代替了 ziplist
linkedlist quicklist是综合考虑了时间效率与空间效率引⼊的新型数据结构。
quicklistlistziplist结合⽽成,它是⼀个由ziplist充当节点的双向链表。

 

52.假如Redis⾥⾯有1亿个key,其中有10wkey是以某个固定的已知的前缀
开头的,如何将它们全部找出来?
使⽤ keys 指令可以扫出指定模式的 key 列表。但是要注意 keys 指令会导致线程阻塞⼀段时
间,线上服务会停顿,直到指令执⾏完毕,服务才能恢复。这个时候可以使⽤ scan
令, scan 指令可以⽆阻塞的提取出指定模式的 key 列表,但是会有⼀定的重复概率,在客
户端做⼀次去重就可以了,但是整体所花费的时间会⽐直接⽤ keys 指令长。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值