0%

Redis-面试宝典

Redis基础

Redis是什么?

Redis是c语言编写的,是开源免费的。是一个高性能的分布式内存数据库(存储结构为key/value键值对)。
Redis是基于内存运行的,并支持持久化的NoSQL数据库。是单线程的。

Redis优缺点

优点

  • 读写性能优异,Redis能读的速度是110000次/s,写的速度是81000次/s。
  • 支持数据持久化,支持AOF和RDB两种持久化方式。
  • 支持事务 // TODO
  • 数据结构丰富,支持String,hash,set,zset,list等数据结构
  • 支持主从复制,哨兵机制,分布式集群部署

缺点

  • 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
  • Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
  • 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
  • Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。

Redis的应用场景

缓存热点数据(常被访问的数据)
可以做消息中间件(发布,订阅)
可以实现分布式锁
可以实现分布式session(web session保存在redis)

Redis有哪些数据类型?

string(字符串)
hash(哈希类型)
list(链表)
set(String类型的无序集合,集合元素是唯一的,不可重复的)
zset(String类型的有序集合,集合元素是唯一的,不可重复的,每个元素都会关联一个double类型的分数,通过分数来排序)

Redis常用命令

ping 连接redis
select 1选择连接哪个数据库,redis默认有16个库
dbsize 查看当前数据库key的数量
flushdb 清空当前库key
flushall 清空全部库key
keys * 查看当前数据库的所有key
exists name 判断当前key是否存在
expire name 6 设置key的过期时间(以秒为单位)
ttl name 查看当前key还有多久过期

Redis的线程模型

Redis线程模型

Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。

  • 文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
  • 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。

Redis持久化方式

持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。

简单介绍下Redis持久化

RDB(Redis Database)持久化(指定时间间隔对数据进行快照存储)
RDB是默认的持久化方式。
RDB持久化是将内存中数据以快照的方式写到二进制文件中,默认的数据文件名为dump.rdb。
可以通过配置文件中的save参数来定义快照的周期,列如:通过配置设置在n秒内如果超过m个key键修改就自动做快照。
当Redis重启时将文件内容读取到内存即完成数据恢复。

AOF(Append Only File)持久化(将每个写操作都记录到日志文件中)
当RDB持久化与AOF持久化同时开启,数据恢复时Redis优先选择AOF数据。
AOF持久化是以日志的形式来记录每个写操作,默认的数据文件名为appendonly.aof。
将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件。默认策略是每秒同步(异步操作,每秒记录),性能较差,但数据完整性较好,最多丢失一秒的数据。
当redis重启时会将记录下来的aof文件执行一遍即完成数据恢复。

Rdb持久化Fork解释

Redis官方解释:

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。
整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。(因为可能出现redis宕机的情况)

AOF持久化rewrite解释

rewrite:

AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof

重写原理:

AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。
重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。

RDB VS AOF

RDB优缺点

优点:

  • 适合大规模的数据恢复
  • fork子进程来完成持久化工作,主进程继续处理命令,主进程是不进行任何IO操作的,这就确保了极高的性能。
  • 相同数据情况下,RDB数据恢复比AOF数据恢复更快。

缺点:

  • fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
  • 在一定间隔时间做一次备份,所以redis意外down掉的话,就会丢失最后一次快照后的所有修改.

AOF优缺点

优点:

  • 数据安全性好,默认策略下每秒异步记录操作,最多只会丢失1秒的数据
  • 通过追加命令模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。

缺点:

  • AOF数据文件比RDB文件大。(rdb存储的是数据,aof存储的是命令)
  • 在相同数据情况下,AOF数据恢复比RDB数据恢复慢(rab只需读取文件加载到内存,aof需要加载文件,并执行)

如何选择合适的持久化方式

  • 官方推荐两个都启用。
  • 如果redis只是用来做缓存服务器,比如数据库查询数据后缓存,那可以不考虑持久化,因为缓存服务失效后还能再从数据库获取恢复。
  • 如果应用场景能容忍数分钟以内的数据丢失,推荐使用RDB持久化。
  • 如果应用场景需要很高的数据保障性,推荐同时使用两种持久化方式。

Redis主从复制

主从复制是什么

主从复制,指主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。
数据的复制是单向的,只能由主机到从机。

注意:Redis主从复制是异步的。

Redis主从复制可以做什么

  1. 读写分离:master写,slave读,提高服务器的读写负载能力
  2. 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
  3. 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
  4. 高可用基石:基于主从复制,构建哨兵模式域集群,实现Redis的高可用方案

Redis主从复制原理

Redis主从复制主要分为三个阶段:建立连接阶段,数据同步阶段,命令传播阶段。

建立连接阶段:主要是在主从节点之间建立连接,主机和从机互相保存配置数据(ip和port),为数据同步做好准备。
数据同步阶段:当建立主从连接后,根据情况进行全量复制或部分复制进行数据同步,将从机状态更新到主机最新状态。
命令传播阶段:数据同步后,进入命令传播阶段,这个阶段中主节点将自己执行的写命令发送给从机,从机接受命令并执行,从而保证主从节点数据的一致性,还保留了心跳机制。

主从复制相关名词

psyn:从节点向主节点发送的命令,表现要进行全量复制或部分复制进行同步数据。

runid:用于识别身份,每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),由40个随机的十六进制字符组成。

复制积压缓存区:又名复制缓冲区,是由主节点维护的。用于备份主节点最近发送给从节点的数据。由偏移量+字节值组成。

offset:复制积压缓存区的偏移量。主节点和从节点分别维护一个复制偏移量,分别表示当前主从复制数据传输到哪了。

全量复制流程

  1. 从节点第一次连接主节点;或从节点发送部分复制请的请求,主节点判断无法进行部分复制;从节点发送psync命令进行全量同步
  2. master接受到全量复制命令后,执行bgsave,后台生成RDB快照文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令
  3. master RDB快照文件生成后,将快照文件发送给从节点;从节点首先清除自己的旧数据,然后载入接受的快照文件,将状态更新到主节点执行bgsave时的状态
  4. 主节点将前述复制缓冲区中的所有写命令发送给从节点,从节点执行这些写命令,将状态更新到主节点的最新状态。

部分复制流程

基于:主节点维护了一个复制缓冲区和节点id(runid),主从节点都维护了一个复制偏移量(offset)。

  1. 从节点通过心跳replconf ack offset检测从节点状态没有更新到主节点的最新状态,触发部分复制同步数据
  2. 从节点发送命令psync2 runid offset请求部分复制
  3. 主节点判断runid是否匹配,判断offset是否存在复制缓冲区中,如果runid或offset有一个不满足,则执行全量复制
  4. 主节点判断runid或offset校验通过,,返回+CONTINUE offset,进行部分复制,通过stocket发送复制缓冲区[从节点中offset]到[主节点offset]的数据到从节点。
  5. 从节点接收到+CONTINUE,保存master的offset,并执行命令,完成数据同步,将状态更新到主节点的最新状态。

psync命令执行详解

psync命令执行详解

  1. 首先,从节点根据当前状态,决定如何调用psync命令
    • 如果从节点之前未执行过slaveof,或最近执行了slaveof no one,则从节点发送命令为psync ? -1,向主节点请求全量复制;
    • 如果从节点之前执行了slaveof,则发送命令为psync <runid> <offset>,其中runid为上次复制的主节点的runid,offset为上次复制截止时从节点保存的复制偏移量。
  2. 主节点根据收到的psync命令,及当前服务器状态,决定执行全量复制还是部分复制
    • 如果主节点版本低于Redis2.8,则返回-ERR回复,此时从节点重新发送sync命令执行全量复制;
    • 如果主节点版本够新,且runid与从节点发送的runid相同,且从节点发送的offset之后的数据在复制积压缓冲区中都存在,则回复+CONTINUE,表示将进行部分复制,从节点等待主节点发送其缺少的数据即可;
    • 如果主节点版本够新,但是runid与从节点发送的runid不同,或从节点发送的offset之后的数据已不在复制积压缓冲区中(在队列中被挤出了),则回复+FULLRESYNC ,表示要进行全量复制,其中runid表示主节点当前的runid,offset表示主节点当前的offset,从节点保存这两个值,以备使用。

Redis哨兵模式

什么是哨兵

Redis Sentinel,即Redis哨兵,在Redis 2.8版本开始引入。
哨兵(sentinel) 是一个分布式系统用于对主从结构中的每台服务器进行监控,当master出现故障时通过投票机制选择新的master,并将所有slave连接到新的master中。

哨兵的作用

哨兵的核心功能是主节点的自动故障转移,下面是Redis官方文档对于哨兵功能的描述:

  • 监控(Monitoring):哨兵会不断的检查主节点和从节点是否运作正常。
  • 通知(Notification):当被监控的服务器出现问题时,向其他(哨兵间,客户端)发送通知。
  • 配置提供者(Configuration provider):客户端可以通过连接哨兵来获得当前Redis服务的主从节点地址。
  • 自动故障转移(Automatic failover):当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。

哨兵的优缺点

优点:对于主从复制,哨兵提高了redis可用性。
缺点:对于从节点,哨兵无法对进行故障转移,只能下线;无法解决写负载均衡,以及存储能力受到单机限制。

哨兵工作原理

哨兵工作原理概述:基于心跳机制,判断master节点是否客观下线,如果客观下线,则选举领导者sentinel去进行故障转移。

  • 判断master节点是否客观下线:节点先判断主观下线,大多节点判定为下线,则为客观下线。
  • 选举领导者:在哨兵节点中,选举出一个领导者,提供投票机制(基于raft算法),获取大多数票的节点成为领导者
  • 进行故障转移
    • 选择新的主节点;原则是:从节点中筛选健康的节点-优先级最高的节点-offset最大的节点-runid最小的节点
    • 更新主从状态;从节点执行slaveof no one命令变更成主节点,让其他从节点关联当前主节点
    • 老主节点变从节点;将下线的主节点设置成新主节点的从节点

主观下线与客观下线

客观下线:在心跳检测的定时任务中,如果其他节点超过一定时间没有回复,哨兵节点就会将其进行主观下线。顾名思义,主观下线的意思是一个哨兵节点“主观地”判断下线;与主观下线相对应的是客观下线。

客观下线:哨兵节点在对主节点进行主观下线后,会通过sentinel is-master-down-by-addr命令询问其他哨兵节点该主节点的状态;如果判断主节点下线的哨兵数量达到一定数值,则对该主节点进行客观下线。(投票是大多数)

为什么哨兵至少要3个节点

原因

需要选举领导者
需要进行客观下线
原理:投票机制,需要得到大多数的票,才能做处理。

详解

相关概念:

  • quorum:确认odown的最少的哨兵数量
  • majority:授权进行主从切换的最少的哨兵数量(大多数)
  • 每次一个哨兵要做主备切换,首先需要quorum数量的哨兵认为odown,然后选举出一个哨兵来做切换,这个哨兵还得得到majority哨兵的授权,才能正式执行切换。

哨兵必须部署2个以上节点。如果哨兵集群仅仅部署了个2个哨兵实例,那么它的majority就是2,如果其中一个哨兵宕机了,就无法满足majority>=2这个条件,那么在master发生故障的时候也就无法进行主从切换。

注意:集群架构下故障转移也是一样的道理,集群必须也是2个以上节点。

数据丢失

异步复制导致的数据丢失

因为 master 和 slave 数据复制是异步的,所以可能有部分数据还没复制到slave,master 就宕机了。而此时新的master节点被选举了出来,旧的master节点的未同步的数据就丢失了。

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

异步复制导致的数据丢失

脑裂导致的数据丢失

脑裂,也就是说,某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着
此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master,这个时候,集群里就会有两个master,也就是所谓的脑裂。

此时虽然某个slave被切换成了master,但是可能client还没来得及切换到新的master,还继续写向旧master的数据可能也丢失了,因此旧master再次恢复的时候,会被作为一个slave挂到新的master上去,自己的数据会清空,重新从新的master复制数据。

脑裂导致的数据丢失

解决方案

1
2
min-slaves-to-write 1 # 要求至少有 1 个 slave。
min-slaves-max-lag 10 # 数据复制和同步延迟不能超过 10s。

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

减少异步复制数据丢失问题

有了min-slaves-max-lag这个配置,就可以确保说,一旦slave复制数据和ack延时太长,就认为可能master宕机后损失的数据太多了,那么就拒绝写请求,这样可以把master宕机时由于部分数据未同步到slave导致的数据丢失降低的可控范围内。

减少脑裂的数据丢失

如果一个master出现了脑裂,跟其他slave丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求,这样脑裂后的旧master就不会接受client的新数据,也就避免了数据丢失,上面的配置就确保了,如果跟任何一个slave丢了连接,在10秒后发现没有slave给自己ack,那么就拒绝新的写请求
因此在脑裂场景下,最多就丢失10秒的数据。

Redis集群模式

Redis场景的集群方案

  1. 官方方案redis-cluster搭建集群(推荐,redis3.0推出的集群方案)
  2. 客户端分片技术(不推荐),扩容/缩容时,必须手动调整分片程序,出现故障不能自动转移
  3. 使用一些代理工具(分布式中间件)

Redis-Cluster 集群原理

redis-cluster原理主要分为:数据分区,节点通讯机制。

数据分区

redis-cluster数据分区使用的是虚拟节点的一致性哈希分区。

带虚拟节点的一致性哈希分区
该方案在一致性哈希分区的基础上,引入了虚拟节点的概念。Redis集群使用的便是该方案,其中的虚拟节点称为槽(slot)。槽是介于数据和实际节点之间的虚拟概念;每个实际节点包含一定数量的槽,每个槽包含哈希值在一定范围内的数据。引入槽以后,数据的映射关系由数据hash->实际节点,变成了数据hash->槽->实际节点。

Redis Cluster特点如下:

  • redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node<>slot<>value。(节点分配槽,数据放在槽中)
  • Redis集群预分好16384个槽,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。
  • 根据槽与节点的映射关系,计算数据属于哪个节点。

好处:在虚拟节点的一致性哈希分区中虚拟节点解耦了数据和实际节点之间的关系,并且增减节点对系统的影响很小,并在增减节点后可以保证数据在剩余节点的分布较为均衡。

通信机制

两个端口

首先需要知道redis集群每个节点,都会提供两个端口,分别是普通端口和集群端口。

  • 普通端口:部署时指定的端口,只要为客户端提供服务。
  • 集群端口:集群端口是普通端口+10000(10000是固定值,无法改变),主要用于节点之间的通信,用于交换集群节点中的信息等等。

Gossip协议

节点间通信,按照通信协议可以分为几种类型:单对单、广播、Gossip协议等。
重点是广播和Gossip的对比。

广播:是指向集群内所有节点发送消息;优点是集群的收敛速度快(集群收敛是指集群内所有节点获得的集群信息是一致的),缺点是每条消息都要发送给所有节点,CPU、带宽等消耗较大。

Gossip协议的特点是:在节点数量有限的网络中,每个节点都“随机”的与部分节点通信(并不是真正的随机,而是根据特定的规则选择通信的节点),经过一番杂乱无章的通信,每个节点的状态很快会达到一致。Gossip协议的优点有负载(比广播)低、去中心化、容错性高(因为通信有冗余)等;缺点主要是集群的收敛速度慢。

消息类型

集群中的节点采用固定频率(每秒10次)的定时任务进行通信相关的工作:判断是否需要发送消息及消息类型、确定接收节点、发送消息等。如果集群状态发生了变化,如增减节点、槽状态变更,通过节点间的通信,所有节点会很快得知整个集群的状态,使集群收敛。

节点间发送的消息主要分为5种:meet消息、ping消息、pong消息、fail消息、publish消息。
不同的消息类型,通信协议、发送的频率和时机、接收节点的选择等是不同的。

  • MEET消息:在节点握手阶段,当节点收到客户端的CLUSTER MEET命令时,会向新加入的节点发送MEET消息,请求新节点加入到当前集群;
    • 新节点收到MEET消息后会回复一个PONG消息。
  • PING消息:集群里每个节点每秒钟会选择部分节点发送PING消息,接收者收到消息后会回复一个PONG消息。
    • PING消息的内容是自身节点和部分其他节点的状态信息;作用是彼此交换信息,以及检测节点是否在线。
    • PING消息使用Gossip协议发送,接收节点的选择兼顾了收敛速度和带宽成本
      • 具体规则如下:(1)随机找5个节点,在其中选择最久没有通信的1个节点(2)扫描节点列表,选择最近一次收到PONG消息时间大于cluster_node_timeout/2的所有节点,防止这些节点长时间未更新。
  • PONG消息:PONG消息封装了自身状态数据。可以分为两种
    • 第一种是在接到MEET/PING消息后回复的PONG消息;
    • 第二种是指节点向集群广播PONG消息,这样其他节点可以获知该节点的最新信息,例如故障恢复后新的主节点会广播PONG消息。
  • FAIL消息:主节点异常消息
    • 当一个主节点判断另一个主节点进入FAIL状态时会向集群广播这一FAIL消息;接收节点会将这一FAIL消息保存起来,便于后续的判断。
  • PUBLISH消息::节点收到PUBLISH命令后,会先执行该命令,然后向集群广播这一消息,接收节点也会执行该PUBLISH命令。

Redis集群的故障转移

Redis集群的故障转移与哨兵机制原理很相似,只是实现细节不同。

Redis集群的故障转移原理:基于集群的通信机制,节点会定时发送PING消息检测其他节点,判断节点是否为客观下线,客观下线后选择从节点进行故障转移。

  • 判断客观下线:集群中大多数主节点判断当前主节点已下线,则为客观下线。
  • 选举主节点:从节点选举出新的主节点,需要由主节点投票选出哪个从节点成为新的主节点,从节点选举胜出需要的票数为N/2+1。
  • 节点数量:在故障转移阶段,需要由主节点投票选出哪个从节点成为新的主节点;从节点选举胜出需要的票数为N/2+1;其中N为主
  • 故障转移
    • 更新主从状态,从节点执行slaveof no one命令变更成主节点,让其他从节点关联当前主节点
    • 新的主节点会把下线主节点的所有指派槽指派给自己
    • 新的主节点广播一条PONG消息,通知其他主节点,自己变成了主节点
    • 新主节点开始接受与自己负责槽位有关的命令请求。

Redis 集群如何选择数据库

Redis集群无法选择数据库,只有一个库,就是0库。

数据过期策略

Redis的数据过期策略

Redis一共有是那种过期策略。

  • 定时删除:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除。
  • 定期删除:所谓定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。
  • 惰性删除:key过期的时候不删除,每次从redis获取key的时候去检查是否过期,若过期,则删除,返回null。

Redis默认采用的过期策略是?

Redis服务器默认过期策略是:惰性删除+定期删除。

Redis key过期后的数据真正删除了吗?(面试问过)

不能确定是否被删除,因为redis默认采用的过期策略是惰性删除+定期删除。
因为惰性删除,key过期后,只是被标识被删除了,但还在内存中,只有在被访问时才会被删除。
因为定期删除,会在规定时间内随机抽取设置了过期时间的key,检查是否过期,如果过期就删除。

Redis的默认采用的过期策略会不会有什么问题?(面试问过)

redis服务器默认过期内置策略是:惰性删除+定期删除。
如果定期删除漏掉了很多过期key,也没及时去查询,即也没走惰性删除,此时就会有大量过期key堆积在内存中,导致redis内存快耗尽了,这应该怎么办?会走内存淘汰机制。

Redis的内存淘汰机制

如果redis的内存占用过多的时候,此时会进行内存淘汰,有如下一些策略:
redis 10个key,现在已经满了,redis需要删除掉5个key
1个key,最近1分钟被查询了100次
1个key,最近10分钟被查询了50次
1个key,最近1个小时被查询了1次

1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了

2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)

3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的key给干掉啊

4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key(这个一般不太合适)

5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key

6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除

缓存穿透 & 缓存雪崩 & 缓存击穿

Redis正常业务使用流程

数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存中,直接向外返回空。

缓存穿透

缓存穿透:

是指查询一个数据库不存在的数据。这将导致不存在的数据每次请求都要到数据库去查询,假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。

解决方案:

  • redis缓存空值(推荐)。如果一个查询返回的数据为空(不管是否数据不存在,还是系统故障),我们就把这个空结果进行缓存。但它的过期时间设置很短即可。
  • 判断key的数据格式。网关判断客户端传入对应的key规则,如果不符合数据库的查询规则直接返回null值。

缓存雪崩

缓存雪崩:

是指在某一个时间段,缓存集中过期失效。
举例:马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。

解决方案:

  • 均摊分配redis key的失效时间(推荐),不同的key,设置不同的过期时间,让缓存失效的时间点不要过于集中。
  • 使用二级缓存,一级缓存为原始缓存,二级缓存为拷贝缓存,一级缓存失效时,可以访问二级缓存。一级缓存失效时间设置为短期,二级缓存设置为长期。

缓存击穿

缓存击穿:

热点数据突然过期。是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

解决方案:

  • 设置热点数据永不过期。(推荐)
  • 使用互斥锁(不推荐),也就是同一时间只允许一个线程查询数据和写缓存,其他线程等待。

分布式锁

Redis实现分布式锁的保障

首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  1. 互斥性:在任意一个时刻,只有一个客户端能持有锁。
  2. 无死锁:即便持有锁的客户端崩溃或网络被分裂而没有主动解锁,锁仍然可以被获取。
  3. 容错性:只要大部分Redis节点都活着,客户端就可以获取和释放锁.
  4. 解铃还须系铃人,加锁和解锁必须是同一个客户端。客户端自己不能把别人加的锁给解了。

单实例实现分布式锁

获取锁:
使用set获取锁。
通过nx设置锁不存在,才创建锁,保证互斥性。
通过ex设置锁的过期时间,超过该时间则自动释放锁,保证无死锁。
设置value值为随机生成的UUID,在释放锁时进行判断,需要上锁的客户端才能删除这把锁,保证解铃还须系铃人

设置获取锁的超时时间:
获取锁的时候还设置一个获取锁的超时时间,若超过这个时间则放弃获取锁,不要让程序一直循环的获取锁。

释放锁:
使用delte释放锁。
在释放锁的过程中,通过UUID判断是不是该客户端上的锁,如果是才释放锁,保证解铃还须系铃人

单实例实现分布式锁存在的问题

在这种场景(主从结构)中存在明显的竞态:

客户端A从master获取到锁
在master将锁同步到slave之前,master宕掉了。
slave节点被晋级为master节点
客户端B取得了同一个资源被客户端A已经获取到的另外一个锁。(互斥性失效)!
有时候程序就是这么巧,比如说正好一个节点挂掉的时候,多个客户端同时取到了锁。如果你可以接受这种小概率错误,那用这个基于复制的方案就完全没有问题。否则的话,我们建议你使用Redlock。

Redlock(多实例实现分布式锁)

为了取到锁,客户端应该执行以下操作:

  1. 获取当前Unix时间,以毫秒为单位。
  2. 依次尝试从N个实例,使用相同的key和随机值获取锁。在步骤2,当向Redis设置锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试另外一个Redis实例。
  3. 客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
  4. 如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
  5. 如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功)。

redlock算法示意图

注意:根据上面实现原理的分析,有些同学应该是对Redlock算法实现有一点点误解,假设我们用5个节点实现Redlock算法的分布式锁。那么要么是5个redis单实例,要么是5个sentinel集群,要么是5个cluster集群。而不是一个有5个主节点的cluster集群,然后向每个节点通过EVAL命令执行LUA脚本尝试获取分布式锁,如上图所示。

其他

Redis事物机制

Redis事物与关系型数据库的事物有很大的区别。
Redis单命令是原子性的,但Redis事物并不是原子性。
Redis事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

Redis 支持的 Java 客户端都有哪些?官方推荐用哪个?

Jedis、Redisson、lettuce等等,官方推荐使用Redisson。

Jedis 与 Redisson 对比有什么优缺点?

Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;
Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。