聊聊redis一些常用的知识点

Redis面试中常见的面试题

    • redis的基本数据结构
    • Redis缓存雪崩
    • Redia缓存穿透
    • Redis缓存击穿
    • 缓存不一致问题
    • 为什么是删除,而不是更新缓存?
    • 布隆过滤器
      • Bloom Filter的缺点
    • Redis持久化
      • AOF 重写
      • ReWrite触发机制是什么?
    • RDB 和 AOF 各自有什么优缺点?
    • Redis 4.0 混合持久化
    • Redis主从复制
      • 主从复制主要的作用
      • 主从复制一些小细节
    • 哨兵模式
      • 判断主服务器是否下线了
    • 数据丢失情况
    • Redis 集群
    • Redis设置过期时间
      • 过期淘汰策略

redis的基本数据结构

Redis有哪些数据结构呀?
应该有很多面试者会像我一样回答5种,String、Hash、List、Set、SortedSet,但这是人人到知道的,即使回答上来也并不会对面试官留下什么印象,所以我整理了这个问题该怎样回答。
答:有5种基本数据类型,分别是String、Hash、List、Set、SortedSet。但是其每一种数据类型中有不同的编码格式。
String类型编码格式:int (整数值),embstr (字符串值,字符串长度小于32字节),raw (字符串值,字符串长度大于32字节)。String数据类型的使用场景:博客系统的点赞功能

Hash类型编码格式:ziplisthashtable
ziplist的使用场景是:当数据量比较小的时候,底层采用ziplist存储。
例如:hset user:001 username song age 20 length 170 (此时因为数据量较少并且单个元素比较小,底层采用ziplist实现,当hgetAll user时,输出的顺序就等于我们输入的顺序)。但是将user weigth 10000000000000 这个属性和值插入时,由于单个元素较大,数据结构升级为hashtable,输出的顺序将会是无序的。元素的数量超过512/元素的大小超过64字节,会改变数据结构。
hashtable(字典)的使用场景:字典的内部结构包含两个hashtable,ht[0]:用于存放真实的数据,ht[1]:用于扩容,扩容为原来的1倍。
重点:渐进式rehash
大字典的扩容是比较耗时的,需要重新申请新数组,然后将旧字典所有链表中的元素重新挂到新的数组下面,这是一个O(n)级别的操作,作为单线程的Rdis很难承受这样耗时的过程,所以Redis使用渐进式rehash搬移。

  1. 在字典中维持一个索引计数器rehashidx,并将设置为0,表示rehash开始
  2. 在rehash期间每次对字典进行增删改查时,会将ht[0]中rehashidx索引上的值rehash到ht[1],操作完成后rehashidx+1
  3. 字典操作不断执行,在最终某个时间所有键值完成rehash,这时将rehashidx设置为-1,表示rehash完成
    Hash数据类型的使用场景:电商系统的购物车功能
    剩下三种我也就不一一介绍了,因为我也不太了解。

除了基本数据类型还可以说一些中级数据类型:Bitmaps、HyperLogLog、GEO
Bitmaps:就是布隆过滤器中的二进制数组
HyperLogLog:用于进行基数统计,不是集合,不保存数据,只记录数量而不是具体数据,核心是基数估算算法,最终数值存在一定误差,基数估计的结果是一个带有0.81%标准错误的近似值。适用场景:UV(独立访客,某网站每天的访客次数)。
GEO:测量两点之间的距离。

Redis缓存雪崩

举个简单的例子:如果所有首页的Key失效时间都是12小时,中午12点刷新的,我零点有个秒杀活动大量用户涌入,假设当时每秒 6000 个请求,本来缓存在可以扛住每秒 5000 个请求,但是缓存当时所有的Key都失效了。此时 1 秒 6000 个请求全部落数据库,数据库必然扛不住,它会报一下警,真实情况可能DBA都没反应过来就直接挂了。
**解决方式:**处理缓存雪崩简单,在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效,我相信,Redis这点流量还是顶得住的。或者设置热点数据永远不过期,有更新操作就更新缓存就好了。

Redia缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库。你如果不对参数做校验,数据库id都是大于0的,我一直用小于0的参数去请求你,每次都能绕开Redis直接打到数据库,数据库也查不到,每次都这样,并发高点就容易崩掉了。
**解决方式:**缓存穿透我会在接口层增加校验,比如用户鉴权校验,参数做校验,不合法的参数直接代码Return,比如:id 做基础校验,id <=0的直接拦截等。
设置缓存无效key:如果缓存和数据库都找不到这个key的数据,就写一个key到redis中并设置过期时间。这种方法可以解决key的变换不频繁情况,如果黑客每次攻击使用不同的key,就会导致redis中缓存大量无效的key。
布隆过滤器:通过随机映射函数,将数据存入二进制数组。假如有两个随机映射函数,那么x就会被散落到二进制数组的两个位置上。当查询x是否存在时,就去查询这两个位置是否都为1.但是布隆过滤器存在误判的情况。

Redis缓存击穿

缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。
**解决方式:**设置热点数据永远不过期。或者加上互斥锁就能搞定了。

缓存不一致问题

先删缓存,再更新数据库
延时双删:为了避免更新数据库的时候,其他线程从缓存中读取不到数据,就在更新完数据库之后,再sleep一段时间,然后再次删除缓存
流程如下:

  1. 线程1删除缓存,然后去更新数据库
  2. 线程2来读缓存,发现缓存已经被删除,所以直接从数据库中读取,这时候由于线程1还没有更新完成,所以读到的是旧值,然后把旧值写入缓存
  3. 线程1,根据估算的时间,sleep,由于sleep的时间大于线程2读数据+写缓存的时间,所以缓存被再次删除
  4. 如果还有其他线程来读取缓存的话,就会再次从数据库中读取到最新值
    聊聊redis一些常用的知识点_第1张图片

先更新数据库,再删除缓存
这个就更明显的问题了,更新数据库成功,如果删除缓存失败或者还没有来得及删除,那么,其他线程从缓存中读取到的就是旧值,还是会发生不一致。
消息队列:先更新数据库,成功后往消息队列发消息,消费到消息后再删除缓存,借助消息队列的重试机制来实现。
聊聊redis一些常用的知识点_第2张图片

为什么是删除,而不是更新缓存?

我们以先更新数据库,再删除缓存来举例。
如果是更新的话,那就是先更新数据库,再更新缓存。
举个例子:如果数据库1小时内更新了1000次,那么缓存也要更新1000次,但是这个缓存可能在1小时内只被读取了1次,那么这1000次的更新有必要吗?
反过来,如果是删除的话,就算数据库更新了1000次,那么也只是做了1次缓存删除,只有当缓存真正被读取的时候才去数据库加载。

布隆过滤器

**基本思想:**布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。
聊聊redis一些常用的知识点_第3张图片

Bloom Filter的缺点

bloom filter之所以能做到在时间和空间上的效率比较高,是因为牺牲了判断的准确率、删除的便利性

  1. 存在误判,可能要查到的元素并没有在容器中,但是hash之后得到的k个位置上值都是1。如果bloom filter中存储的是黑名单,那么可以通过建立一个白名单来存储可能会误判的元素。
  2. 删除困难。一个放入容器的元素映射到bit数组的k个位置上是1,删除的时候不能简单的直接置为0,可能会影响其他元素的判断。

Redis持久化

Redis持久化的问题一直是面试中的高频题,可是我看过很多视频,视频讲的都很好,但是我始终记不住该怎么具体完整的去回答这个问题,或者说怎样回答让面试官觉得我真的对持久化有一个不错的理解,至今也没有找到,只能写一些过往的总结。
RDB持久化
当触发了rdb快照机制,Redis会单独创建(fork)一个子进程,简单理解也就是基于当前进程 复制了一个主进程,主进程和子进程会共享内存里面的代码块和数据段。子进程会将共享内存中的数据写入到临时rdb文件中,当完成持久化后(将共享内存中的数据全部写入临时文件后),将临时文件替换原来的rdb文件。
聊聊redis一些常用的知识点_第4张图片
聊聊redis一些常用的知识点_第5张图片
触发RDB持久化机制

  1. 配置文件中默认的快照配置。
    在这里插入图片描述
  2. 手动创建命令:
    SAVE会阻塞Redis服务器进程,服务器不能接收任何请求,直到RDB文件创建完毕为止。
    BGSAVE创建出一个子进程,由子进程来负责创建RDB文件,服务器进程可以继续接收请求。

AOF持久化
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件。每次执行修改内存中数据集的写操作时,都会记录该操作。当 Redis 收到客户端修改指令后,会先进行参数校验、逻辑处理,如果没问题,就 立即 将该指令文本 存储 到 AOF 日志中,也就是说,先执行指令再将日志存盘。
如果aof文件损坏可以使用redis-check-aof --fix appendonly.aof来修复aof文件
触发aof持久化:
always :每个写命令都同步,选项会严重减低服务器的性能;
everysec :每秒同步一次,选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;
no :让操作系统来决定何时同步,选项并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量。

AOF 重写

Redis 在长期运行的过程中,AOF 的日志会越变越长。如果实例宕机重启,重放整个 AOF 日志会非常耗时,导致长时间 Redis 无法对外提供服务。所以需要对 AOF 日志 “瘦身”。
Redis 提供了 bgrewriteaof 指令用于对 AOF 日志进行瘦身。其 原理 就是 开辟一个子进程 对内存进行 遍历 ,转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件 中。序列化完毕后再将操作期间发生的 增量 AOF 日志 追加到这个新的 AOF 日志文件中,追加完毕后就立即替代旧的 AOF 日志文件了,瘦身工作就完成了。
聊聊redis一些常用的知识点_第6张图片

ReWrite触发机制是什么?

Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发

RDB 和 AOF 各自有什么优缺点?

RDB 优点

  1. 性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以使 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 Redis 的高性能。
  2. 相对于数据集大时,比 AOF 的 启动效率 更高。
    RDB 缺点
    数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 Redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候;
    AOF 优点
  3. 数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。
  4. 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
  5. AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写)
    AOF 缺点
  6. AOF 文件比 RDB 文件大,且 恢复速度慢。
  7. 数据集大 的时候,比 rdb 启动效率低。

Redis 4.0 混合持久化

重启 Redis 时,我们很少使用 rdb 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。
Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是 自持久化开始到持久化结束 的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小:
聊聊redis一些常用的知识点_第7张图片
于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

Redis主从复制

复制过程
下图为Redis复制工作过程:

  1. slave向master发送sync命令。
  2. master开启子进程来将dataset写入rdb文件,同时将子进程完成之前接收到的写命令缓存起来。
  3. 子进程写完,父进程得知,开始将RDB文件发送给slave。
  4. master发送完RDB文件,将缓存的命令也发给slave。
  5. master增量的把写命令发给slave。
    聊聊redis一些常用的知识点_第8张图片

主从复制主要的作用

数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
故障恢复: 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复 (实际上是一种服务的冗余)。
负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务 (即写 Redis 数据时应用连接主节点,读 Redis 数据时应用连接从节点),分担服务器负载。尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量。
高可用基石: 除了上述作用以外,主从复制还是哨兵和集群能够实施的 基础,因此说主从复制是 Redis 高可用的基础。

主从复制一些小细节

1.配从不配主
查看redis的状态 info replication
聊聊redis一些常用的知识点_第9张图片
在6380客户端执行语句:Slaveof 127.0.0.1 6379 将6379设置为6380的从机
小知识点:
1)如果主机死了(shutdown),那么从机的状态依旧为从机,只是连接状态由up变为down。如果主机又活了,那么从机将继续当他的从机。
2)如果从机死了,那么另一个从机不受影响,死掉的从机重新启动,状态会变回主机。
(只要和master主机断开就必须重新连接,也可以将从机语句写入配置文件)手动配置完从机后仍可以正常使用。
2.薪火相传
6379为主机 6380为6379从机 6381 为6380从机 //6380的状态仍然为从机
3.反客为主
当从机的主机shutdown后,Slaveof no one 命令可以让从机变为主机

哨兵模式

在复制的基础上,哨兵实现了 自动化的故障恢复 功能,下方是官方对于哨兵功能的描述:
监控: 哨兵会不断地检查主节点和从节点是否运作正常。
自动故障转移: 当 主节点 不能正常工作时,哨兵会开始 自动故障转移操作,它会将失效主节点的其中一个 从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
配置提供者: 客户端在初始化时,通过连接哨兵来获得当前 Redis 服务的主节点地址。
通知: 哨兵可以将故障转移的结果发送给客户端。

判断主服务器是否下线了

主观下线
Sentinel会以每秒一次的频率向与它创建命令连接的实例(包括主从服务器和其他的Sentinel)发送PING命令,通过PING命令返回的信息判断实例是否在线
如果一个主服务器在down-after-milliseconds毫秒内连续向Sentinel发送无效回复,那么当前Sentinel就会主观认为该主服务器已经下线了。
客观下线
当Sentinel将一个主服务器判断为主观下线以后,为了确认该主服务器是否真的下线,它会向同样监视该主服务器的Sentinel询问,看它们是否也认为该主服务器是否下线。
如果足够多的Sentinel认为该主服务器是下线的(通常为哨兵的半数以上),那么就判定该主服务为客观下线,并对主服务器执行故障转移操作。

数据丢失情况

丢失数据有两种情况:
异步复制导致的数据丢失:有部分数据还没复制到从服务器,主服务器就宕机了,此时这些部分数据就丢失了
脑裂导致的数据丢失
有时候主服务器脱离了正常网络,跟其他从服务器不能连接。此时哨兵可能就会认为主服务器下线了(然后开启选举,将某个从服务器切换成了主服务器),但是实际上主服务器还运行着。这个时候,集群里就会有两个服务器(也就是所谓的脑裂)。
虽然某个从服务器被切换成了主服务器,但是可能客户端还没来得及切换到新的主服务器,客户端还继续写向旧主服务器写数据。旧的服务器重新连接时,会作为从服务器复制新的主服务器(这意味着旧数据丢失)。

Redis 集群

聊聊redis一些常用的知识点_第10张图片

Redis设置过期时间

当我们set key value时,可以为这个key增加一个过期时间expire time,在这个过期时间内我们可以判定这个key是存活的,当过了这个时间redis是怎样对这个key进行删除的?
定期删除+惰性删除
定期删除: redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查是否过期,如果过期就删除。注意这里是随机抽取的,为什么要随机抽取呢?如果redis存了几十万个key,每隔100ms就遍历所有设置存活时间的key,这会给cpu带来很大的负载
惰性删除: 因为定期删除是随机抽取,因此会会有一些过期了的key到了时间没有被删除,所以就有了惰性删除。假设这个过期的key还存在redis中除非你的系统去查看一下这个key,当他被发现了才会被redis删除
但是因为数据量过大,你没办法去查看到所有过期的key,导致过期的key堆积在redis中,怎么解决这个问题呢?

过期淘汰策略

Redis提供的6种淘汰策略:
1.从已经设置过期时间的数据集中挑选最近最少使用的数据淘汰
2.从已经设置过期时间的数据集中挑选将要过期的数据淘汰
3.从已经设置过期时间的数据集中任意选择数据淘汰
4.从所有数据集中挑选最近最少使用的数据淘汰
5.从所有数据集中任意选择数据淘汰
6.禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错

你可能感兴趣的:(redis)