Redis原理大全
1.Redis持久化
持久化的两种机制
RDB,按照时间间隔对数据进行快照
AOP,顺序增加执行命令,后台定期对文件进行整合,使文件不要过大
RDB
优点
1.是一个紧凑的二进制文件,保存指定时间点的数据集,适合做数据备份,灾难恢复
2.使用CopyOnWrite技术,父进程Fork出一个子进程,父进程作为快照点,恢复大量数据的时候速度更快
缺点
1.RTO比较大,可能丢失数据多,例如没30分钟一次快照
2.打快照的时候性能较差,单线程IO,可能导致毫秒级无响应
AOF(优先)
优点
顺序写,只追加的文件,备份数据性能好
丢失数据少,最多1s的数据
AOF在文件过大的时候,后台进行重写,重写过程是安全的,不会丢失数据
AOF中的文件是可读的,可用于恢复数据,例如失误执行了删除,可以从AOF文件中找到原始语句
缺点
1.备份文件比RDB更大
2.数据恢复比RDB慢
4.x版本整合策略
在AOF进行重写的时候,将AOF文件以RDB的形式进行落地,恢复性能更好
2.缓存问题
缓存击穿、穿透、雪崩本质上是一个问题,大量请求打到db上,除了每种问题自身的应对策略,核心要考虑的是,当多个线程想要去请求数据库的时候,应该通过枷锁的方式,保证只有一个线程对db发起访问,较少db的负载。
布隆过滤器
用于检索一个元素是否在集合中,缺点是有一定概率误识别,和删除困难。
原理:使用了K个哈希函数,可以保证一定不在集合中
主要参数:
expectedInsertions:期望插入的值的个数
fpp 错误率(默认值为0.03)
Redis集群
Redis Sentinal 着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
Redis Cluster 着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
Redis的同步机制了解么?
Redis可以使用主从同步,从从同步。
1.第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。
2.加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog。
哨兵的作用
集群监控:负责监控 Redis master 和 slave 进程是否正常工作。
消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
Redis速度
支持每秒10万QPS
- 基于内存,查询速度快
- 数据结构简单
- IO单线程,没有线程切换以及枷锁
- 多路复用的IO模型
过期时间
防治缓存雪崩,需要增加一点随机值
Redis分布式锁
使用Setnx当作分布式锁,配置expire配置过期时间,放置线程挂掉导致无法释放
Keys命令
Redis是单线程的,Keys命令会导致一定时间的阻塞,线上服务出现卡顿,使用scan命令,可以无阻塞的提取key列表,但是有一定概率的重复
异步队列
一般使用List做异步队列,rpush生产消息,lpop消费消息,没有消息的时候,需要sleep或者blpop阻塞消息
Redis淘汰机制
LRU 最近最少使用 LFU 最近频率最低 例如,原始包括11112,插入3的时候,LRU会淘汰1,LFU会淘汰2,2最近使用概率低
在redis中,允许用户设置最大使用内存大小maxmemory(需要配合maxmemory-policy使用),设置为0表示不限制;当redis内存数据集快到达maxmemory时,redis会实行数据淘汰策略。Redis提供6种数据淘汰策略:
- volatile-lru:从已设置过期时间的内存数据集中挑选最近最少使用的数据 淘汰;
- volatile-ttl: 从已设置过期时间的内存数据集中挑选即将过期的数据 淘汰;
- volatile-random:从已设置过期时间的内存数据集中任意挑选数据 淘汰;
- allkeys-lru:从内存数据集中挑选最近最少使用的数据 淘汰;
- allkeys-random:从数据集中任意挑选数据 淘汰;
- no-enviction(驱逐):禁止驱逐数据。(默认淘汰策略。当redis内存数据达到maxmemory,在该策略下,直接返回OOM错误); 关于maxmemory设置,通过在redis.conf中maxmemory参数设置,或者通过命令CONFIG SET动态修改 关于数据淘汰策略的设置,通过在redis.conf中的maxmemory-policy参数设置,或者通过命令CONFIG SET动态修改
Redis通信协议
Redis 的通信协议是 Redis Serialization Protocol,翻译为 Redis 序列化协议,简称 RESP
- 在 TCP 层
- 是二进制安全的
- 基于请求 - 响应模式
- 简单、易懂(人都可以看懂)
Redis 的线程模型
Redis 在单线程下还可以支持高并发的一个重要原因就是 Redis 的线程模型:基于非阻塞的IO多路复用机制
Redis 是基于 reactor 模式开发了网络事件处理器,这个处理器叫做文件事件处理器(file event handler)。由于这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。采用 IO 多路复用机制同时监听多个 Socket,根据 socket 上的事件来选择对应的事件处理器来处理这个事件。模型如下图:
从上图可知,文件事件处理器的结构包含了四个部分:
多个 Socket
IO 多路复用程序
文件事件分派器
事件处理器
Redis持久化
RDB 持久化
RDB(Redis Database) 通过快照的形式将数据保存到磁盘中。所谓快照,可以理解为在某一时间点将数据集拍照并保存下来。Redis 通过这种方式可以在指定的时间间隔或者执行特定命令时将当前系统中的数据保存备份,以二进制的形式写入磁盘中,默认文件名为dump.rdb。
RDB 的触发有三种机制:
-
save命令Redis是单线程程序,这个线程要同时负责多个客户端套接字的并发读写操作和内存结构的逻辑读写。而save命令会阻塞当前的Redis服务器,在执行该命令期间,Redis无法处理其他的命令,直到整个RDB过程完成为止
-
bgsave命令,后台备份为了不阻塞线上的业务,那么Redis就必须一边持久化,一边响应客户端的请求。所以在执行
bgsave时可以通过fork一个子进程,然后通过这个子进程来处理接下来所有的保存工作,父进程就可以继续响应请求而无需去关心I/O操作 -
redis.config中配置自动化备份save 300 10意味着在 300 秒内如果有十次或以上的修改操作,那么就进行数据备份,依次类推COW:Redis在持久化的时候会去调用
glibc的函数fork出一个子进程,快照持久化完成交由子进程来处理,父进程继续响应客户端的请求。而在子进程刚刚产生时,它其实使用的是父进程中的代码段和数据段。所以fork之后,kernel会将父进程中所有的内存页的权限都设置为read-only,然后子进程的地址空间指向父进程的地址空间。当父进程写内存时,CPU硬件检测到内存页是read-only的,就会触发页异常中断(page-fault),陷入 kernel 的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。而此时子进程相应的数据还是没有发生变化,依旧是进程产生时那一瞬间的数据,故而子进程可以安心地遍历数据,进行序列化写入磁盘了。
优劣势
-
优势:
- RDB 是一个非常紧凑(compact)的文件(保存二进制数据),它保存了 Redis 在某个时间点上的数据集。 这种文件非常适合用于进行备份: 比如说,你可以在最近的 24 小时内,每小时备份一次 RDB 文件,并且在每个月的每一天,也备份一个 RDB 文件。 这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本;
- RDB 非常适用于灾难恢复(disaster recovery):它只有一个文件,并且内容都非常紧凑,可以(在加密后)将它传送到别的数据中心;
- RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是
fork出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作; - RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
-
劣势:
- 如果业务上需要尽量避免在服务器故障时丢失数据,那么 RDB 并不适合。 虽然 Redis 允许在设置不同的保存点(save point)来控制保存 RDB 文件的频率, 但是, 由于 RDB 文件需要保存整个数据集的状态, 所以这个过程并不快,可能会至少 5 分钟才能完成一次 RDB 文件保存。 在这种情况下, 一旦发生故障停机, 就可能会丢失好几分钟的数据。
- 每次保存 RDB 的时候,Redis 都要
fork()出一个子进程,并由子进程来进行实际的持久化工作。 在数据集比较庞大时,fork()可能会非常耗时,造成服务器在某某毫秒内停止处理客户端; 如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒。 虽然 AOF 重写也需要进行fork(),但无论 AOF 重写的执行间隔有多长,数据的耐久性都不会有任何损失。
AOF持久化
RDB 持久化是全量备份,比较耗时,所以Redis就提供了一种更为高效地AOF(Append Only-file)持久化方案,简单描述它的工作原理:AOF日志存储的是Redis服务器指令序列,AOF只记录对内存进行修改的指令记录。
在服务器从新启动时,Redis就会利用 AOF 日志中记录的这些操作从新构建原始数据集。
Redis会在收到客户端修改指令后,进行参数修改、逻辑处理,如果没有问题,就立即将该指令文本存储到 AOF 日志中,也就是说,先执行指令才将日志存盘。
触发方案
- always:每次发生数据修改就会立即记录到磁盘文件中,这种方案的完整性好但是IO开销很大,性能较差;
- everysec:在每一秒中进行同步,速度有所提升。但是如果在一秒内宕机的话可能失去这一秒内的数据;
- no:默认配置,即不使用 AOF 持久化方案。
AOF 重写机制
Redis 提供了bgrewriteaof指令用于对AOF日志进行重写,该指令运行时会开辟一个子进程对内存进行遍历,然后将其转换为一系列的 Redis 的操作指令,再序列化到一个日志文件中。完成后再替换原有的AOF文件,至此完成。
同样的也可以在redis.config中对重写机制的触发进行配置:
通过将no-appendfsync-on-rewrite设置为yes,开启重写机制;auto-aof-rewrite-percentage 100意为比上次从写后文件大小增长了100%再次触发重写;
auto-aof-rewrite-min-size 64mb意为当文件至少要达到64mb才会触发制动重写
fsync 函数
再将AOF配置为appendfsync everysec之后,Redis在处理一条命令后,并不直接立即调用write将数据写入 AOF 文件,而是先将数据写入AOF buffer(server.aof_buf)。调用write和命令处理是分开的,Redis只在每次进入epoll_wait之前做 write 操作
AOF 的优劣
- AOF 持久化的默认策略为每秒钟
fsync一次,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多也只会丢失掉一秒钟内的数据; - AOF 文件是一个只进行追加操作的日志文件(append only log), 因此对 AOF 文件的写入不需要进行
seek, 即使日志因为某些原因而包含了未写入完整的命令(比如写入时磁盘已满,写入中途停机,等等),redis-check-aof工具也可以轻易地修复这种问题。 - Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
- AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。
AOF 的缺点
- 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
- 丢失数据最少,性能高。
- 重启数据恢复效率低
混合持久化
重启 Redis 时,如果使用 RDB 来恢复内存状态,会丢失大量数据。而如果只使用 AOF 日志重放,那么效率又太过于低下。Redis 4.0 提供了混合持久化方案,将 RDB 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是自 RDB 持久化开始到持久化结束这段时间发生的增量 AOF 日志,通常这部分日志很小。
Redis事务
Redis 通过 MULTI、 EXEC、 DISCARD、 WATCH 、 UNWATCH 来实现事务功能,Redis 事务具备如下几个特性
- Redis 会将事务中的多个命令一次性、按顺序一次执行,在执行期间可以保证不会中断事务去执行其他命令
- Redis 的事务机制是不能保证原子性的,它只保证隔离性和一致性。
multi 标志着事务的开始,在 multi 之后的命令都不会执行,全部进入事务队列中,直到服务器接收 exec 或者 discard 才会开始执行或者放弃整个事务,当执行完整个事务后,Redis 会一次性返回所有命令的运行结果。
Redis 发布订阅
Redis 提供了基于“发布/订阅”模式的消息机制,发送者(publish)发布消息,订阅者(subscribe)接收消息,两者之间不需要进行直接通信,他们之间通过频道进行消息传递。发布者向指定的频道(channel)发布消息,订阅了该频道的订阅者都可以接收到该消息。
Redis 中相关的命令如下:
PSUBSCRIBE pattern [pattern ...]:订阅一个或多个符合给定模式的频道。
PUBSUB subcommand [argument [argument ...]]:查看订阅与发布系统状态。
PUBLISH channel message :将信息发送到指定的频道。
PUNSUBSCRIBE [pattern [pattern ...]]:退订所有给定模式的频道。
SUBSCRIBE channel [channel ...]:订阅给定的一个或多个频道的信息。
UNSUBSCRIBE [channel [channel ...]]:退订给定的频道。
Redis慢查询
通过慢查询分析,找到有问题的命令进行优化。慢查询的结构记录再自己的列表中,列表为先进先出。
Redis 执行命令分为四个步骤:发送命令、命令排队、执行命令、返回结果。需要注意的是,慢查询只统计步骤 3 的时间,所以没有慢查询并不代表客户端没有超时问题。
查询
SLOWLOG GET 10
查询超过某个阈值的慢查询
slowlog-log-slower-than
配置慢查询列表长度
config set slowlog-log-slower-than 0
config set slowlog-max-len 1024
config rewrite
最佳实践
慢查询功能可以有效地帮助我们找到 Redis 可能存在的瓶颈,但在实际使用过程中要注意以下几点:
slowlog-log-slower-than:默认是 10 毫秒,我们需要根据 Redis 的并发量调整该值。若我们采用默认,那么命令的执行时间要超过 10 毫米才会记录,如果命令的执行时间超过 10 毫秒,那么 Redis 的 QPS 连 100 都不到,因此对于高并发场景,建议将该值设置较小。slowlog-max-len:线上建议调大慢查询列表,记录慢查询时Redis会对长命令做阶段操作,并不会占用大量内存.增大慢查询列表可以减缓慢查询被剔除的可能,例如线上可设置为1000以上。- 慢查询只记录命令的执行时间,并不包括命令排队和网络传输时间.因此客户端执行命令的时间会大于命令的实际执行时间.因为命令执行排队机制,慢查询会导致其他命令级联阻塞,因此客户端出现请求超时时,需要检查该时间点是否有对应的慢查询,从而分析是否为慢查询导致的命令级联阻塞。
布隆过滤器
为加快检索给定值是否存在的速度,减少数据接口所占用的空间。
布隆过滤器是一个由 一个长度为 M 比特的位数组(bit array)与 K 个哈希函数(hash function) 组成的数据结构。布隆过滤器主要用于用于检索一个元素是否在一个集合中。
我们再查下 ”Redis“,假设返回的哈希值为 1 5 7,得到的比特值为 1 0 0 ,所以我们可以很确切地说”Redis“这个值一定不存在,如果查询 “Java” 得到的哈希值为 1 6 9,比特值为 1 1 1,那么我们是否可以说一定存在呢?答案是不可以,只能说 “Java” 这个值有可能存在。因为随着数据的增多,越来越多位置的比特值被设置为 1,有可能存在某个值从来没有被存储,但是哈希函数返回的位值都为 1 。
优缺点
布隆过滤器的优点显而易见:
- 不需要存储数据,只用比特表示,因此在空间占用率上有巨大的优势
- 检索效率搞,插入和查询的时间复杂度都为 O(K)(K 表示哈希函数的个数)
- 哈希函数之间相互独立,可以在硬件指令层次并行计算,因此效率较高。
缺点:
- 存在不确定的因素,无法判断一个元素是否一定存在,所以不适合要求 100% 准确率的场景
- 只能插入和查询元素,不能删除元素。
在缓存的应用中,布隆过滤器也是解决缓存穿透的解决方案
Redis配置项
redis.conf 配置项说明如下:
- Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
daemonize no
- 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid
- 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
port 6379
- 绑定的主机地址
bind 127.0.0.1
5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300
- 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
loglevel verbose
- 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
logfile stdout
- 设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id
databases 16
- 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
save
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。
- 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes
- 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
- 指定本地数据库存放目录
dir ./
- 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
slaveof
- 当master服务设置了密码保护时,slav服务连接master的密码
masterauth
- 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,默认关闭
requirepass foobared
- 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxclients 128
- 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory
- 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendonly no
- 指定更新日志文件名,默认为appendonly.aof
appendfilename appendonly.aof
- 指定更新日志条件,共有3个可选值: no:表示等操作系统进行数据缓存同步到磁盘(快) always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全) everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec
- 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
vm-enabled no
- 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-swap-file /tmp/redis.swap
- 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
vm-max-memory 0
- Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
vm-page-size 32
- 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
vm-pages 134217728
- 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
vm-max-threads 4
- 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
glueoutputbuf yes
- 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
- 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
activerehashing yes
- 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
include /path/to/local.conf
配置文件的查看和修改最简单的方式就是通过编辑工具打开然后就可以查看和修改了,但是为了能更好的装逼,还是可以学几个命令的,通过命令来查看和修改配置文件,如下:
你可以通过 CONFIG 命令查看或设置配置项。