第三章 Redis相关知识
一、Redis的优点
- 数据类型丰富。
- 支持数据磁盘持久化存储。
- 支持主从。
- 支持分片。
二、为什么Redis能这么快?
- Redis的使用能达到100000+QPS(QPS即Query Per Second,每秒内查询次数)。
- 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高。
- 数据结构简单,对数据操作也简单。
- 采用单线程,单线程也能处理高并发请求,可以通过启动多实例实现多核操作,Redis使用单线程的原因是,省去了很多上下文切换线程的时间(多线程切换上下文的时间远远超过了读取数据所使用的时间)。
- 使用多路I/O服用模型,非阻塞I/O 。
三、常用的Redis存储结构
1. 字符串(String)
常用语法:
set key value [expiration EX seconds|PX milliseconds] [NX|XX]
get key
127.0.0.1:6379>SET name "zhangsan"
OK
127.0.0.1:6379>GET "name"
"zhangsan"
2. 哈希(Hash)
- Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
- Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)。
常用语法:
hmset key field value [field value ...]
hgetall key
举个栗子:
127.0.0.1:6379> HMSET student name "zhangsan" age 11 sex 1
OK
127.0.0.1:6379> HGETALL student
1) "name"
2) "zhangsan"
3) "age"
4) "11"
5) "sex"
6) "1"
3. 列表(List)
- Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
- 一个列表最多可以包含 2^32 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
常用命令:
lpush key value [value ...]
lrange key start stop
举个栗子:
127.0.0.1:6379> LPUSH mate zhangsan
(integer) 1
127.0.0.1:6379> LPUSH mate lisi
(integer) 2
127.0.0.1:6379> LPUSH mate wangwu
(integer) 3
127.0.0.1:6379> LRANGE mate 0 2
1) "wangwu"
2) "lisi"
3) "zhangsan"
4. 集合(Set)
- Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
- Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
- 集合中最大的成员数为 2^32 - 1(4294967295, 每个集合可存储40多亿个成员)。
常用命令:
sadd key member [member ...]
smembers key
举个栗子:
127.0.0.1:6379> sadd color red
(integer) 1
127.0.0.1:6379> sadd color blue
(integer) 1
127.0.0.1:6379> sadd color white
(integer) 1
127.0.0.1:6379> sadd color yellow grew black
(integer) 3
127.0.0.1:6379> smembers color
1) "blue"
2) "white"
3) "red"
4) "grew"
5) "black"
6) "yellow"
4. 有序集合(Sorted Set)
- Redis 有序集合和集合一样也是 String 类型元素的集合,且不允许重复的成员
- 不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
- 有序集合的成员是唯一的,但分数(score)却可以重复。
- 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 2^32 - 1(4294967295, 每个集合可存储40多亿个成员)。
常用命令:
zadd key [NX|XX] [CH] [INCR] score member [score member ...]
zrange key start stop [WITHSCORES]
举个栗子:
127.0.0.1:6379> zadd food 1 apple
(integer) 1
127.0.0.1:6379> zadd food 2 apple
(integer) 0 #未添加新成员,apple对应的分数改为2
127.0.0.1:6379> zadd food 1 banana
(integer) 1
127.0.0.1:6379> zadd food 2 banana
(integer) 0 #添未添加新成员,banana对应的分数改为2
127.0.0.1:6379> zadd food 3 banana
(integer) 0 #添未添加新成员,banana对应的分数改为3
127.0.0.1:6379> zadd food 3 peach
(integer) 1
127.0.0.1:6379> zadd food 2 fish
(integer) 1
127.0.0.1:6379> zrange food 0 3
1) "apple"
2) "fish"
3) "banana"
4) "peach"
127.0.0.1:6379> zrange food 0 3 withscores
1) "apple"
2) "2"
3) "fish"
4) "2"
5) "banana"
6) "3"
7) "peach"
8) "3"
四、从海量的KEY中查询出某一固定前缀的KEY
- 需要先确定数据规模的范围,根据数据范围的边界确认解决思路。
五、如何通过Redis实现分布式锁
1. 分布式锁需要解决的问题
- 互斥性:即任意时刻,只能有一个客户端获取锁,不能有多个客户端获取锁;
- 安全性:锁只能由持有该客户端所删除,不能被其他客户端删除;
- 死锁:客户端因宕机等原因无法正常释放锁而产生的死锁,需要对应的设定机制避免死锁的产生;
- 容错:当部分节点宕机时,客户端仍能获取锁。
2. 分布式锁的实现方法
- 通过使用
SETNX
实现分布式锁,(SETNX设置成功,返回 1 。 设置失败,返回 0)SETNX
具有原子性,使用SETNX
对某个KEY
设值,如果设值成功,则没有其他线程使用该KEY
;反之,设值失败,则说明此时有其他线程或程序正在占用该KEY
。
常用命令:
setnx key value
get key
举个栗子:
127.0.0.1:6379> exists cat
(integer) 0
127.0.0.1:6379> setnx cat tom
(integer) 1
127.0.0.1:6379> setnx cat tommy
(integer) 0
127.0.0.1:6379> get cat
"tom"
- 为解决
SETNX
长期有效的问题,引入了EXPIRE
,来为SETNX
设置生存时间。
常用命令:
expire key seconds
举个栗子:
127.0.0.1:6379> get cat
"tom"
127.0.0.1:6379> expire cat 10
(integer) 1
127.0.0.1:6379> setnx cat tommy
(integer) 0
127.0.0.1:6379> setnx cat tommy
(integer) 1
127.0.0.1:6379> get cat
"tommy"
- 使用
EXPIRE
,但同时也存在着问题,如果在执行EXPIRE
前,SETNX
就已经完成了相应的操作,EXPIRE
就会一直占用资源,导致无法释放。
- 通过使用
SETNX
实现分布式锁(set key value [expiration EX seconds|PX milliseconds] [NX|XX]
)
(1)EX second :设置键的过期时间second秒;
(2)PX millisecond:设置键的过期时间喂millisecond毫秒;
(3)NX:只在键不存在是,才对键进行设置操作;
(4)XX:只在键已经存在时,才对键进行设置操作;
(5)SET操作成功时,返回OK,否则返回nil。
举个栗子:
127.0.0.1:6379> set rat Jerry ex 10 nx
OK
127.0.0.1:6379> set rat Mickey ex 10 nx
(nil)
127.0.0.1:6379> get rat
"Jerry"
127.0.0.1:6379> get rat
(nil)
3. 当大量的KEY同时过期时,应注意的问题
- 当存在集中过期的情况时,由于消除大量的KEY会耗时很长,导致出现卡顿的情况发生
- 解决方案:在设置KEY的过期时间时,使用随机值,使得过期时间分散,从而解决同时消除大量KEY所产生的的卡顿现象。
六、如何使用Redis实现异步队列
1. 使用List作为队列,RPUSH生产消息,LPOP消费消息
- 缺点:LPOP不会去等待队列中的值。而是直接消费;
- 改进:可以在应用层中引入SLEEP机制去重新调用LPOP重试。
如果不使用SLEEP,怎么实现异步队列?
2. 使用BLPOP,实现异步队列
- 缺点只能供一个消费者使用
常用命令:
blpop key [key ...] timeout
举个栗子:
客户端1
127.0.0.1:6379> blpop bear 30
客户端2:
127.0.0.1:6379> lpush bear panda
(integer) 1
客户端2完成操作后,客户端1的变化:
127.0.0.1:6379> blpop bear 30
1) "bear"
2) "panda"
(27.55s)
3. 使用pub/sub,主题订阅者模式
- 消息的发布是无状态的,无法保证正常到达(客户端下线期间无法正常获取消息)
举个栗子:
客户端1:(创建主题topic1)
127.0.0.1:6379> SUBSCRIBE topic1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "topic1"
3) (integer) 1
客户端2:
127.0.0.1:6379> SUBSCRIBE topic1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "topic1"
3) (integer) 1
客户端3:(发送内容)
127.0.0.1:6379> PUBLISH topic1 "hello"
(integer) 2
此时,客户端1 、2显示新的内容:
1) "message"
2) "topic1"
3) "hello"
六、Redis如何做到持久化?
1. RDB(快照)持久化:保存某个时间点的全量数据快照
1.1 RDB持久化的相关配置
由于我使用的redis是安装在windows环境,所以打开:
D:\Software\Redis-x64-5.0.10
下的redis.windows.conf
文件
找到对应的配置:
save 900 1 # 在900秒以内,产生1次写入,就进行一次快照
save 300 10 # 在300秒以内,产生10次写入,就进行一次快照
save 60 10000 # 在60秒以内,产生10000次写入,就进行一次快照
save "" # 禁用rdb的配置
# 设置为yes时,表示当备份进程出错的时候,主进程就不再进行写入的操作了
stop-writes-on-bgsave-error yes
rdbcompression yes
1.2 SAVE:(使用较少)阻塞Redis的服务器进程,知道RDB文件被创建完毕
127.0.0.1:6379> save # 重新生成dump.rdb文件
OK
127.0.0.1:6379> lastsave
(integer) 1618413018
1.3 BGSAVE:Fork出一个子进程来创建RDB文件,不阻塞服务器的进程
127.0.0.1:6379> bgsave
Background saving started
127.0.0.1:6379> lastsave
(integer) 1618413131
1.4 自动化出发RDB持久化的方式
- 根据redis.conf配置里的
SAVE m n
定时出发(使用的是BGSAVE) - 主从复制时,主节点自动触发
- 执行
Debug Reload
命令 - 执行
Shutdown
且没有开启AOF持久化时
1.5 RDS持久化的缺点
- 内存数据的全量同步,数据量大会由于I/O而严重影响性能
- 可能会因为Redis挂掉而丢失从当前至最近一次快照期间的数据
2. AOF(Append-Only-File)持久化:通过保存写命令
2.1 AOF持久化的方式
- 记录下除了查询意外的所有变更数据状态的指令
- 以append的形式追加保存到AOF文件中(增量)
2.2 AOF持久化的相关配置
打开:
D:\Software\Redis-x64-5.0.10
下的redis.windows.conf
文件
找到对应的配置:
appendonly no # 默认状态为 关闭状态
appendfilename "appendonly.aof" # 生成AOF文件的文件名
# appendfsync always # 缓存发生变化,就执行持久化操作
appendfsync everysec # 默认状态,每隔一秒
# appendfsync no # 交由操作系统决定(缓存区填满)
2.3 AOF持久化的流程
- 调用Fork(),创建一个子进程
- 子进程吧新的AOF写入一个临时文件中,不依赖原来的AOF文件
- 主进程持续将新的变动同步写到内存和原来的AOF中
- 主进程获取子进程重写AOF完成的信号,将辛的AOF同步增量变动
- 使用新的AOF文件代替旧的AOF文件
3. RDB和AOF文件共存的情况下的恢复流程
4. RDB和AOF的优缺点
优缺点 | RDB | AOF |
---|---|---|
优点 | 全量数据快照,文件小,恢复快 | 可读性高,适合保存增量数据,数据不易丢失 |
缺点 | 无法保存最近一次快照之后的数据 | 文件体积大,恢复时间长 |
5. RDB-AOF混合持久化方式(较为推荐)
- BGSAVE做镜像全量持久化(BGASVE会花费较长时间,停机时会导致大量数据丢失),AOF做增量持久化(配合使用,存放近期的操作指令)
七、Redis的集群原理
1. 如何从海量的数据中快速找到所需的数据
- 分片:按照某种规则去划分数据,分散存储在多个节点上
- 常规的按照哈希划分无法实现节点的动态增减
2. 一致性哈希算法
对2^32取模,将哈希值空间组织成虚拟的圆环