1 数据类型与基本类型
数据类型 | 底层数据 | 特性 | 示例 |
---|---|---|---|
String | SDS | set key vale | |
List | 快速列表(双向压缩列表) | rpush list-key value | |
Set | 整数集合 | 不重复 | sadd set-key item |
Hash | 字典等 | hset hash-key key value | |
ZSet | 调表 | 有序 | zadd zset-key score item |
- String:对整个字符串或者字符串的其中一部分执行操作对整数和浮点数执行自增或者自减操作
- List:从两端压入或者弹出元素对单个或者多个元素进行修剪,只保留一个范围内的元素
- Set:添加、获取、移除单个元素检查一个元素是否存在于集合中;计算交集、并集、差集从集合里面随机获取元素
- Hash:添加、获取、移除单个键值对获取所有键值;对检查某个键是否存在
- ZSet:添加、获取、删除元素根据分值范围或者成员来获取元素计算一个键的排名
1.1 基本数据类型
1.1.1 Simple Dynamic String
SDS(简单动态字符串)最后一个字符仍旧是 \0
,目的是和C语言兼容。
String和SDS的区别:
- C语言API的二进制不安全,可能会导致缓存区溢出,只能保存文本数据;SDS安全可保存二进制文件。
- 前者可以使用所有
<string.h>
,后者只能使用一部分。 - 前者获取一个字符串长度的复杂度为
O(N)
,后者为O(1)
。 - 修改一个字符串前者需要
O(N)
,后者至多O(N)
。
1.1.2 链表
是消息订阅发布、监视器、慢查询等相关功能的基层实现。
1.1.3 压缩列表
当一个列表/哈希键的键元素比较少时,且列表/键元素为小整数或短字符串,就会用压缩链表来实现链表键的底层。
压缩列表:是一个顺序型的数据结构,采用一种特殊编码的连续内存空间。
1.1.4 快速列表
Redis 3.2该类型是列表的新型实现,底层是由压缩列表组成的双向列表。
由于使用了entry节点,相对于压缩列表可以存储更多的数据
1.1.5 字典
字典采用拉链法解决哈希冲突。
1.1.6 整数集合
整数集合也是集合键的一种实现方式,当一个集合只包含整数值集合时且数目不是太多时,它就会被采用。
类型转化:
- 按照高类型(如int32->int64)扩展并为新元素分配空间。
- 将所有底层数组转化为高类型并按从小到大的顺序依次放到正确的位置。
- 添加新的高类型元素。
1.1.7 跳表
- 跳表是有序链表的底层实现之一。
查询时,从上层指针开始扫描(使用前进指针进行遍历),找到指定的区间再去下一层查找:
相比于红黑树:
- 跳表插入速度块(不需要旋转变色)。
- 容易实现和无锁操作。
2 事务处理
2.1 一般流程
- 使用
MULTI
开启事务 - 除了
EXEC
、WATCH
、DISCARD
之外的命令(),都不会立即执行和返回结果,而是放到一个事务队列里。 DISCARD
将会放弃事务,并清空事务队列里的所有命令。EXEC
将会把事务队列里的命令按顺序执行,并将结果返回客户端。WATCH
命令是一个乐观锁,用来监视任一key是否被修改过。(使用UNWATCH
撤销)
- Redis采用版本号实现的乐观锁。
2.2 WATCH的监视机制
在执行 SET
等命令后都会堆 watched_keys
字典进行检查,如果发现键被监视,就会打开 REDIS_DIRTY_CAS
标志。
2.3 事务的性质
2.3.1 原子性
Redis事务可以一次执行多个命令,要么全部成功要么全部失败。
2.3.2 一致性
把数据库从数据库从一个状态变为另一个状态,数据库中的数据也保证一致性(符合数据本身的定义和要求、无脏数据等)。
2.3.3 隔离性
当同时进行多个事务时,每个用户的事务执行互不干扰。
2.3.4 持久性
当事务完成时,对数据库的改变是永久性的。
2.4 缓存相关(雪崩、击穿、渗透)
- 缓存击穿:缓存中没有但数据库中有的数据(即“热点数据”),当对其的请求并发量较大时瞬间引起数据库压力激增。解决方案:设置热点数据永不过期、对热点数据的数据库取值加互斥锁以防止重复进入数据库。
- 缓存雪崩:缓存数据大批量过期,而此时恰好有较高的查询请求。解决方案:缓存数据的过期时间加上随机扰动、将热点数据均匀分布在不同的节点、设置热点数据永不过期。
- 缓存穿透:对缓存和数据库中都不存在的数据不断发起请求。解决方案:增加鉴权校验、将
key-value
写为key-null
并给定一个较短的有效时间。
3. Redis持久化
3.1 AOF
- 默认情况下AOF没有被开启。
- 持久化方式:保存服务器执行的所有写操作命令到单独的日志文件。在后台,AOF文件会被重写以控制体积。
- 写入时,并不是直接写文件,而是协议给缓存区;当缓冲区达到阈值或达到指定周期时,统一将文件写入硬盘。
3.1.1 AOF文件重写
- 定****义:定期重写AOF文件以减小AOF文件的体积(创建一个新文件然后重写替换旧的文件、去除冗余)。
- 重写是通过读取数据库状态来实现的,而不是对旧的AOF文件进行读写。
- 重写过程中,新的请求不会再被写入旧的文件。
- 减小体积的方式:1.丢弃过期数据、2.丢弃无效命令(重复设置某个值)、3.多条命令合并为一条命令。
- 触发方式:手动触发(
BGREWRITEAOF
)和自动触发。 - 由于Redis使用单线程来处理命令请求,为避免阻塞,因此通过子进程来重写这个文件。由此导致的数据不一致问题,通过AOF文件重写缓冲区来解决——对于一个新的写请求,Redis除了写AOF缓冲区还会写重写缓冲区。
3.1.2 备份与恢复
- 创建一个无网络的伪客户端,读取AOF文件。
- 执行每一条AOF中记录的写命令。
3.1.3 优点与缺点
- +兼容性较好;
- +支持后台重写;
- -文件体积逐渐变大、性能逐渐变低;
- -恢复速度慢于RDB;
- -若AOF文件损坏可能导致恢复失败。
- -无法保存历史快照。
3.2 RDB【默认】
- 定义:在指定时间间隔内,生成数据集的时间点快照。
- 触发方****式:自动触发(被修改的键达到阈值或达到时间间隔)或手动触发(
BGSAVE
)。
3.2.1 快照过程
- 父进程调用
fork()
创建子进程,子进程持有父进程的数据。 - 父进程继续处理客户端的请求,子进程把内存中的数据写到硬盘上的一个临时RDB文件中。
- 通过写时复制策略保证在执行
fork()
过程中的两份内存数据副本不会加倍或前后数据差异过大。
写时复制:当父进程要修改某个数据时,将内存共享数据复制一份给子进程使用。
3.2.2 优点与缺点
- +经过压缩的二进制文件,适用于数据备份。
- +适用于灾难恢复,且更快。
- +性能优异(父子进程协同,互不干扰)。
- -可能会丢失大量数据。
- -当内存数据量比较大时,会占用CPU耗时。
- -兼容性问题。
4 Redis主从、哨兵与集群
4.1 主从复制
- 一个master可以有多个slave;一个slave只能有一个master。
- Redis 2.8采用异步复制,从节点会每隔1秒向主服务器报告复制流的处理进度。
- 复制过程中,并不会阻塞主服务器。
- 需要注意的是,当一个节点B变为A的从节点后,B之前保存的数据将会被清除以和A保持同步。
作用:
- 备份,防止节点宕机数据丢失。
- 读写分流,减轻服务器压力。
4.1.1 全量同步与部分同步
- 全量同步:用于处理第一次复制时的情况,即尽在第一次接入主节点时才会进行。通过主节点向从节点发送RDB文件的方式实现。
- 部分同步:当从节点离线重连后的情况。主节点向从节点发送它自离线以来新增的写命令。
- 通过一个运行ID来确认是否为第一次接入主节点;
- 通过一个复制偏移量来确认复制缓冲区中的数据;若存在则进行部分同步。
- 通过每秒一次的心跳机制确认主从服务器间的网络连接情况。
master宕机之后,slave会原地等待;slave宕机之后需要重新连接。
4.2 Redis读写分离
- 用处:用于将读流量分摊到每个节点。
- 具体形式:master进行写操作,slave进行读操作。
- 缺点:主从数据更新有延迟,导致读取的数据不一致,如读到过期数据等。
4.3 哨兵模式
- 定义:由一个或多个哨兵组成的哨兵系统,监控任意多台服务器是否发生故障;当主节点发生故障时将一个从节点选为新主节点。
- 意义:故障转移、高可用、热部署。
哨兵本身并不存放数据,但可以同时监控多套主从复制的集群。
通过订阅主节点的 _sentinel_:hello
频道,查找新的哨兵、主服务器的信息。
- 工作机制:当多个哨兵认为一个master有问题,就从内部选出一个哨兵领导,它会从所有从服务器中选出一个成为新的master、其它slave进行认主。随后,哨兵通知客户端新的master所在位置,并持续监视旧的master(当其复活后将变为新master的slave)。
4.4 Redis集群
- 定义:不存在中心节点或代理节点,通过主从模式完成它的容错性,主要用于解决高并发和大数据量的问题。
不支持需要同时处理多个键的命令,因为需要再多个Redis间移动数据,会使整体性能降低和不稳定。
4.4.1 节点
集群模式下,每一台Redis服务器都是一个节点。
4.4.2 槽
- Redis为了能够存储大量的数据信息,采用分片的方式将大量数据保存在数据库中,每个数据库被划分为16384个槽。
- 每个槽映射一个大数据集,通过哈希来确认某个值的槽归属。
- 某条命令操作的数据符合自己的槽归属,才会进行相应操作。