一、Redis基础信息

1.1 redis数据类型和底层数据结构的对应关系

1.2 数据结构的时间复杂度

1.3 为什么单线程Redis能那么快?

  1. Redis单线程是指它对网络IO和数据读写的操作采用了一个线程,而采用单线程的一个核心原因是避免多线程开发的并发控制问题
  2. 单线程的Redis也能获得高性能,跟多路复用的IO模型密切相关,因为这避免了accept()和send()/recv()潜在的网络IO操作阻塞点。

1.4 redis的持久化

a. AOF日志

优点:  
    1.写后日志,避免记录错误命令的情况
    2.不阻塞当前写操作。
缺点:
    1.刚执行完一个命令,还没有来得及记日志就宕机,有丢失数据风险。
    2.AOF虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。

两个风险都是和AOF写回磁盘的时机相关,因此产生了三种回写策略

Always:同步写回,每个写命令执行完,立马同步的将日志写回磁盘
Everysec: 每秒写回,每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区的内容写入磁盘
No: 操作系统控制的写回,每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘

回写策略的优缺点

AOF重写机制:

AOF重写机制就是在重写时,Redis根据数据库的现状创建一个新的AOF文件
作用:避免日志太大,不阻塞主线程由后台线程bgrewriteaof子进程来完成

b. RDB快照(内存快照)

1. 给那些内存数据做快照?
Redis的数据都在内存中,为了提供所有数据的可靠性保证,执行的是全量快照,把内存中所有的数据都写入磁盘中。
2. RDB文件的生成是否会阻塞主线程?
    a.save: 在主线程中执行,会导致阻塞;
    b.bgsave: 创建一个子进程,专门用于写入RDB文件,避免了主线程的阻塞,这也是Redis RDB文件生成的默认配置。
    
优点:  
    1.快照的恢复速度快   
缺点:
    快照的频率不好把握,太低可能丢失数据量大,太高产生额外的性能开销
Redis 4.0中提出了一个混合使用AOF日志和内存快照

1.5 数据同步

主从模式
当我们启动多个Redis实例的时候,它们相互之间就可以通过replicaof(Redis 5.0之前使用slaveof)命令形成主库和从库的关系,之后会按照三个阶段完成数据的第一次同步

1. 建立连接,协商同步,主要是为全量复制做准备。
2. 主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载,这个过程依赖生成的RDB文件。
3. 主库会把第二阶段执行过程中新收到的写命令,再发送给从库

同步的耗时操作:

1. 生成RDB文件  
2. 传输RDB文件

主从从模式

1.7 哨兵机制:主库挂了,如何不间断服务?

哨兵其实就是一个运行在特殊模式下的Redis进程,主从库实例运行的同时,它也在运行。哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知 。 哨兵机制实现了主从库的自动切换:

监控主库运行状态,并判断主库是否客观下线;
在主库客观下线后,选取新主库;
选出新主库后,通知从库和客户端

客户端只能把写失败的请求先缓存起来或写入消息队列中间件中,等哨兵切换完主从后,再把这些写请求发给新的主库,但这种场景只适合对写入请求返回值不敏感的业务,而且还需要业务层做适配,另外主从切换时间过长,也会导致客户端或消息队列中间件缓存写请求过多,切换完成之后重放这些请求的时间变长。

1.8 哨兵集群:哨兵挂了,主从库还能切换吗?

  1. 基于pub/sub机制(发布订阅机制)的哨兵集群组成
  2. 基于INFO命令的从库列表,这可以帮助哨兵和从库建立连接;
  3. 基于哨兵自身的pub/sub功能,这实现了客户端和哨兵之间的事件通知。

对于主从切换,当然不是哪个哨兵想执行就可以执行的,否则就乱套了。所以,这就需要哨兵集群在判断了主库“客观下线”后,经过投票仲裁,选举一个Leader出来,由它负责实际的主从切换,即由它来完成新主库的选择以及通知从库与客户端。

1.9 切片集群:数据增多了,是该加内存还是加实例

切片集群是一种保存大量数据的通用机制 在应对数据量扩容时,虽然增加内存这种纵向扩展的方法简单直接,但是会造成数据库的内存过大,导致性能变慢。Redis切片集群提供了横向扩展的模式,也就是使用多个实例,并给每个实例配置一定数量的哈希槽,数据可以通过键的哈希值映射到哈希槽,再通过哈希槽分散保存到不同的实例上。这样做的好处是扩展性好,不管有多少数据,切片集群都能应对。

2.0 String内存开销大

除了记录实际数据,String类型还需要额外的内存空间记录数据长度、空间使用等信息,这些信息也叫作元数据。当实际保存的数据较小时,元数据的空间开销就显得比较大。

String类型就会用简单动态字符串

buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis会自动在数组最后加一个“\0”,这就会额外占用1个字节的开销。
len:占4个字节,表示buf的已用长度。
alloc:也占个4字节,表示buf的实际分配长度,一般大于len。

2.1 集合统计

集合类型常见的四种统计模式,包括聚合统计、排序统计、二值状态统计和基数统计

聚合统计(Set):
    Set的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致Redis实例阻塞。
    所以,我给你分享一个小建议:你可以从主从集群中选择一个从库,让它专门负责聚合计算,
    或者是把数据读取到客户端,在客户端来完成聚合统计,这样就可以规避阻塞主库实例和其他从库实例的风险了。
    
排序统计(sorted set):
    有序集合
    List是按照元素进入List的顺序进行排序的,而Sorted Set可以根据元素的权重来排序
二值状态统计(BitMap):
    Bitmap本身是用String类型作为底层数据结构实现的一种统计二值状态的数据类型
    指集合元素的取值就只有0和1两种。在签到打卡的场景中
基数统计():
    基数统计就是指统计一个集合中不重复的元素个数

2.2 Redis的简单事务

MULTI:表示一系列原子性操作的开始。收到这个命令后,Redis就知道,接下来再收到的命令需要放到一个内部队列中,后续一起执行,保证原子性
EXEC:表示一系列原子性操作的结束。一旦Redis收到了这个命令,就表示所有要保证原子性的命令操作都已经发送完成了。
此时,Redis开始执行刚才放到内部队列中的所有命令操作。

2.3 基于redis实现消息队列

消息队列在存取消息的三个需求:

  1. 消息保序:保证业务正常数据流输出。
  2. 处理重复消息:网络抖动回出生多条消息,所以应该拥有防止重复消费的能力。
  3. 保证消息的可靠性:重启能恢复

a. 基于List实现消息队列解决方案

List本身就是按先进先出的顺序对数据进行存取的,能满足消息保序的需求.
LPUSH:把要发送的消息依次写入List
RPOP: 从List的另一端依次读取消息并进行处理
BRPOP: 阻塞式读取,客户端在没有读到队列数据时,自动阻塞,直到有新的数据写入队列,再开始读取新数据
BRPOPLPUSH: 让消费者程序从一个List中读取消息,同时,Redis会把这个消息再插入到另一个List(可以叫作备份List)留存。
这样一来,如果消费者程序读了消息但没能正常处理,等它重启后,就可以从备份List中重新读取消息并进行处理了
List类型并不支持消费组的实现

b. 基于Streams的消息队列解决方案
Streams是Redis专门为消息队列设计的数据类型,它提供了丰富的消息队列操作命令。

XADD:插入消息,保证有序,可以自动生成全局唯一ID;
XREAD:用于读取消息,可以按ID读取数据;
XREADGROUP:按消费组形式读取消息;
XPENDING和XACK:XPENDING命令可以用来查询每个消费组内所有消费者已读取但尚未确认的消息,而XACK命令用于向消息队列确认消息处理已完成。

2.4 Redis的阻塞点

a. 和客户端交互时的阻塞点
    1. 集合全量查询和聚合操作(复杂度通常为O(N))
    2. bigkey删除操作就是Redis的第二个阻塞点 (释放内存,把释放掉的内存块插入一个空闲内存块的链表)
    3. 清空数据库
b. 和磁盘交互时的阻塞点
    4. AOF日志同步写
c. 主从节点交互时的阻塞点   
    5. 加载RDB文件
d. 切片集群实例交互时的阻塞点
    

2.5 redis缓存的淘汰策略

noeviction: 写满了,再有写请求来时,Redis不再提供服务
volatile-ttl: 根据过期时间的先后进行删除
volatile-random: 设置了过期时间的键值对中,进行随机删除
volatile-lru: 使用LRU算法筛选设置了过期时间的键值对(最近最少使用)
volatile-lfu: 使用LFU算法选择设置了过期时间的键值对(最不经常使用)
allkeys-random: 从所有键值对中随机选择并删除数据
allkeys-lru: 使用LRU算法在所有数据中进行筛选
allkeys-lfu: 使用LFU算法在所有数据中进行筛选

2.6 redis缓存常见问题及解决方案

2.7 缓存污染怎么办?

1. 什么缓存污染?
有些数据被访问的次数非常少,甚至只会被访问一次。当这些数据服务完访问请求后,如果还继续留存在缓存中的话,就只会白白占用缓存空间
2. 如何解决缓存污染问题?
淘汰策略: LRU策略和LFU策略
随机淘汰策略:避免缓存污染效果差

2.8 Redis无锁原子操作

1. 把多个操作在Redis中实现成一个操作,也就是单命令操作;
2. 把多个操作写到一个Lua脚本中,以原子性方式执行单个Lua脚本。

数据修改时可能包含多个操作,读数据、数据增减、写回数据三个操作,这显然就不是单个命令操作。  
1. Redis提供了INCR/DECR命令,把这三个操作转变为一个原子操作
2. Lua脚本

2.9 Redis实现分布式锁

单命令操作实现加锁
    SETNX: 在执行时会判断键值对是否存在,如果不存在,就设置键值对的值,如果存在,就不做任何设置
    DEL命令删除锁变量
给锁变量设置一个过期时间, 避免在处理业务时产生异常,无法释放锁变量。    

基于多个Redis节点实现高可靠的分布式锁:
Redlock算法的基本思路,是让客户端和多个独立的Redis实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,
那么我们就认为,客户端成功地获得分布式锁了,否则加锁失败

3.0 Redis的事务实现

事务的ACID属性是我们使用事务进行正确操作的基本要求

Redis的事务机制可以保证一致性和隔离性
无法保证持久性
当事务中使用的命令语法有误时,原子性得不到保证,在其它情况下,事务都可以原子性执行。

3.1 Redis6.0新特性

  1. 面向网络处理的多IO线程
  2. 客户端缓存
  3. 细粒度的权限控制
  4. 以及RESP 3协议的使用

3.2 Redis与其他缓存数据库比较

 

3.3 Redis的学习路线


redis学习推荐书籍

  1. 《Redis使用手册》
  2. 《Redis设计与实现》
  3. 《Redis开发与运维》
  4. 《Redis 深度历险:核心原理与应用实践》
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐