《深入分布式缓存:从原理到实践》学习笔记(2)

第七章 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表示元素个数;后续每个即元素自身内容;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节点的时间判断

你可能感兴趣的:(《深入分布式缓存:从原理到实践》学习笔记(2))