[TOC]

常用参数说明

配置项说明
daemonize noRedis 默认不是以守护进程的方式运行,可以通过该配置项修改,使用 yes 启用守护进程(Windows 不支持守护线程的配置为 no )
pidfile /var/run/redis.pid当 Redis 以守护进程方式运行时,Redis 默认会把 pid 写入 /var/run/redis.pid 文件,可以通过 pidfile 指定
port 6379指定 Redis 监听端口,默认端口为 6379
bind 127.0.0.1绑定的主机地址,默认只允许本地访问,将此配置删除,则允许外网访问(但是不建议,因为redis处理快,可能被暴力破解,而且以前也出现过redis漏洞)
logfile stdout日志记录方式,默认为标准输出,也可以写文件路径,如果配置 Redis 为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给 /dev/null
requirepass password设置 Redis 连接密码,如果配置了连接密码,客户端在连接 Redis 时需要通过 AUTH 命令提供密码,默认关闭

https://www.runoob.com/redis/redis-conf.html

数据类型

Redis支持五种数据类型:

  • string(字符串),

  • hash(哈希),

  • list(列表),

  • set(集合)

  • zset(sorted set:有序集合)。

    String

    string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。

string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。

// 基本命令
SET runoob "菜鸟教程"
GET runoob

Hash

Redis hash 是一个键值(key=>value)对集合。

Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。 每个 hash 可以存储

2^{32}-1

键值对(40多亿)。

可以理解为vaule是一个map,里面存很多k-v

HMSET runoob field1 "Hello" field2 "World"
HGET runoob field1

List

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

  • 比如可以通过lrange命令,就是从某个元素开始读取多少个元素,可以基于list实现分页查询,这个很棒的一个功能,基于redis实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走
  • 比如可以搞个简单的消息队列,从list头怼进去,从list尾巴那里弄出来
lpush runoob redis
lpush runoob mongodb
lrange runoob 0 10

set

Redis 的 Set 是 string 类型的无序集合。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。集合中最大的成员数为

2^{32}-1

(4294967295, 每个集合可存储40多亿个成员)。如果有多台服务器产生的值需要放一起去重,就可以用set全局去重了

sadd runoob redis
sadd runoob mongodb
sadd runoob mongodb
smembers runoob

zset

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。 不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。比如把分数换成时间戳,就可以按照时间来排序啦

zset的成员是唯一的,但分数(score)却可以重复。score可以是整数,也可以是浮点数,还可以是+inf表示无穷大,-inf表示负无穷大

zadd runoob 0 redis
zadd runoob 0 mongodb
zadd runoob 0 mongodb
zadd runoob 1 rabitmq
ZRANGEBYSCORE runoob 0 1000
ZRANGEBYSCORE runoob 0 +inf

HyperLogLog (不算类型)

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。用来计算集合数量,(基数数量,理解为去重后数量),数量值为估算,不是100%准确(这是因为它的(概率)算法),一般用来统计在线用户数,访问量等等

感觉有set,HyperLogLog就鸡肋了,其实不然,HyperLogLog统计数量时要求内存很小,几乎是恒定的,但是set会随着集合增加而增大所需内存

PFADD runoobkey "redis"
PFADD runoobkey "mongodb"
PFCOUNT runoobkey

Pub/Sub模式

发布/订阅模式,类似于消息中间件,但实现方式不一样

原理

client->pubsub_channels 是客户端维护的一个以dict结构的维护的订阅频道哈希表,VAL是NULL,不需要值。

server->pubsub_channels 是服务端维护的一个以dict结构的维护的订阅频道哈希表,VAL是以client维护的双向链表adlist。

订阅

订阅流程:SUBSCRIBE命令 SUBSCRIBE channel [channel …]

1.首先是将当前订阅的频道channel添加进客户端的pubsub_channels哈希表里面。

2.然后在将当前订阅的频道channel和对应的client以键值对添加服务端的pubsub_channels的哈希表里。

3.最后将返回的信息返回给客户端。

发布

发布流程:PUBLISH命令 PUBLISH channel message

1.首先通过频道在服务端的pubsub_channels哈希表里面找到对应的客户端链表。

2.然后递归循环链表,逐个将消息message发送对应订阅的客户端。

3.正则匹配的频道 逐个发送对应订阅的客户端。

缺点:

  1. 消息发布后如果没有消费者消费, 则这条消息会丢失,
  2. 消息不会持久化,如果redis宕机数据则丢失
  3. 集群模式下, publish命令都会向所有节点进行广播,加重带宽负担(因为集群通信是gossip,所以相当于全部节点都广播了)

Redis深度历险-Redis PubSub消息订阅发送_樊先知樊先知的博客-CSDN博客

redis pub/sub原理及实战_iverson2010112228的专栏-CSDN博客

Redis 集群中的 PUB/SUB 相关问题。 - SegmentFault 思否

过期策略

如果假设你设置一个一批key只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的? 答案是:定期删除+惰性删除

redis是单线程,收割时间也会占用线程处理时间,如果收割过于频繁,会导致读写出现卡顿。

1、主库过期策略

1.1、定时扫描

首先将每个设置了过期时间的key放到一个独立的hash中,默认每秒定时遍历这个hash而不是整个空间:

并不会遍历所有的key,采用一种简单的贪心策略

1.1.1、从过期key字典中,随机找20个key。

1.1.2、删除20个key中过期的key

1.1.3、如果2中过期的key超过1/4,则重复第一步

1.1.4、每次处理的时间都不会超过25ms (若超过则直接结束)

如果有大量的key在同一时间段内过期,就会造成数据库的集中访问,就是缓存雪崩!

1.2、惰性策略

客户端访问的时候,会对这个key的过期时间进行检查,如果过期了就立即删除。惰性策略是对定时策略的补充,因为定时策略不会删除所有过期的key

2、从库过期策略

redis不会扫描从库,删除主库数据的时候,在aof文件里生成一条del指令,在主从同步的时候,从库会执行这条指令,删除过期key。

所以集群分布式锁算法的漏洞就是这样产生的。(所以得用红锁,但红锁也不是百分百准确,详见分布式锁文章 )

很简单,就是说,你的过期key,靠定期删除没有被删除掉,还停留在内存里,占用着你的内存呢,除非你的系统去查一下那个key,才会被redis给删除掉。 如果定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了,咋整? 答案是:走内存淘汰机制。

Redis系列六:redis缓存失效策略 - 工作中的点点滴滴 - 博客园 (cnblogs.com)

内存淘汰

最大内存的设置是通过设置maxmemory来完成的,格式为maxmemory bytes ,当目前使用的内存超过了设置的最大内存,就会进行内存释放

  1. noeviction:新写入操作会报错(不删除数据),也是默认值
  2. allkeys-lru:在键空间中,移除最近最少使用的key
  3. allkeys-random:在键空间中,随机移除某个key
  4. volatile-lru:在设置了过期时间的键空间中,移除最近最少使用的key
  5. volatile-random:在设置了过期时间的键空间中,随机移除某个key
  6. volatile-ttl:在设置了过期时间的键空间中,有更早过期时间的key优先移除

持久化

Redis的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file,AOF).默认使用RDB方式

Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。

如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头, 所以会先加载RDB的数据再加载AOF的数据。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。

链接:https://www.jianshu.com/p/65765dd10671

AOF 文件的体积通常要大于 RDB 文件的体积。

AOF的速度会慢于RDB方式 ,因为AOF默认是每秒同步一次

当开启混合持久化时,fork 出的子进程先将当前全量数据以 RDB 方式写入新的 AOF 文件,然后再将 AOF 重写缓冲区(aof_rewrite_buf_blocks)的增量命令以 AOF 方式写入到文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。

面试必问的 Redis:RDB、AOF、混合持久化 - 知乎

RDB持久化

RDB持久化方式是通过快照(snapshotting)完成的,当符合一定条件时,redis会自动将内存中所有数据以二进制方式生成一份副本并存储在硬盘上。当redis重启时,并且AOF持久化未开启时(默认不开启),redis会读取RDB持久化生成的二进制文件(默认名称dump.rdb,可通过设置dbfilename修改)进行数据恢复,对于持久化信息可以用过命令“info Persistence”查看。

RDB生成快照可自动促发,也可以使用命令手动触发,以下是redis触发执行快照条件,后续会对每个条件详细说明:

  1. 客户端执行命令save和bgsave会生成快照;
  2. 根据配置文件save m n规则进行自动快照;
  3. 主从复制时,从库全量复制同步主库数据,此时主库会执行bgsave命令进行快照;
  4. 客户端执行数据库清空命令FLUSHALL时候,触发快照(清空快照);
  5. 客户端执行shutdown关闭redis时,触发快照(保存全部数据,以便下次启动恢复);
  • 客户端执行save命令,该命令强制redis执行快照,这时候redis处于阻塞状态(指fork的过程),不会响应任何其他客户端发来的请求,直到RDB快照文件执行完毕,所以请慎用。
  • save m n规则说明:在指定的m秒内,redis中有n个键发生改变,则自动触发bgsave。该规则默认也在redis.conf中进行了配置,并且可组合使用(就是写多个),满足其中一个规则,则触发bgsave

当触发生成快照时, 是通过调用操作系统的fork函数创建子进程去实现的, 这个过程它会强制阻塞进程, 导致redis无法对外提供服务, fork完成后就是子线程在生成快照了, redis就可以正常提供服务

因为fork函数 可以让子进程共享父进程的数据空间, 子进程不会复制父进程的数据空间,但是会复制内存页表(页表相当于内存的索引、目录);父进程的数据空间越大,内存页表越大,fork时复制耗时也会越多。

RDB优点和缺点

Redis中存在的两大阻塞:fork阻塞和AOF追加阻塞_u014753478的博客-CSDN博客

Redis的两种持久化方式 - 知乎 (zhihu.com)

AOF持久化

默认情况下,redis是关闭了AOF持久化,开启AOF通过配置appendonly为yes开启(配置文件).AOF实现本质是基于redis通讯协议,将命令以纯文本的方式写入到文件中。

我们修改配置文件或者在命令行直接使用config set修改,在用config rewrite同步到配置文件。通过客户端修改好处是不用重启redis,AOF持久化直接生效。但不是所有的配置都可以这样,了解一下,还是老实改配置文件吧

Redis 是先执行命令,再把数据写入日志的

AOF持久化过程

redisAOF持久化过程可分为以下阶段:

  1. 追加写入

      redis将每一条写命令以redis通讯协议添加至缓冲区aof_buf,这样的好处在于在大量写请求情况下,采用缓冲区暂存一部分命令随后根据策略一次性写入磁盘,这样可以减少磁盘的I/O次数,提高性能。

  2. 同步命令到硬盘 当写命令写入aof_buf缓冲区后,redis会将缓冲区的命令写入到文件,redis提供了三种同步策略,由配置参数appendfsync决定,下面是每个策略所对应的含义:

  • appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度

  • appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘(默认值)

  • appendfsync no #让操作系统决定何时进行同步

  1. 文件重写(bgrewriteaof)

      当开启的AOF时,随着时间推移,AOF文件会越来越大,当然redis也对AOF文件进行了优化,即触发AOF文件重写条件(后续会说明)时候,redis将使用bgrewriteaof对AOF文件进行重写。这样的好处在于减少AOF文件大小,同时有利于数据的恢复。 重写时候策略:

  • 重复或无效的命令不写入文件

  • 过期的数据不再写入文件

  • 多条命令合并写入(当多个命令能合并一条命令时候会对其优化合并作为一个命令写入,例如“RPUSH list1 a RPUSH list1 b" 合并为“RPUSH list1 a b”

    重写触发条件

    AOF文件触发条件可分为手动触发和自动触发:

    • 手动触发:客户端执行bgrewriteaof命令。
    • 自动触发:自动触发通过以下两个配置协作生效:
    • auto-aof-rewrite-min-size: AOF文件最小重写大小,只有当AOF文件大小大于该值时候才可能重写,4.0默认配置64mb。
    • auto-aof-rewrite-percentage:当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比,如100代表当前AOF文件是上次重写的两倍时候才重写。 

    AOF文件重写的时候,也会调用fork函数, 所以也会造成redis阻塞,cpu飙升

AOF追加操作会造成阻塞

命令的写入是在主线程中执行的

在AOF中,如果AOF缓冲区的文件同步策略为everysec,则在主线程中,命令写入aof_buf后调用操作系统write操作内存,write完成后主线程返回;然后fsync操作由专门的文件同步线程每秒调用一次。

这种做法的问题在于,如果硬盘负载过高,那么fsync操作可能会超过1s;如果Redis主线程持续高速向aof_buf写入命令,硬盘的负载可能会越来越大,IO资源消耗会更快, 此时write操作会被阻塞,直到fsync 处理完成。如果此时Redis异常退出,会导致数据丢失可能远超过1s。

AOF优缺点比较

Redis中存在的两大阻塞:fork阻塞和AOF追加阻塞_u014753478的博客-CSDN博客

RDB 做快照的时候数据能修改吗

save 是同步的会阻塞客户端命令,bgsave 的时候是可以修改的

bgsave时可以修改数据, 主要是利用 bgsave 的子线程实现的,具体操作如下:

  • 如果主线程执行读操作,则主线程和 bgsave 子进程互相不影响;
  • 如果主线程执行写操作,则被修改的数据会复制一份副本,然后 bgsave 子进程会把该副本数据写入 RDB 文件,在这个过程中,主线程仍然可以直接修改原来的数据。

RDB-AOF混合持久化

Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble yes 开启)。

Redis 5.0 则默认开启

如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头

链接:

https://www.jianshu.com/p/65765dd10671

https://www.cnblogs.com/wdliu/p/9377278.html

单线程模型

Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于Redis是单线程来处理命令的,所有每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。并且多个客户端发送的命令的执行顺序是不确定的。但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型。

一次请求的执行步骤

redis分客户端和服务端,一次完整的redis请求事件有多个阶段(客户端到服务器的网络连接==>redis读写事件发生==>redis服务端的数据处理(单线程)==>数据返回)。平时所说的redis单线程模型,本质上指的是服务端的数据处理阶段,不牵扯网络连接和数据返回,这是理解redis单线程的第一步。接下来,针对不同阶段分别阐述个人的一些理解。

1:客户端到服务器的网络连接

首先,客户端和服务器是socket通信方式,socket服务端监听可同时接受多个客户端请求,这点很重要,如果不理解可先记住。注意这里可以理解为本质上与redis无关,这里仅仅做网络连接,或者可以理解为,为redis服务端提供网络交互api。

​ 假设建立网络连接需要30秒(为了更容易理解,所以时间上扩大了N倍)

2:redis读写事件发生并向服务端发送请求数据

​ 首先确定一点,redis的客户端与服务器端通信是基于TCP连接,第一阶段仅仅是建立了客户端到服务器的网络连接,然后才是发生第二阶段的读写事件。

​ 完成了上一个阶段的网络连接,redis客户端开始真正向服务器发起读写事件,假设是set(写)事件,此时redis客户端开始向建立的网络流中送数据,服务端可以理解为给每一个网络连接创建一个线程同时接收客户端的请求数据。

​ 假设从客户端发数据,到服务端接收完数据需要10秒。

3:redis服务端的数据处理

​ 服务端完成了第二阶段的数据接收,接下来开始依据接收到的数据做逻辑处理,然后得到处理后的数据。数据处理可以理解为一次方法调用,带参调用方法,最终得到方法返回值。不要想复杂,重在理解流程。

​ 假设redis服务端处理数据需要0.1秒

4:数据返回

​ 这一阶段很简单,当reids服务端数据处理完后 就会立即返回处理后的数据,没什么特别需要强调的。

​ 假设服务端把处理后的数据回送给客户端需要5秒。

redis 单线程的理解 - myseries - 博客园 (cnblogs.com)

运行方式

前面提到 redis是通过socket的方式监听多个客户端的, 每客户端和服务端都会共同经历 建立连接-请求数据-处理数据-返回数据 这几个阶段,

如果有几个socket同时发起请求, 岂不是得挨个等待? 其实不然

redis会基于这些建立的连接去探测哪个连接已经接收完了客户端的请求数据(注意:不是探测哪个连接建立好了,而是探测哪个接收完了请求数据),而且这里的探测动作就是单线程的开始,一旦探测到则基于接收到的数据开始数据处理阶段,然后返回数据,再继续探测下一个已经接收完请求数据的网络连接。注意,从探测到数据处理再到数据返回,全程单线程。这应该就是所谓的redis单线程

redis 单线程的理解 - myseries - 博客园 (cnblogs.com)

IO的多路复用

“多路”指的是多个网络连接,“复用”指的是复用同一个线程。

redis运行方式 段落中, 说到redis会去探测哪个链接已经接收完数据了, 这个探测就是那个线程

img

redis 单线程的理解 - myseries - 博客园 (cnblogs.com)

一篇超详细的文章, 日后再好好消化 彻底搞懂IO多路复用 (qq.com)