Redis基础知识点
盘点了常考的Redis基础知识点
- 缓存中间件——Memcache和Redis的区别 √
- 为什么Redis能那么快 √
- Redis数据类型:√
- 从海量数据里查询某一固定前缀的key √
- 如何通过Redis实现分布式锁
- Redis如何持久化
- Redis的集群原理
- 集群可用性
缓存中间件——Memcache和Redis的区别 √
Memcache:代码层次类似Hash
Ø 支持简单数据类型
Ø 不支持数据持久化存储
Ø 不支持主从
Ø 不支持分片
Redis
Ø 数据类型丰富
Ø 支持数据磁盘持久化存储
Ø 支持主从
Ø 支持分片
为什么Redis能那么快 √
100000+QPS(QPS即query per second,每秒查询次数)
Ø 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高
Ø 数据结构简单,对数据操作也简单
Ø 采用单线程,不会有上下文切换开销,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
Ø 使用多路I/O复用模型,非阻塞IO
多路I/O复用模型
FD:File Descriptor,文件描述符
一个打开的文件通过唯一的描述符进行引用,该描述符是打开文件的元数据到文件本身的映射
传统的阻塞I/O模型
Select系统调用
Selector同时监控多个FD的可读可写情况,返回可读可写FD个数
可达到在同一个线程内同时处理多个I/O请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
Redis采用的I/O多路复用函数:epoll/kqueue/evport/select
Ø 因地制宜
Ø 优先选择时间复杂度为O(1)的I/O多路复用函数作为底层实现
Ø 以时间复杂度为O(n)的select作为保底
Ø请求/响应模型监听I/O事件
Redis数据类型:√
供用户使用的数据类型
Ø String:最基本的数据类型,二进制安全
二进制指的是可以是图片或者序列化对象,等等二进制表示的文件,最多512M。
Ø Hash:String元素组成的字典,适合用于存储对象
Ø List:列表,按照String元素插入顺序排序
List是Stack结构的 先入后出
Ø Set:String元素组成的无序集合,通过哈希表实现,不允许重复(整数集合或字典)
Ø Sorted Set:通过分数来为集合中的成员进行从小到大的排序 (压缩列表或跳表)
分数重复 值不同没有问题
底层数据类型基础(了解)
简单动态字符串
链表
字典
跳跃表
整数集合
压缩列表
对象
从海量数据里查询某一固定前缀的key √
留意细节
Ø 摸清数据规模,即问清楚边界
使用keys对线上的业务的影响 √
KEYS pattern:查找所有符合给定模式pattern的key
Ø KEYS指令一次性返回所有匹配的key
Ø 键的数量过大会使服务卡顿
从海量Key里查询出某一固定前缀的Key √
SCAN cursor [MATCH pattern] [COUNT count]
Ø 基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程
Ø 以0作为游标开始一次新的迭代,直到命令返回游标0完成一次遍历
Ø 不保证每次执行都返回某个给定数量的元素,支持模糊查询
Ø 一次返回的数量不可控,只能是大概率符合count参数
第一个返回值是游标,第二个返回值是结果集,虽然写count=10但是不一定返回10个
Cursor并不一定是递增的,可能比前一次还小,可能会获取重复key
如何通过Redis实现分布式锁
分布式锁需要解决的问题 ×
Ø 互斥性
Ø 安全性
Ø 死锁
Ø 容错
SETNX key value: 如果key不存在,则创建并赋值 √
Ø 时间复杂度:O(1)
Ø 返回值:设置成功,返回1;设置失败,返回0。
因此我们可以通过该命令来实现锁,但是由于setnx长期存在,我们还需要设置过期的时间。
EXPIRE key seconds
设置key的生存时间,当key过期时(生存时间为0),会被自动删除
但是还是有风险,如果执行设置成功后,来不及设置expire就挂掉
违背了操作原子性的初衷
解决方案:
SET key value [EX seconds] [PX milliseconds] [NX|XX] √
Ø EX second: 设置键的过期时间为second秒
Ø PX millisecond: 设置键的过期时间为millisecond毫秒
Ø NX:只在键不存在时,才对键进行设置操作
Ø XX:只在键已经存在时,才对键进行设置操作
Ø Set操作成功完成时,返回OK,否则返回nil
大量key同时过期的注意事项
集中过期,由于清除大量key很耗时,会出现短暂卡顿现象 √
Ø 解决方案:在设置key的过期时间的时候,给每个key加上随机值
使用List作为队列,RPUSH生产消息,LPOP消费消息 √
Ø 缺点:没有等待队列里有值就直接消费
Ø 弥补:可以通过在应用层引入Sleep机制去调用LPOP重试
如果不用Sleep呢?
BLPOP key [key …] timeout: 阻塞直到队列有消息或者超时 √
Ø 缺点:只能供一个消费者消费
pub/sub: 主题订阅者模式 √
Ø 发送者(pub)发送消息,订阅者(sub)接收消息
Ø 订阅者可以订阅任意数量的频道
pub/sub的缺点 √
消息的发布是无状态的,无法保证可达
Redis如何持久化
Rdb(快照)持久化:保存某个时间点的全量数据快照
如果900s 内有1条修改就保存
如下同理
当备份进程出错,主进程停止写入操作
尽量不开启,因为redis是cpu密集型的,开启增加cpu消耗
关闭rdb的命令:config set save “”
或者在配置文件中save都注释掉后再加上 save “”就禁用了rdb方式
RDB(快照)持久化:保存某个时间点的全量数据快照
Ø SAVE:阻塞Redis的服务器进程,直到RDB文件被创建完毕
Ø BGSAVE:Fork出一个子进程来创建RDB文件,不阻塞服务器进程
上次执行save指令的时间
自动化触发RDB持久化的方式 ×
Ø 根据redis.conf配置里的SAVE m n定时触发(用的是BGSAVE)
Ø 主从复制时,主节点自动触发
Ø 执行Debug Reload
Ø 执行Shutdown且没有开启AOF持久化
Copy-on-Write
- 系统调用fork():创建进程,实现了Copy-on-Write
如果有多个调用者同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本给该调用者,而其他调用者所见到的最初的资源仍然保持不变。
RDB持久化的缺点 √
Ø 内存数据的全量同步,数据量大会由于I/O而严重影响性能
Ø 可能会因为Redis挂掉而丢失从当前至最近一次快照期间的数据
AOF(Append-Only-File)持久化:保持写状态
Ø 记录下除了查询以外的所有变更数据库状态的指令
Ø 以append的形式追加保存到AOF文件中(增量)
默认关闭,修改redis.conf
改成yes
然后在redis client里面输入config set appendonly yes使其生效
fork子线程将内存里面的数据更改转换成对应指令然后同时也记录新的变动到原aof。
日志重写解决AOF文件大小不断增大的问题,原理如下 -
Ø 调用fork(), 创建一个子进程
Ø 子进程把新的AOF写到一个临时文件里,不依赖原来的AOF文件
Ø 主进程持续将新的变动同时写到内存和原来的AOF里
Ø 主进程获取子进程重写AOF的完成信号,往新AOF同步增量变动
Ø 使用新的AOF文件替换掉旧的AOF文件
redis在载入AOF文件的时候,会创建一个虚拟的client,把AOF中每一条命令都执行一遍,最终还原回数据库的状态,它的载入也是自动的。在RDB和AOF备份文件都有的情况下,redis会优先载入AOF备份文件
AOF文件可能会随着服务器运行的时间越来越大,可以利用AOF重写的功能,来控制AOF文件的大小。AOF重写功能会首先读取数据库中现有的键值对状态,然后根据类型使用一条命令来替代前的键值对多条命令。
Redis数据的恢复
RDB和AOF文件共存情况下的恢复流程 √
有AOF就用AOF没有就用RDB
RDB和AOF的优缺点 √
Ø RDB优点:全量数据快照,文件小,恢复快
Ø RDB缺点:无法保存最近一次快照后的数据
Ø AOF优点:可读性高,适合保存增量数据,数据不易丢失
Ø AOF缺点:文件体积大,恢复时间长
RDB-AOF混合持久化的方式 -
数据备份
Ø 先使用RDB全量持久化,AOF做增量持久化
Ø AOF文件的前半段是RDB格式的全量数据后半段是redis命令格式的增量数据
数据恢复
当我们开启了混合持久化时,启动redis依然优先加载aof文件,aof文件加载可能有两种情况如下:
aof文件开头是rdb的格式, 先加载 rdb内容再加载剩余的 aof。
aof文件开头不是rdb的格式,直接以aof格式加载整个文件。
使用Pipline的好处 -
Ø Pipeline和Linux的管道类似
Ø Redis基于请求/响应模型,单个请求处理需要一一应答
Ø Pipeline批量执行指令,节省多次IO往返的时间
Ø 有顺序依赖的指令建议分批发送
Redis的同步机制
Ø 主从
Ø 哨兵
Ø 集群
主从同步原理
Master节点用于写操作
Slaver节点用于读操作
定期备份工作也是单独选择一个slaver实现的,
全同步流程:
Ø Salver发送sync命令到Master
Ø Master启动一个后台进程,将Redis中的数据快照保存到文件中
Ø Master将保存数据快照期间接收到的写命令缓存起来
Ø Master在完成写文件操作后,将该文件发送给Salver
Ø 使用新的AOF文件替换掉旧的AOF文件
Ø Master将这期间收集的增量写命令发送给Salver端
增量同步过程
Ø Master接收到用户的操作指令,判断是否需要传播到Slave
Ø 将操作记录追加到AOF文件
Ø 将操作传播到其他Slave:1、对齐主从库;2、往响应缓存写入指令
Ø 将缓存中的数据发送给Slave
弊端是,不具备高可用性。当Master节点挂掉后,redis将无法提供写入服务
哨兵同步Redis Sentinel
解决主从同步Master宕机后的主从切换问题:
Ø 监控:检查主从服务器是否运行正常
Ø 提醒:通过API向管理员或者其他应用程序发送故障通知
Ø 自动故障迁移:主从切换
投票决定是否故障迁移,和谁作为新的主服务器
流言协议接收主服务器是否下线的信息
流言协议Gossip
在杂乱无章中寻求一致
Ø 每个节点都随机地和对方通信,最终所有节点的状态达成一致
Ø 种子节点定期随机向其他节点发送节点列表以及需要传播的消息
Ø 不保证信息一定会传递给所有节点,但是最终会趋于一致
Redis 集群是去中心化的,彼此之间状态同步靠 gossip 协议通信,集群的消息有以下几种类型:
- Meet 通过「cluster meet ip port」命令,已有集群的节点会向新的节点发送邀请,加入现有集群。
- Ping 节点每秒会向集群中其他节点发送 ping 消息,消息中带有自己已知的两个节点的地址、槽、状态信息、最后一次通信时间等。
- Pong 节点收到 ping 消息后会回复 pong 消息,消息中同样带有自己已知的两个节点信息。
- Fail 节点 ping 不通某节点后,会向集群所有节点广播该节点挂掉的消息。其他节点收到消息后标记已下线。
由于去中心化和通信机制,Redis Cluster 选择了最终一致性和基本可用。
Redis的集群原理
集群可用性
如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完整时进入fail状态.。
如何从海量数据里快速找到所需?
Ø 分片:按照某种规则去划分数据,分散存储在多个节点上
Ø 常规的按照哈希划分无法实现节点的动态增减
一致性哈希算法:对2^32取模,将哈希值空间组织成虚拟的圆环
顺时针递增
将数据key使用相同的函数Hash计算出哈希值
使用服务器主机名或者ip地址哈希
对数据同样进行hash算法,确定位置,然后根据环顺时针行走,第一个遇到的服务器就是目标存储服务器。
假设nodeC宕机,只有NodeB到nodeC之间的数据受影响,新来的数据都会沿顺时针存入nodeD,因此可以做到最小化有损服务。
新增服务器Node X
受影响的数据也只是nodeX到NodeB之间的数据。
受影响的数据是新服务器到其环空间中前一台服务器之间的数据
Hash环的数据倾斜问题
引入虚拟节点解决数据倾斜的问题
对每个节点都计算多个哈希,服务器ip或者主机名后面加编号实现