Redis八股速通

八股记忆笔记

Redis

1.字符串

  1. 为什么不采用c语言作为字符串具体实现

    1. O(n):因为c语言的字符串结尾是以\0结尾的

    2. 没有很好的扩容机制

    3. 特殊字符无法处理:还是因为\0

  2. 使用java语言的优点(虽然实际还是以c语言为基础,加了别的标识属性的结构体)

    1. 字符串长度获取时间复杂度从 O(n)->O(1)

    2. 减少字符串扩容引起的数据搬运次数

    3. 储存更加复杂的二进制数据

2.链表

 

  1. 底层实现是双向链表

  2. 其中有三种函数,分别是复制函数, 释放函数, 对比函数

  3. 常见API函数:

    1. lpush:向链表左边增加元素

    2. rpush:向链表右边增加元素

    3. lpop:弹出左侧第一个元素

    4. rpop:弹出右侧第一个元素

    5. llen:获得链表长度

    6. lrange:按索引范围获取值

 

3.哈希表

  1. 键值对,一对一的实现,一个key对应一个value

    1. 通过键key,在O(1)时间复杂度下获得相对应的值

    2. 因为c语言没有内置哈希表这个数据结构,所以就是Redis是自己实现的Hash表

  2. Redis采用拉链法

    1. 直接找到对应的key然后直接插到单链表的末端

  3. sizemask的作用是拿来防止error,做与运算,防止溢出

4.集合

  1. 二分查找

  2. 增加就是扩容,删除就是缩容

  3. insert是占用一段连续的内存

  4. 每次修改数据都要重新申请空间

 

5.有序集合

  1. 二分查找

  1. Zset应用场景 :

    1. 就是热搜话题,微博热搜之类的

    2. 选取TopK作为返回值,时间复杂度维O(nlogn)

    3. 如果这个map本身就是按照标题热度值组织的有序结构,就是复杂度为O(k)

    4. Zset中有一种实现方式就是跳表

 

 

6.持久化

  • Redis是内存型数据库

  • 持久化意思就是:

    • 把内存上的数据同步到硬盘中去,断电后不丢失

  • 缺点,数据易失性,断电后内存数据消失

  • 解决方法RDB(Redis Data Base),AOF(Append Only File)

 

RDB(Redis Data Base)

  • 通俗:就是把内存中所有数据直接存储到硬盘中!!!!

  • 就是把目前redis内存中的数据,生成一个快照(RDB文件),保存在硬盘中,就是相当于备份,然后文件是以.rdb为后缀的,就是如果发生事故的话,redis就可以通过这个文件,进行文件读取,并且将数据重新载入内存中

  • 上图中不一样的就是

    • 数据里有设置的数据的有效期

      • 一个是有过期时间的数据

      • 一个是没有过期时间的数据

 

  • 以下是各个基本类型在RDB中的结构

 

触发RDB条件

  • 手动触发

    • save:执行save指令,主线程执行rdbSave函数,服务器进程阻塞,就是不能处理别的请求了,直接就是卡住了,这个redis

    • bgsave(background save):本质就是这个命令和save差不多,区别就是这个命令会去fork一个子进程,去执行rdbSave函数,所以就是主线程还是可以执行新请求,不过就是bgsave在子进程上用,不会干扰主进程。

  • 自动触发

    • redis的配置文件写入save m n ,就是当m秒内发生n次此变化,就会自动执行bgsave

    • 意思就是相当于在后台一直跑这个bgsave这个函数到达一定条件就是m秒内发生了n次变化就到达条件,直接执行bgsave。

 

 

AOF(Append Only File)

  • 记录之后所有对Redis数据进行修改的操作

    • 发生事故,redis可以通过AOF文件,将文件中的数据修改命令全部执行一遍,一次来恢复数据

 

  • 重写与恢复

    •  

 

问:

当AOF执行过程中会产生很多的无效命令以及过期命令,造成AOF文件过大,请问怎么样才能缓解这种情况???

:Redis提供了一个AOF文件重写功能,创建一个新的AOF文件,当有对同一条数据操作或者不同的数据操作而产生新的命令,redis的AOF会有一个文件重写策略,产生一个新的AOF文件,代替先前的AOF文件,但是俩个AOF文件保存的数据库数据相同的,只是新的不会有冗余的命令

注意:新的AOF文件是不会参考老的AOF文件的,而是由当前Redis的数据状态产生的。

 

触发AOF条件:

  • 手动触发:

    • bgrewriteaof

    • 执行这个命令,然后redis就会根据当前数据库的状态生成一个AOF文件,并且把老版的AOF文件替换掉,旧的会被删掉

  • 自动触发:

    • 配置文件中设置appendonly yes开启

      • appendonly yes策略

        • Always:就是同步写回,意思就是在每个命令执行完毕后直接将命令写入到磁盘文件中,就是AOF文件中(数据基本保证了可靠性,但是redis性能大大减低)

        • Everysec:就是每秒写回,在每个命令执行完成后,直接被写入到文件的内存的缓冲区,然后每过1s,redis就会把这个缓冲区的命令写到磁盘的AOF文件中(发生事故最多丢失1s内的数据,性能影响不大)

        • No:这个NO就是将操作命令全部写到Redis缓冲区,至于什么时候将缓存数据写到磁盘,这得看操作系统决定(出了问题,数据不知道有没有,但是性能影响最小)

      • Redis得appendonly yes默认策略是Everysec

      • 注意:其实No的效率跟Everysec差不多,建议用Everysec


 

 

 

 

7.缓存

 

1.缓存淘汰

因为一般数据比缓存大,就是比如数据有20个g,但是高速缓存只有2个g 这样,所以当在缓存中满了的情况下,redis会采用淘汰策略。


 

  • 先进先出FIFO算法

    • 就是队列那样,先进缓存的老元素被淘汰 ,然后最新元素进入。

  • 最近最少使用LRU

    • 实现思路:双向链表+哈希表形式储存

    • 通俗:就是按照数据中被使用数据时间排序,然后把末尾的数据(键以及对应的值)删掉,再把最新的加上去

    • 就是当某个key被访问到了,然后程序会通过哈希表迅速定位到这个节点(时间复杂度O(1)),并将改节点调整到链表最开始的位置。

  • 最不经常使用LFU

    • 按照访问次数淘汰

    • 就是俩个表:键值对映射表+次数到链表的映射表(都是哈希表),以及双向链表管理同次数的key

    • 淘汰时找到最小次数的链表的末尾节点。

    • 键值对映射表:

      • 存每个 Key 对应的 Value(实际数据)、访问次数(count)最近访问时间(lastTime)(处理 “次数相同” 时的淘汰顺序)。

    • 次数到链表的映射表:

      • 相同访问次数的 Key 放在同一个双向链表中,方便快速找到 “当前次数最小的所有 Key”,以及对 Key 的访问次数变化进行分组管理。

 

注意:!!!!!!!!

redis如果需要淘汰老数据了,默认采用的是最近最少使用LRU算法


 

2.过期删除

就是缓存中的数据超过了预设的有效期,然后系统自动将这些过期数据从缓存中移除的机制

关键概念

过期字典,就是redis会默认为每个数据库维护一个过期字典,存储了所有的过期时间的键。

Key:就是时间戳对应的键

Value:就是过期时间戳

 

有三种删除模式:

  • 定时删除主动删除

    • 核心:设置缓存的时候设置一个定时器,时间已到达就立马触发删除操作。

    • 实现:每个键值对附带一个时间戳,存入缓存时,就启动一个单独的线程或者定时器,到期后直接删除这个键。

    • 优点:过期数据立即删除,不i会使内存占用超标

    • 缺点:大量的定时器会消耗很多的CPU资源(特别时缓存量很大的时候),而且定时器的创建和管理成本也很高。

  • 惰性删除

    • 核心:程序取值的时候查看该数据是否过期,如果没过期,就直接返回,过期就删除该键,并返回null

    • 优点:服务器运算资源占用小

    • 缺点:造成某些数据长期占用内存,不被删除

  • 定期删除

    • 核心:redis默认每10秒执行一下随机抽取一定数量的键(默认20个),检查并删除过期的键,条件是如果本轮的过期键比例超过25%,就会继续抽取删除,直到比例低于25%或者达到最大的执行时间(25ms)

    • 实现:(通过redis.conf配置)

      • hz:每秒执行定期删除的次数(默认是10,范围是1-500)

      • active-expire-effort:删除力度(默认是1,范围是1-10)

    • !!!:每隔一段时间,跑主动删除,不跑主动删除时,就是执行惰性删除策略

注意

Redis底层是采用惰性删除定期删除结合的方式


 

3.缓存一致(Cache Aside)

    指的是**缓存**中的数据跟**数据库数据**保持**同步**的状态,但现实是缓存数据跟数据库数据不同步一致,具体表现为:当数据库更新时,缓存中的旧数据需要被及时清理或更新,**避免请求读取了脏数据**。
  • 核心思想:当缓存数据有更新值,采用删除缓存数据。

 

  • 更新策略(核心点)

    • 延迟双删

      • 实现:先删除缓存,再更新数据库,然后延迟一段时间,再删除缓存

      • 目的:解决数据库更新未完成,缓存被加载旧数据问题

      • 注意:延迟时间要大于数据库更新的时间

  • Read/Write Through 模式

    • 这个模式就是,应用把缓存作为主要的数据读取方式,即是避免缓存击穿。

 

  • Write Behid模式

    • 实现:将更新步骤放到消息队列中,异步执行。


 

4.缓存击穿

定义:大量请求查询某一个值,该值在缓存中无,在数据库中有。

场景:非常常见,因为有时候会设置数据过期时间,当数据过期就是找不到值了,所以就会发生缓存击穿

 

  • 场景问题

    • 热点商品抢购,如iphone16抢购,缓存存了商品的信息,小程序挤满了想要抢货的人,时间一到开抢,但是这个缓存的信息过期了 ,Redis给删了这个信息,于是大量的请求就没打到缓存上,直接请求到后端数据库上。

    • 主要问题:Redis的性能是普通关系型数据库的10-100倍,如果Redis能勉强处理的请求直接打到Mysql上,那么Mysql就处理不过来直接宕机了。

  • 解决方式

    • Mysql方面:减少击穿后的直接流量

      • 实现:直接加分布式锁(互斥锁),保证同时间只有一个线程查询数据库并更新缓存,其他线程等待缓存加载我弄成后再读取。

      • // 伪代码:使用Redis的setnx实现互斥锁
        String lockKey = "lock:hot_key";
        // 尝试加锁(设置过期时间防止死锁)
        if (redis.set(lockKey, "1", "NX", "EX", 10)) { 
            try {
                // 查询数据库,更新缓存
                String data = mysql.query("SELECT * FROM table WHERE id=热点Key");
                redis.set("hot_key", data, "EX", 3600); // 重新设置缓存
            } finally {
                redis.del(lockKey); // 释放锁
            }
        } else {
            // 未获取到锁,等待一段时间后重试
            Thread.sleep(100); 
            return redis.get("hot_key"); // 再次查询缓存(此时可能已被加载)
        }
    • Redis方面:

      • 1.设置热点数据永不过期

      • 热点数据后台启动一个异步线程,重新把数据回填缓存层


 

5.缓存穿透

定义:查询一个数据库和缓存都不存在的数据,如果刚删除,本来就没有,所以叫缓存穿透

  • 解决方法

    • 1.拦截非法查询请求

    • 2.缓存空对象

    • 3.布隆过滤器

  • 具体实现

    • 拦截非法查询请求:

      • 直接在控制器中配置,直接说明非法请求

    • 缓存空对象

      • 高访问的数据,在缓存中缓存一个空对象,这样也不会直接打到数据库上。

    • 布隆过滤器

      • 是缓存穿透得前置防线,通过极低的空间成本,拦截大量的无效请求。

        • 在请求到达缓存或数据库前,通过布隆过滤器快速判断 Key 是否 “可能存在”:

          • 若判断为 “不存在”(一定正确),直接返回请求(无需查缓存 / 数据库);

          • 若判断为 “可能存在”(可能误判),继续查缓存→查数据库(此时 Key 可能真实存在)


 

6.缓存雪崩

定义:一大批被缓存的数据,同时失效,此时对于这一批的数据请求就全打到数据库上,导致数据库宕机。缓存击穿是单点的数据,缓存雪崩是多条数据

  • Mysql:装互斥锁,减少并发量,让请求在合理范围内

  • Redis

    • 设置热点数据不过期

    • 分析失效时间,尽量让失效时间点分散

    • 缓存预热,在上线前,根据当天情况分析,将热数据直接加载到缓存系统

 


 

8.集群

以上8.集群之前的都是单机运行Redis,这节讲的是,在好几台机器上运行Redis是怎么样的。

 

1.主从复制

  • ①首先就是三次握手通信(采用TCP协议),主库和从库要想连接就得知道双方的IP和端口号,当主库和从库上线后就进行握手,然后信息验证。

  • ②从库发送PSYNC命令:即是同步命令,开启数据同步过程,发送主库的运行ID和复制进度的偏移量Offset。

  • ③握完手开始进入数据同步阶段,有俩种模式:

    • 一是全量复制(全量同步)

      • why:从库第一次连接这个主库(ID不匹配或offset=-1),就会触发全量同步

        • step1:主库生成RDB快照文件(二进制格式),生成RDB快照的同时会将所有新的写命令记录到复制积压缓冲区(用于后面的增量同步),RDB生成完毕通过网络传输到从库

        • step2:从库接收到RDB文件后,直接清除自身当前所有数据,加载RDB文件,将数据写入内存,恢复主库的全量数据状态。

        • step3:RDB加载完毕后,主库将复制积压缓冲区中的写命令(RDB生成以及从库运行RDB文件期间产生的)发送给从库,从库就执行这些命令,确保和主节点数据完全一致,至此全量同步完成。

    • 二是增量同步

      • why:从库和主库曾经连接过(ID匹配而且offset有效),且主库的复制积压缓冲区保留着从库的偏移量,才会触发增量同步。

        • step1:从库发送PSYNC <master_run_id> <offset>命令自报偏移量。

          • 这里注意!!!

          • 主节点检查复制积压缓冲区中是否包含该偏移量之后的命令:

            • 若包含(偏移量在缓冲区范围内)→ 进入增量步;

            • 若不包含(偏移量已被覆盖)→ 触发全量同步(因缓冲区是环形结构,旧命令会被新命令覆盖)。

        • step2:主库从复制积压缓冲区中提取从库偏移量(offset)之后的所有写命令(如 offset=1000offset=2000 的命令),将这些命令发送给从库,从库执行后即可追上主库的最新状态。

主从复制完毕!!:

无论是全量还是增量同步完成后,主从节点会进入持续增量同步阶段,确保后续数据一致!!!

复制积压缓冲区是一个先进先出的队列,存储的是最近主库的数据修改命令

 

  • 下图是redis断线后的重连机制

 

 

2.哨兵模式

主从模式保证不了redis的高可用,因为谁也不知道什么时候主库崩了或者从库崩了或者大家一起崩了,这样前面主从模式配置的一切都用不上了。

  • 前提Redis中提供了哨兵(Sentinel)机制来监控主从集群以及实现主从集群的自动故障恢复。

  • 哨兵(Sentinel):三个作用加一种情况如下

    • 监控:Sentinel基于心跳机制监测状态,默认每隔1s去发送一个PING命令去给每一个节点。

      • 没回应PONG:就是在Sentinel规定的时间内没有得到回应,则发命令的Sentinel主观认为这个节点下线,为主观下线

      • 都没回应:当一个Sentinel发现某个节点主观下线后,就发通知给其他的Sentinel,然后其他的Sentinel也发PING命令给该节点,如果超过指定数量quorum(默认quorum为Sentinel实例的一半),就会被认为客观下线

    • 选举和恢复故障,通知:如果master故障,Sentinel会将一个slave提升为master,故障恢复还是以新的为主。

      • 选择:先是比较优先级(slave-priority)谁高,再是比较谁的offset大,最后比较ID谁靠前,最重要的就是比较offset偏移量。

      • 选择后:哨兵向新节点发送SLAVEOF NO ONE命令,让此节点成为主节点,然后通知其他的从节点SLAVEOF命令,从新的主节点开始数据同步,同时也会更新配置文件和内存的数据结构,通知客户端让客户端连接新的主节点。

    • 脑裂:当主节点有三个从节点,而有俩个从节点因为网络原因跟主节点和另一个从节点分离,误判主节点下线,触发不必要的故障转移,原主节点的写操作未同步到新主节点,导致数据丢失。

      • 防止此情况配置

        • min-replicas-to-write 1:表示最少salve节点为1个

        • min-replicas-max-lag 5:表示数据复制和同步的延迟不能超过五秒

    •  

 

 

 

 

Sentinel

监控

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3.分片集群

主从复制和哨兵模式可以解决高可用,高并发读的问题,但是还有俩个问题:海量数据存储问题高并发写的问题

  • 特征作用

    • 多个master,每个master保存不同数据

    • 每个master有多个slave节点

    • master通过ping监测彼此健康状态

    • 客户端请求可以访问集群任意节点,最后都会转发到正确的节点

  • 存储和读取

    • Redis分片集群中引入了哈希槽的概念,一共有16384个哈希槽。

    • redis集群将16384个插槽分配到不同的实例

    • 读写数据:根据key的有效部分计算哈希值,对16384取余(有效部分,就是如果key前面有大括号,那么大括号内的即是有效部分,如果没有,则是key本身为有效部分)余数作为插槽,寻找插槽所在的实例。

 

9.为什么redis快

  • why:redis是单线程的,为什么还这么快

    • Redis是纯内存操作,执行速度快,0磁盘IO延迟,访问速度比磁盘快10万倍。

    • 采用单线程,避免不必要的上下文切换可竞争条件,所有操作都是原子性。

    • 使用I/O多路复用模型,非阻塞IO

 

 

I/O多路复用模型

实现高效的网络请求

  • 用户空间和内核空间

  • 常见的IO模型

    • 阻塞IO(Blocking IO)

    • 非阻塞IO(Nonblocking IO)

    • IO多路复用(IO Multiplexing)

  • Redis网络模型

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值