目录
- 前言
- 一、基础概念
- 二、Redis持久化
- 三、Redis分布式存储
- 后记
前言
本篇主要介绍Redis数据库的相关内容。
“基础知识”是本专栏的第一个部分,本篇博文是第九篇博文,如有需要,可:
- 点击这里,返回本专栏的索引文章
- 点击这里,返回上一篇《【Java校招面试】基础知识(八)——Linux服务器》
一、基础概念
01. 缓存中间件Memcache和Redis的区别
1) Memcache支持简单数据类型,Redis支持的数据类型更加丰富;
2) Memcache不支持数据持久化,Redis支持;
3) Memcache不支持主从同步,Redis支持;
4) Memcache不支持分片,Redis支持。
02. 为什么Redis能这么快?
1) 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高;
2) 数据结构简单,对数据的操作也简单;
3) 采用单线程,单线程也能处理高并发请求,需要多核时也可以开启多实例;
4) 使用多路I/O复用模型,非阻塞I/O。
03. I/O多路复用模型
1) File Descriptor: 文件描述符,一个打开的文件通过唯一的描述符进行引用,该描述符是打开文件的元数据到文件本身的映射;
2) 传统的阻塞I/O模型
3) Select 系统调用
Linux下的Selector用于同时监控多个文件描述符的可读可写状态,当某些文件可读或者可写时,Select方法就会返回可读以及可写的文件描述符个数。监听的任务交给Selector之后,程序就可以做别的事情,不用被阻塞。
4) Redis采用的I/O多路复用函数
i) Redis因地制宜,优先选择时间复杂度为O(1)的I/O多路复用函数作为底层实现,屏蔽底层的差异。
① evport——Solaris
② epoll——Linux
③ kqueue——MacOS或FreeBSD
ii) 以时间复杂度为O(n)的select函数作为以上环境以外的环境中的保底函数;
iii) 基于react设计模式监听I/O事件
04. Redis供用户使用的数据类型
1) String: 最基本的数据类型,二进制安全;
2) Hash: String元素组成的字典,适合用于存储对象;
3) List: 列表,按照String元素插入顺序排序;
4) Set: String元素组成的无序集合,通过哈希表实现,不允许重复;
5) Sorted Set: 通过分数来为集合中的成员进行从小到大的排序;
6) 用于计数的HyperLogLog
,用于支持存储地理位置信息的Geo
等。
05. 从海量的Key里查询某一固定前缀的Key
1) KEYS pattern
: 一次性返回pattern匹配到的所有key;
缺点: key的数量过大时,会使服务出现卡顿
2) SCAN cursor [MATCH pattern] [COUNT count]
:
i) 基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程;
ii) 以0作为游标开始一次新的迭代,直到命令返回游标0完成本次遍历;
iii) 不保证每次执行都返回某个给定数量的元素,支持模糊查询;
iv) 一次返回的数量不可控,只能大概率符合count参数。
06. 如何通过Redis实现分布式锁?
1) 分布式锁需要解决的问题
i) 互斥性: 任意时间只能有一个客户端持有锁;
ii) 安全性: 锁只能被持有该锁的客户端删除,不能由其他客户端删除;
iii) 死锁: 持有锁的客户端因为某些原因宕机,未能释放锁,造成其他客户端无 法获得锁;
iv) 容错: 当部分节点宕机时,客户端仍然能获取锁和释放锁。
2) SETNX key value
: 如果key不存在,创建并赋值
i) 时间复杂度为O(1);
ii) 返回值: 设置成功为1,失败为0。
3) 如何解决SETNX长期有效的问题?
EXPIRE key seconds
: 设置key的存活时间,当key过期时,会被自动删除。
4) 一个实现分布式锁的例子
RedisService redisService = ctx.getBean(RedisService.class);
Long status = redisService.setnx(key, “1”);
if (status == 1){
redisService.expire(key, expire);
//执行独占资源的业务逻辑
doOcuppiedBusiness();
}
缺点: 原子性得不到满足,若第二句执行完宕机,仍然不能达到目标
5) 改进的分布式锁方案
SET key value [EX seconds] [PX milliseconds] [NX|XX]
i) EX seconds
: 设置键的过期时间为seconds秒;
ii) PX milliseconds
: 设置键的过期时间为milliseconds毫秒;
iii) NX
: 只在键不存在时,才对键进行设置操作;
iv) XX
: 只在键存在时,才对键进行设置操作。
v) SET成功时返回OK,否则返回nil。
RedisService redisService = ctx.getBean(RedisService.class);
String result = redisService.set(key, “1”, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expire);
if ("OK".equals(result)){
//执行独占资源的业务逻辑
doOcuppiedBusiness();
}
07. 有大量的key同时过期时需要注意什么?
1) 问题: 由于集中过期,清除大量的key比较耗时,会出现卡顿现象。
2) 解决方案: 在设置key的过期时间时,加上一定的随机值,是过期时间分散开来。
08. 如何使用Redis做异步队列?
使用List作为队列,RPUSH生产消息,LPOP消费消息
缺点: LPOP不会等待队列里有值才消费
改进措施:
1) 在业务逻辑中引入Sleep机制调用LPOP重试;
2) BLPOP key timeout: 阻塞直到队列有消息或者超时。
缺点: 只能供一个消费者消费
3) pub/sub: 主题/订阅者模式
i) 发送者(pub)发送消息,订阅者(sub)接收消息;
ii) 订阅者可以订阅任意数量的频道;
iii) 消息的发布是无状态的,无法保证可达。
二、Redis持久化
01. Redis如何做持久化?
1) RDB(快照)持久化:
保存某个时间点的全量数据快照;
i) SAVE: 阻塞Redis进程,直到RDB文件被创建完成;
ii) BGSAVE: Fork出一个子进程来创建,不阻塞Redis进程。
RDB文件会在Redis启动时自动被载入。
缺点:
i) 内存数据的全量同步,数据量大会由于I/O而严重影响性能;
ii) 可能会因为Redis崩溃而丢失从当前至最近一次快照期间的数据。
操作方法:
在redis.conf文件中配置,参数:
i) save interval count
,表示interval秒内有count次写入操作就保存快照
例如:
save 900 1
save 300 10
save 60 10000
配置多条是因为不同时段负载不同,为了性能和数据安全的需要
ii) stop-writes-on-bgsave-error yes/no
,表示在备份线程出错时是否继续写 入数据。为了保证持久化的数据的一致性.
iii) rdbcompression yes/no
,表示是否压缩后再保存RDB文件。最好设置为 no,因为Redis本来就是CPU密集型的数据库,再进行压缩会增加CPU负担。
2) AOF(Append-Only-File)持久化:
保存写状态
i) 记录除了查询以外的所有变更数据库状态的指令;
ii) 以append的形式追加保存到AOF文件中(增量)。
操作方法:
在redis.conf文件中配置(默认关闭),参数:
i) appendonly yes/no
,表示是否开始AOF持久化;
ii) appendfilename “filename.aof”
,指定AOF文件名;
iii) appendfsync always/everysec/no
,表示同步的机制,每次写入就同步/每 秒同步一次/决策交由系统决定,最好设置为everysec
,速度快,安全性也好。
02. 自动化触发RDB持久化的方式
1) 根据redis.conf配置里的SAVE m n
定时触发(用的是BGSAVE);
2) 主从复制时,主节点自动触发;
3) 执行Debug Reload;
4) 执行Shutdown且没有开启AOF持久化。
03. BGSAVE的原理
Linux下Fork进程时用到的Copy-On-Write机制
如果有多个调用者同时要求相同的资源(如内存或磁盘上的数据),他们会共同 获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会 真正复制一份专用的副本给该调用者,而其他调用者所见到的最初的资源仍然保持不 变。
04. 日志重写解决AOF文件不断增大的问题
1) 调用fork(),创建一个子进程;
2) 子进程把当前数据库的键值写入一个临时文件里,不依赖原来的AOF文件;
3) 主进程持续将新的变动同时写到内存和原来的AOF里;
4) 主进程获取子进程重写AOF完成的信号,往新AOF同步增量动态;
5) 使用新的AOF文件替换掉旧的AOF文件。
05. RDB和AOF共存情况下的回复流程
Redis在启动时,先判断判断AOF文件是否存在,如果存在就直接用AOF文件恢复数据库,否则判断RDB文件是否存在,如果存在则使用RDB文件恢复数据库,否则启动失败。
06. RDB和AOF的优点及缺点
1) RDB
优点: 全量数据快照,文件小,恢复快;
缺点: 无法保存最近一次快照之后的数据。
2) AOF
优点: 可读性强,适合保存增量数据,数据不易丢失;
缺点: 文件体积大,恢复时间长。
07. RDB-AOF混合持久化方式
BGSAVE
做镜像全量持久化,AOF
做增量持久化。
08. 使用Pipeline的好处
1) Pipeline和Linux的管道类似;
2) Redis基于请求/响应模型,单个请求处理需要一一应答, Pipeline批量执行命令,节省多次I/O往返的时间;
3) 有顺序以来的指令应该分批发送。
三、Redis分布式存储
01. Redis的同步机制
1) 主从同步原理
2) 全量同步
i) Slave发送sync命令道Master;
ii) Master启动一个后台进程,将Redis中的数据快照保存到文件中;
iii) Master将保存数据快照期间接收到的写命令混存起来;
iv) Master完成写文件操作后,将文件发送给Slave;
v) Slave使用新的AOF文件替换掉旧的AOF文件;
vi) Master将这期间收集的增量写命令发送给Slave;
3) 增量同步
i) Master接收到用户的操作指令,判断是否需要传播到Slave;
ii) 将操作记录追加到AOF文件;
iii) 将操作传播到其他Slave:①对齐主从库;②往响应Slave缓存写入数据;
iv) 将缓存中的数据发送给Slave。
4) 主从模式的弊端
不具备高可用性,当Master崩溃后,Redis将不能对外提供写入操作。
02. Redis Sentinel
哨兵: 解决主从同步中Master宕机后的主从切换问题
1) 监控: 检查主从服务器是否运行正常;
2) 提醒: 通过API向管理员或者其他应用程序发送故障通知;
3) 自动故障迁移: 主从切换。
03. 流言协议Gossip
1) 每个节点都随机地与对方通信,最终所有节点的状态达成一致;
2) 种子节点定期随机向其他节点发送节点列表以及需要传播的消息;
3) 不保证信息一定会传递给所有节点,但最终会趋于一致。
04. 如何从海量的数据里快速找到所需?
1) 分片: 按照某种规则划分数据,分散存储在多个节点上。例如哈希算法,缺点是常规的按照哈希划分无法实现节点的动态增减;
2) 一致性哈希算法
对
2
32
2^{32}
232取模,将哈希值空间组织成一个圆环
3) 若Node C宕机(删减节点)
节点A、B将不受影响,原先节点C的数据将会被移动到D
4) 若新增Node X(增加节点)
节点A、B、D不受影响,C的部分数据将会被移动到X
5) 哈希环的数据倾斜问题
节点较少时,容易造成数据倾斜,多数数据存在某一台服务器上
6) 虚拟节点——解决数据倾斜问题
为每个节点计算多个哈希值,让所有的虚拟节点均匀分布在哈希环上,数据定位时只需要多加一步虚拟节点到实际节点的映射
后记
Redis相关的知识还有快表
、跳跃表
等,在后续的实战面经我们会涉及到,这里常考的知识点可以用作速查和补缺。