REDIS简介
- 完全基于内存,操作效率高
- 数据结构简单
- 基于单线程,顺序执行所有请求,避免多线程环境的上下文切换
- 使用IO多路复用,非阻塞IO
多路IO复用模型
FD(file description) 文件描述符
在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。
BIO(Blocking IO)阻塞IO模型
select()系统调用
阻塞用户进程,通过轮询的方式监控文件描述符的可读可写状态。将通知任务交由select函数,用户进程可以继续执行其他任务,不被阻塞。
redis采用的多路IO复用函数
epoll/kqueue/evport/select
- 根据操作系统选择不同函数
- 优先选择O(1)时间复杂度的函数
- 使用O(n)时间复杂度的select保底
- 基于react设计模式监听IO事件
redis基本数据类型
string
hash
list
set
sorted set
hyperloglog
geo
Q:从海量数据里查询某一固定前缀的key
keys pattern: 查找所有符合给定模式pattern的key
使用keys对线上业务的影响
- 一次返回所有匹配的keys
- 当key的数量过大会导致服务卡顿
SCAN cursor [MATCH pattern] [COUNT count]:
- 基于游标的迭代器,需要基于上次查询返回的游标延续之前的迭代行为
- 以0开始作为一次新的迭代,直到命令返回游标0迭代结束
- 不保证每次查询返回给定count参数值数量的结果,返回数量不可控
- 支持以match参数指定的模式模糊查询
Q:如何通过redis实现分布式锁
分布式锁: 控制不同客户端同时访问同一共享数据时所需锁的实现
分布式锁需要解决的问题
- 互斥性
- 安全性
- 容错性
- 死锁
方式1:setnx + expire
SETNX key value
set not exist 当key不存在时,返回1。否则返回0。
EXPIRE key timeout
给key设置超时时间
int status = jedis.setnx("lock", "1");
if(status == 1){
jedis.expire("lock", 1000);
doSomething();
}
当执行完setnx后宕机,导致没有设置超时时间而一直占用锁
方式2:set
SET key value [EX seconds] [PX milliseconds] [NX|XX]
- EX 超时时间 单位秒
- PX 超时时间 单位毫秒
- NX 当key不存在 执行
- XX 当key存在 执行
- 操作成功返回"ok",否则返回"nil"
String status = jedis.set(key, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime );
if("OK".equals(status)){
doSomething();
}
大量key同时过期
集中过期,由于要清除大量过期的key,redis服务可能会出现卡顿
通过使用随机过期时间,使key的过期时间点分散。
Q:如何实现异步队列
方式1:使用list作为队列,rpush生产消息,lpop消费消息
缺点:不会等待队列里有值,直接消费
弥补:在应用层引入循环机制,等待队列里有消息才消费,否则sleep。
方式2:使用blpop key timeout,阻塞控制消费消息
缺点:只能供一个消费者消费
方式3:pub/sub 发布订阅
pub发布者发布消息 PUBLISH channel message
sub订阅者接收消息 SUBSCRIBE channel [channel … ]
订阅者可以订阅任意数量的频道
缺点:消息的发布是无状态的,不保证消息的可达。
持久化方式之RDB
RDB:保存某个时间点的全量数据快照
SAVE:阻塞redis主进程,直到rdb文件被创建,接收到服务端返回的"ok"。
BGSAVE:服务端立即返回一条消息"Background saving started",并fork出一个子进程来创建rdb文件,不会阻塞主进程继续处理客户端请求。同时主进程通过轮询的方式检查rdb文件是否创建完成。
自动触发RDB持久化的方式
- 根据redis.conf配置文件中的SAVE m n触发
- 主从复制时,主节点触发
- 执行SHUTDOWN,且没有开启AOF时触发
- 执行DEBUG RELOAD时触发
RDB原理
redis执行bgsave指令时,会先检查有没有执行aof/rdb的子进程,存在则返回错误调用信息。否则触发持久化操作,调用rdbsavebackground,fork子进程创建一个临时的rdb文件,直到数据持久化完毕,替换之前的rdb文件。在执行fork时,系统使用copy-on-write策略。此过程主进程不会被阻塞,可以执行其他客户端请求。
redis在启动时,发现rdb文件会自动载入。
缺点:
redis内存数据的全量同步,当数据量大时由于IO而影响系统性能
可能由于redis服务器故障而丢失从当前至上次备份期间的数据
copy-on-write
当多个调用者同时请求相同资源时,他们会获得指向相同物理地址的指针。直到其中某个调用者试图修改数据时,系统会为其复制一份副本,而其他调用者所指向的初始资源保持不变。
持久化方式之AOF
- 记录除了查询操作以外的所有变更数据库状态的操作
- 以append形式追加到aof文件中(增量保存)
AOF重写原理
- 调用fork生成子进程
- 把新的aof写入临时文件,写入操作是基于当前内存数据的状态解析出指令,所以不依赖于原先的aof文件。
- 主进程持续将新的变动写入内存和原先的aof文件中,保证数据完整性。
- 当主进程获取到子进程重写aof完成信号时,把内存中新的操作追加到新的aof文件中(增量操作)。
- 替换原先的aof文件。
BGREWRITEAOF: 手动触发aof重写指令
持久化方式之混合模式
RDB和AOF共存的情况下数据恢复流程
RDB和AOF的优缺点
- **RDB优点:**全量数据的快照,文件小,恢复快
- **RDB缺点:**无法保存最近一次快照后的更新数据
- **AOF优点:**可读性高,适合保存增量数据,不已丢失
- **AOF缺点:**文件体积大,恢复时间长。日志回放。
RDB和AOF混合持久化模式
redis 4.0+
BDSAVE全量持久化,AOF增量持久化
redis启动时,使用rdb文件重新构建内容,再通过aof文件重放近期更新指令恢复数据完整状态。
PIPELINE主从同步
pipeline
- pipeline和linux的管道相似
- redis基于请求响应模式,需要对请求一一应答
- pipeline可以批量传输数据,节省多次请求IO时间
- 有顺序依赖的指令需要按次序发送
redis的主从同步原理
- 首次同步时,主节点调用一次BGSAVE指令保存当前内存的全量数据快照,同时将后续修改操作记录到缓存中。
- 当BGSAVE完成后,将rdb文件全量同步到从节点中。接收完成后,将数据加载内存中。
- 加载完成后通知主节点,将期间修改的增量数据同步到从节点重放。
全同步过程
- slave发送sync指令到master
- master启动后台进程,将redis中的数据快照保存到rdb文件 (BGSAVE)
- master将保存数据快照期间的修改操作缓存起来
- 当master完成rdb文件写入后,将文件发送给slave
- 使用心得rdb文件替换原先的rdb文件,并将全量数据载入内存。
- 完成数据载入后,通知master发送此期间新增的修改命令并重放。
全量同步完成之后,所有写操作都是在master上进行,所有读操作都是在slave上进行。
增量同步过程
为保证redis主从在运行期间数据的最终一致性,需要把master上的写操作扩散到slave中。
- master接收到指令,处理并判断是否需要扩散到slava中
- 将操作记录追加到aof文件中
- 将操作传播到其他slave中: a.对其主从库。b.将操作写入响应缓存中。
- 将缓存中的数据发送给slave
主从模式缺点:不具备高可用性。当主服务器挂掉后就不能对外提供新的写入操作。因此会选择使用哨兵模式(sentinel)保证redis集群的高可用性
redia哨兵模式
sentinel功能
- 监控:检查主从服务器运行状态 (留言协议)
- 提醒:通过api向管理员发送故障通知
- 自动故障迁移:主从切换 (投票协议)
留言协议 gossip
集群
如何从海量数据中快速找到所需
- 分片:按照某种规则划分数据,分散存储在多个节点中
- 常规的按照哈希划分无法实现节点的动态增减
一致性哈希算法
- 对redis服务器的ip地址或者主机名进行哈希后,对2^32取模,计算redis服务器在哈希环上的位置
- 对数据的key同样进行哈希取模后,获取到数据的存放位置。
- 数据沿顺时针遇到的第一个redis服务器节点保存次数据。
Node C宕机
新增Node X
hash环的数据倾斜
引入虚拟节点解决数据倾斜问题