《redis设计与实现》读书笔记
一,复制
用户使用slaveof去复制一个服务器的时候,被复制的服务器称为主服务器,请求复制的称为从服务器。这两个服务器的数据状态一致,
这样有什么好处?对于插入,修改,删除来说要同时修改两个服务器的值,增加了消耗,但是对于get来说,就可以进行分流了。可以简介实现数据持久化,一个服务器挂了,另外一个还在。负载均衡:独写分离,主服务负责写,从服务器负责读。高可用基石:主从复制是哨兵和集群实施的基础。
1,旧版本复制功能
同步:将从的数据库状态更新到主的数据库状态,从向主发送SYNC命令完成,
执行步骤:发送sync,主收到,执行bgsave生成rdb,同时使用一个缓冲区来存放现在开始的所有 写 命令。将rdb发送给从,从载入这个rdb。缓冲区的命令也发送给从,从进行执行。
命令传播:同步设置完毕之后,主服务器收到的命令,要同时发送给从服务器,从服务器也要执行。
2,旧版复制功能缺陷:初次复制,没什么问题,断线后重复制(本来主从是正常连接的,但是各种问题断开了,然后又重新连上),有很大的效率问题,问题出现在:旧版本的重连就如同第一次连接一样,重新赋值rdb文件,这是很耗时间的!(意味着新版本在这里进行了升级优化)
3,新版复制功能的实现:从2.8开始,使用psync命令代替sync命令,psync具有完整同步和部分同步两种模式,自认第二次复制就变成了部分复制了,
(感悟,就如同mysql的事务,要理解为什么这样,就需要理解业务上存放的问题,逐步升级到达今天这个方案,这个思想也可以借鉴到其他业务的处理,所以业务是驱动技术的发展,技术又去实现业务,不能单独的去学习一个技术,体会不到这个技术的精髓,就无法良好的使用,毕竟我们的技术学习是用来使用的)
4,部分重同步的实现:两个服务器之间的同步,又如同内存与磁盘库的持久化,又如同mysql中内存页与磁盘页的一致性redo。思想都差不多,总之就是想办法尽量快。
-
复制偏移量:主服务器向从服务传播N个字节时,就将自己的偏移量加上N,从服务器也是如此,就如同mysql中redo中的LSN.
-
复制积压缓冲区:主服务器维护的一个固定长度先进先出的队列,默认大小1MB(redo file 50M),那么如果出现偏移量偏差,主服务器就会找从服务器的偏移量点,如果存在就会读取发送给从服务器。buffer是可以调整的,
-
服务器运行ID:从需要主的运行唯一ID,当连接请求带了Id,说明不是第一次,没有带对应id,说明是第一次连接。(可能集群中有多个主服务器,然后上一个主连接断了,现在连接另外一个)
5,复制的实现
-
设置主服务器的地址和端口
-
建立套接字连接
-
发送PING命令
-
身份验证
-
发送端口信息
-
同步
-
命令传播
7,心态检测:在命令传播阶段,从服务器会每秒一次的频率,向主服务发送命令。有以下三个任务:
-
检测主服务器的网络连接状态
-
辅助实现min-slaves配置选项
-
检测命令丢失
二,Sentinel(哨兵)
Redis高可用解决方案:又一个或者多个哨兵组成哨兵系统,监控服务系统,当主服务器下线,自动将其下的从服务器升级为主服务器(这里感觉哨兵就是一个管理者,但是权限不高)
1.启动并初始化Sentinel
-
初始化服务器:Sentinel本质还是一个redis服务器,Sentinel的启动就不需要加载持久化数据了,简化得多,
-
使用Sentinel专用代码:将redis服务器启动的代码替换为Sentinel专用代码,(命令表中的命令都不相同了)
-
初始化Sentinel状态:sentinelState结构(称为X状态),保存了服务器中所有和Sentinel相关的状态
-
初始化Sentinel中的masters属性:它是一个字典,记录了所有被监视的主服务器信息,键是服务器名字,值是sentinelRedisInstance实例
-
创建练习主服务器的网络连接:Sentinel作为主的客户端,发送命令,而且有两个连接,一个是命令连接,另外一个是订阅连接
2.获取主服务器信息
Sentinel默认是十秒一次发送INFO命令,通过分析回复获得主的信息,可以获得主的运行id,服务器角色,还有其下面的从服务器信息,ip,port等。
Sentinel对主的实例结构进行更新,根据返回的从信息,更新,如果发现sentinel没有保存,那么就是一个新的从服务器,需要建立连接(还是两个),实例同样使用sentinelRedisInstance,但是标志位属性进行了区分:master和slave。
3.获取从服务器信息:与从建立连接,实例,回复里面包含:从Id,角色,主从连接状态,优先级,复制偏移量等等。
4.接收来自主和从的信息:Sentinel通过命令连接发送信息(2秒发一次,自己+目标服务器的基本信息),通过订阅连接接收信息,它的作用是进行sentinel之间的通信,因为一个服务器可能不止一个监视器,
-
更新sentinels字典:它保存了其他sentinel监视这个服务器的信息。如ip,端口,运行id等。
-
创建连向其他Sentinel的命令连接:哨兵之间也会通过发现,然后进行实例保存,连接。只有命令连接
5.检测主管下线状态:每秒一次向所有命令连接发送ping,毫秒内没有回应,则要修改实例结构,标识下线。
6.检测客观下线状态:当本哨兵检测主服务器下线,会询问同时检测这个主的其他哨兵,是否判断下线,
7.选举领土Sentinel:主客观下线之后,监视它的哨兵会协商,选举出领头哨兵,让它来进行故障转移。
8.故障转移:挑选一个从服务器,转为主服务器,更改其他从服务器的主目标。将下线的主设置为当前主的从,等待它上线。
三,集群
Redis集群是Redis提供的分布式数据库方案。
1.节点
最开始各个节点都是独立的,要进行连接,使用cluster meet命令,+ ip +端口进行。A,B,C三个节点,A,向B发送,AB集群成立,A向C发送,ABC集群成立(这里就没有B-C的过程)
1.启动节点:判断配置文件中,是否开启了集群。开启了,就把自己设为一个节点,没有就是单机模式。
2.集群数据结构:clusterNode,保存一个点的当前状态,创建时间,名字,ip,端口号等等。它不仅记录集群中其他节点,还记录了自己。里面存在一个clusterlink结构,它和redisClient一个概念,是连接状态。还有一个cluseterState结构,保存了当前节点视角任务的集群状态。
3.cluster meet命令的实现:A命令发送,B接收,握手,A创建一个clusterNode,添加到clusterState的nodes里面,然后发送一条meet信息,B收到,B创建一个XXNode,添加到nodes中。然后再ping-pong一下,搞定。A节点把B的信息传播给集群中其他节点。
2.槽指派
集群通过分片来处理数据库中键值对,将整个数据库分为16384个槽slot,每个键都是其中的一个,当数据库中的每个槽都有存在节点处理的时候,集群上线成功,反之失败。节点连接建立,例如A,B,C,下一步就需要给这三个节点分配槽点,每个节点
-
记录节点中的槽指派信息:clusterNode中的属性char slots[],二进制位数组,包含16394个二进制位(一个字节占8位),这里就是位图法。为1,表示该节点复制该槽点。
-
传播节点的槽指派信息:将自己复制的槽点发送给集群中其他节点。接收方在nodes中找到发送方的节点状态,然后对里面的属性slots[]进行更新。
-
记录集群所有槽的指派信息:clusterState中存在clusterNode *slots[16384]这个就非常大了,每一个都指向一个节点,为什么不存放id号呢。。奥,这里存放指针,可以进行指针的复用(因为nodes已经存在了实例了),为什么有了这个总的表还需要slots[],因为存放的指针根本没办法进行发送传递,存的值才能进行发送。
-
cluster addslots命令的实现:这个命令接收一个or多个槽位作为参数,
3.在集群中执行命令:现在集群上线成功,可以发送命令了。当客户端发送命令,有一个节点负责接收,然后计算键的位置,把命令产给对应的节点。集群只能使用0号数据库(键值对装满了怎么办?)
4.重新分片:将已经分配的槽指定给其他的节点,它可以在线进行,比如新增一个节点,那么就需要从槽点里面分一部分出来给新节点。
5.ASK错误:
6.复制与故障转移:分为主节点和从节点,主节点用来处理槽,从节点备份,在主下线的时候顶上。
7.消息:节点直接的通信,有五种类型的信息,消息头由clusterMsg结构表示