第七章 Redis探秘
7.1 数据结构
-
通常以“命名空间:业务key”的方式作为Redis的key值,如 “article:12563”,类似关系型数据库article表中主键未12563的数据。
-
value值的类型包括:string、list、set、map、sorted-set
-
type表示结构化类型,string、list等
-
encoding表示结构化类型具体实现方式,string可以是int、char[];list可以是ziplist等
-
lru即可被lru清理时长
-
refcount为引用计数,用于垃圾回收
-
ptr指向引用地址
-
ziplist
-
ziplist的数据结构:zlbytes表示本ziplist的总长度;zltail指向最末元素;zllen表示元素个数;后续每个<entry>即元素自身内容;zlend恒未0xFF作为定界符
-
ziplist中的元素是连续存放的;适合存储list的元素个数不多且元素本身长度不大的情况
-
-
Map
-
map内部的key和value不能在嵌套使用map,只能是String所能表达的类型
-
hashtable扩容:
-
rehashindex标记已完成迁移的桶,迁移时分为源表和目标表,已完成迁移的访问目标表,未完成迁移的访问源表。
-
redis为单线程处理请求,所以迁移过程不存在并发问题。
-
-
-
Set
-
set中的数据以intset和hashtable来存储:hashtable中的value为null;若数据都会int,则用intset,且intset从小到大排序,所以使用二分法查找
-
-
Sorted-set
-
有序的kev-value对
-
value为浮点数,称为score,按score排序
-
基本操作:ZRAND、ZRANGE、ZRANGEBYSCORE、ZSCORE、ZINCRBY
-
采用跳表skiplist实现这种数据结构
-
7.2 客户端与服务器的交互
-
Redis交互协议分为:
-
网络模型:
-
序列化协议:通过数据的首字符区分不同的协议类型
-
inline command:首字符为字母,代表命令,如EXISTS key1,首字符E表示Redis检查key1是否存在这个命令。
-
simple string:首字符为+,手续字符就为string的内容,以\r\n结束,所以不能包含\r\n。
-
bulk string:收字符为$,之后跟着数字表示string内容长度,以\r\n结束;数字0表示空字符、数字-1表示null字符。
-
error:与simple string相同,但是以-开头。
-
integer:以字符 :开头,后紧跟数字
-
array:以*开通,而后为长度+\r\n+数组元素+\r\n,如*2\r\n+abc\r\n:9\r\n这个13个字节表示["abc",9]
-
-
-
请求/响应模式:服务器对于客户端的每个请求返回一个响应数据,可以使用pipeline实现请求的批处理,而不用串行发生请求。
-
事务模式:MULTI+EXEC命令实现事务
-
MULTI命令开启事物模式
-
接下来的命令都放到暂存到服务端连接的队列上
-
EXEC为批量处理命令(Redis执行一次的粒度是命令)
-
非一致性:入队阶段出错,不执行EXEC;执行阶段出错,不回滚,在返回的array数据中标记出错结果。
-
入队操作应都为写操作,读操作是没有意义的,因为每次的写操作值应在入队前就确认,而不依赖批处理命令中的上一个结果。
-
-
串行事务的隔离:
-
问题:
-
解决:
-
引入WATCH,观察本次事务涉及到的所有key,此时时间为tstart
-
tcommit为批处理命令EXEC的执行时间
-
在sstart~tcommit之前有相关key值被修改,拒绝执行EXEC
-
-
-
脚本模型:Redis允许客户端以脚本的模式在服务端嵌入逻辑。
-
发布/订阅模式
7.3 单机处理逻辑
-
单线程+IO多路复用
-
evport->epoll->kqueue->select效率从高到低
-
fd(文件描述符、socket的句柄)的处理逻辑有3种实现
-
acceptTcpHandle:处理建立连接的请求
-
readQueryFromClient:处理来自客户端的数据
-
sendReplyToClient:将暂存的执行结果写回客户端
-
aeApiPoll的等待时间取决于定时任务。如下图所示,以确保单线程情况下能执行定时任务
-
-
定时任务(processEvent),默认情况下Redis只有一个周期性定时任务serverCon,负责:
-
7.4 持久化
-
全量模式
-
两种写入方式:SAVE和BGSAVE
-
两种方式都可以由客户端通过命令显示出发
-
SAVE在当前线程执行,期间不能提供数据读写服务
-
BGSAVE为子线程执行,期间可以提供服务,代价在于子进程复制父进程内存,在内存不足时,会造成秒级的不可用
-
-
-
增量模式
-
仅对数据的变化进行存储
-
Redis的AOF(append-only file,增量持久话)有3中同步策略
-
always:在每个迭代中通过flushAppendOnlyFile函数直接触发fsync()是数据落地磁盘
-
every second:每秒异步的触发一次fsync方法。flushAppendOnlyFile函数只是作为生产着将fsync job放入bio_jobs队列中,由bio线程消费。当磁盘吞吐量小于Redis服务器的写服务的吞吐量时,会造成bio_jobs中任务积压,所以当bio线程在执行时,阻塞任务入队。
-
-
增量模式的优化(结合快照snapshot)
-
当AOF过多超过一个全量快照时,将AOF合并为一个快照
-
快照写入期间的增量在写入完快照后append在快照末尾
-
后续增量写入新的AOF
-
-
第八章 分布式Redis
8.1 水平拆分
-
数据分布:hash映射、范围映射、hash映射+范围映射
8.2 主备复制
-
slave主动发起同步流程
-
当有多个slave并发发起同步请求时,在master执行BGSAVE前的请求会收到同一份快照。
-
断点续传:PSYNC,主从都维护一个offset记录当前已同步过的命令。
8.3 故障转移(failover)
-
sentinel集群用于解决故障发现、failover决策协商机制等问题。
-
sentinel集群中的所有监视相同master的节点两两连接
-
故障发现:当认为matser不可用的sentinel节点数大于某个阈值(可配置),则进入failover流程
-
failover决策:通过类型Raft协议实现选举发起failvoer决策的sentinel节点。
-
8.4 Redis Cluter
-
Redis3.0,提供了去中心化的方案,Redis Cluter。将proxy/senetinel的工作都融合到了普通的Redis节点里。
-
A和A1,B和B1、B2,构成两个节点组
-
1、2、3、4、5为一个数据全集分成的5个slot
-
配置一致性:
-
所有节点通过gossip协议向其他节点发送自身观察到的其他节点的信息的PING包
-
所有节点收到其他节点的PONG包,并更新自身关于其他节点的信息
-
由于Redis Cluster大多数时候是稳定的,所以PING包中只有随机选取的部分节点信息
-
各节点通过比较消息和自身的epoch、currentEpoch值,确认是否更新节点信息
-
-
客户端路由:ask和move
-
ask只是本条操作重定向到新节点,后续的相同slot操作仍路由到旧节点
-
move会更新client数据分布
-
由于迁移过程可能持续一段时间,所以应该将重定向和路由缓存更新分离,即在迁移完成后返回move,迁移中返回ask
-
-
分片迁移:
-
A节点在MGRATING状态下:
-
如果客户端访问尚未迁出的key,则正常访问
-
如过key已迁出或根本不存在,则回复ASK使其跳转到B执行
-
-
B节点在IMPORTING状态下
-
对于该slot上所有非ASK跳转操作,都不处理,返回MOVE命令让客户端跳转到A执行
-
-
-
slave选举:
-
在收到选票时,未发起选举,则同意选票
-
每个slave在发现master处于FAIL时,发起竞选
-
为了避免平票,高优先级的slave会更早的发起竞选,优先级根据最后一次同步master节点的时间判断
-