不积跬步,无以至千里

Redis


Hash

引用百度百科的解释:就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值 常用HASH函数:

  • 直接取余法:f(x):= x mod maxM ; maxM一般是不太接近 2^t 的一个质数
  • 乘法取整法:f(x):=trunc((x/maxX)*maxlongit) mod maxM,主要用于实数
  • 平方取中法:f(x):=(x*x div 1000 ) mod 1000000); 平方后取中间的,每位包含信息比较多

Redis的基本数据类型

字符串

  1. 缓冲区剩余长度
  2. 字符串长度
  3. 字节数组(char类型的数组)

通过惰性释放,避免频繁分配内存空间

链表

链表

  1. 表头节点
  2. 表尾节点
  3. 链表包含的节点数量
  4. 节点值的复制函数
  5. 节点值的释放函数
  6. 节点值的对比函数

节点

  1. 前置节点
  2. 后置节点
  3. 节点的值

字典

用于保存键值对

底层通过哈希表实现,一个哈希表中可以有多个哈希表节点,每个节点保存一个键值对

字典

  1. 类型特定函数数组指针(每个特定函数类型保存用于操作特定类型键值对的函数)
  2. 私有数据(保存需要传给特定类型键值对的可选参数)
  3. 哈希表(size为2,一般只用第0个,当需要做rehash的时候会用到第1个)
  4. rehash索引

哈希表

  1. 哈希表节点数组
  2. 哈希表大小
  3. 哈希表大小的掩码(用来计算索引值)
  4. 该哈希表已有节点的数量

哈希表节点

  1. 下一个哈希表节点(连接key的hash值相同的节点,解决键冲突问题)

什么时候做rehash

  1. 服务器没有执行BGSAVEBGREWRITEAOF,并且哈希表的负载银子大于等于1
  2. 服务器正在执行BGSAVEBGREWRITEAOF,并且哈希表的负载因子大于等于5

以上两个条件中的任意一个被满足,都会做rehash。

在做rehash的时候,可以采用渐进式rehash,也就是对key和value的删除、查找、更新操作会同时在两个hash表中进行,但不会再对第0个哈希表做添加操作,直到第0个哈希表最终变为空表。

跳跃表

通过在每个节点中维持多个指向其他节点的指针,达到快速方位节点的目的。Redis通过跳跃表作为有序集合的底层实现之一。

跳跃表

  1. 指向跳跃表的表头节点
  2. 指向跳跃表的表尾节点
  3. 目前跳跃表内,层数最大的那个节点的层数
  4. 跳跃表的长度,也就是目前跳跃表包含节点的数量

###跳跃表节点

整数集合

保存整数集合,且集合中不包含重复数据

只支持升级,不支持降级

压缩列表

列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么是小整数值,要么是长度比较短的字符串,那么Redis会使用压缩列表来做列表键的底层实现。

对象

对过期键的处理

当服务器运行在复制模式下时,对过期键的删除由主服务器来控制,当主服务器删除一个过期键时,会向从服务器发送DEL命令,对从服务器的过期键做删除操作;而从服务器不会主动删除过期键。

删除的策略有几种:

  1. 定时扫描
  2. 查询时做检查
  3. 设置定时器,在键过期的时候对键做删除操作

持久化

RDB

通过保存数据库中的键值对来记录数据库的状态变化

在执行SAVE或BGSAVE命令后,Redis服务器会创建RDB文件,对数据库中发生的状态改变做持久化备份操作。SAVE命令由主线程操作,所以会对进程造成阻塞;而BGSAVE则会在子线程中做备份操作。

对于RDB持久化方式来说,在执行SAVE命令时,并不会包含已经过期的KEY

而在载入RDB文件时,会根据加载服务器的不同,存在处理方式上的不同:1. 如果是主服务器加载,那么已过期的KEY不会被加载。2. 如果是从服务器加载,不会检查KEY是否过期,在从主服务器同步数据到从服务器时,这些过期的KEY会被删除掉。

对于AOF文件加载,需要等待惰性删除或定期删除后,程序会在AOF文件后追加一条DEL命令。

Redis可以在启动时,设置自动保存参数,使得Redis在一定时间内执行指定次修改后,自动通过BGSAVE命令将内存中的修改保存在RDB文件中,为了达到自动保存的目的,Redis维护了dirty计数器和lastsave属性,分别表示自上一次执行SAVE或BGSAVE命令后,服务器对数据库状态做了多少次修改,以及上次执行SAVE、BGSAVE命令的时间。设置好后,Redis服务器会定期检查是否满足自动保存的条件,如果满足的话,执行BGSAVE命令做备份。

AOF

通过记录数据库的执行命令来记录数据库的状态变化

AOF文件重写

AOF文件保存的是执行的命令,不可避免的,其中会包含很多重复的命令;这会造成文件体积较大,不利于存储,恢复起来也会变慢,为了减少冗余的命令,需要对AOF文件进行重写。重写是通过命令BGREWIRTEAOF操作。操作过程是从数据库中当前状态生成最新的一份AOF文件,并替换掉旧文件,所以新生成的AOF文件中,只包含恢复当前数据库状态的必要的命令。

为了解决在重新生成过程中,对数据库操作造成的数据不一致问题,Redis服务器设置了AOF重写缓冲区,在新的AOF文件生成完毕后,将AOF重写缓冲区中的内容追加到新的AOF文件中。

事件

Redis服务器是基于事件驱动的,其中的事件包含文件事件和事件事件。

由于Redis服务器通过套接字和客户端进行通信,文件事件可以看做是对套接字处理的一个抽象概念。当监听的套接字状态发生变化时,对应的文件事件被触发,对应的文件事件处理器就会做相应的处理。基于Reactor模式

一个服务器通常会连接多个套接字,通过I/O多路复用技术,可以同时接收文件事件,最终这些事件会被放入到一个队列中进行处理,从而保证了事件处理的有序性。

时间事件

时间事件有两类,一类是指定延时事件发生,一类是每隔固定时间发生。每个时间事件包含3个属性:时间事件ID,发生事件的时间戳,事件处理器函数。

时间事件被放入无序的列表,通过遍历获取当前需要执行的时间处理器函数。

客户端和服务器

客户端的属性包括:套接字描述符、名字、输入缓冲区和输出缓冲区、命令、存活事件、认证信息正在使用的数据库指针及号码

一个服务器会和多个客户端连接,在服务器中所有连接的client的状态信息被保存在一个链表中。

在Redis的服务器中,serverCron函数默认每隔100ms执行一次,负责管理服务器资源,并保持服务器自身的良好运转。

复制

从主服务器同步数据,使得主从服务器的数据保持一致。

复制功能包含两种方式,同步操作通过主服务器向从服务器发送RDB文件实现;而命令传播操作则通过主服务器向从服务器发送命令实现。

在Redis1.8之前,同步操作会同步所有的主服务器数据,在2.8之后添加了通过偏移量来同步的功能。也就是主服务器和从服务器分别保存当前同步数据的偏移量,若两者不一致,则只发送不一致的一部分数据;同时在主服务器中存在一个复制挤压缓冲区,这个缓冲区大小固定,并且遵循先进先出的策略,若不一致的数据偏移量在这个缓冲区,则从复制缓冲区中取出不一致的数据,并发送给从服务器,若不一致的数据不在这个复制缓冲区,则同步主服务器的全量数据。

另外由于Redis服务器会存在主从切换的情况,所以部分同步的时候需要对当前同步的主服务器ID做验证,如果不一致的话也是需要做全部数据复制同步的。

高可用

复制功能使得Redis服务器保证了数据的一致性,而Redis的哨兵功能保证了Redis的高可用:由一个或多个Sentinel实例组成的Sentinel系统,监视任意多个主服务器,以及这些主服务器下的从服务器。当被监视的主服务器进入下线状态时,自动将下线服务器的某个从服务器升级为主服务器。之后发送命令,使得其他从服务器从升级后的主服务器同步数据;而当主服务器重新上线后,将这个上线后的服务器设定为从服务器。

在Sentinel模式下,Redis服务器可以执行的命令只有:PINGSENTINELINFOSUBSCRIBEUNSUBSCRIBEPSUBSCRIBEPUNSUBSCRIBE

集群

通过设置集群,Redis实现了分布式的数据库存储。集群通过分片来进行数据共享,并提供复制和故障转移功能。

集群中的每个节点会存储集群中每个节点的信息到一个字典(clusterState.nodes)中

集群中的整个数据库被分为16384个槽,集群中的每个节点可以处理0到16384个槽。每个节点被分配完槽后,会把自己的slots数据通过消息发送给集群中的其他节点。当集群中的槽被分配完毕,集群完成上线。这些槽的分配信息也会保存在clusterState.slots数组中中。

当向一个节点发送一个key的查询时,该节点会先通过算法确定这个key位于哪个槽中,如果这个槽由自己处理,则直接返回value,否则返回给客户端MOVED,并将正确的节点信息发送给客户端。

Redis命令

CLUSTER MEET ${IP} ${PORT}:将指定ip和port的节点添加到当前node节点所在的集群

CLUSTER ADDSLOTES [${SLOT}]:将${SLOT}指派给当前节点负责

CLUSTER NODES:展示节点信息

集群的故障处理

Redis集群中的节点分为主节点和从节点,主节点处理槽,其他从节点用于复制主节点,并在主节点下线时,代替下线节点继续处理命令请求,所以一个集群中存在多个主节点,分别处理不同的槽。

集群中的每个节点默认会每隔1s钟从已知节点中随机选出5个节点,然后对这5个节点中最长时间没有发送过PING消息的节点发送PING消息,以检测对方节点是否在线,如果接收PING消息的节点未在指定时间内向发送PING消息的节点返回PONG消息,那么这个主节点会被标记为疑似下线(PFAIL)状态,如果一个主节点被集群中半数以上的主节点标记为PFAIL,那么这个节点被标记为下线(FAIL)状态,然后做标记的节点会向其他节点广播节点下线的消息。同时如果节点A最后一次接收到节点B发送的PONG消息的时间已经距离当前时间超过了节点A的cluster-node-timeout时间,节点A也会向B发送PING消息,作为对随机选择的补充。

当从节点发现自己复制的节点处于下线状态,一个从节点会被选举为主节点。这个主节点执行SLAVEOF no one,成为主节点。新的主节点撤销所有对以下线主节点的槽指派,并发这些槽指派给自己,然后向集群中广播PONG消息。

集群的选举

选举过程

  1. 集群配置一个计数器,初始值为0.当集群中的节点开始故障转移操作时,这个计数器加一
  2. 在每个配置纪元,每个主节点只有一个投票机会
  3. 发现主节点下线的从节点向广播中广播一条信息,要求所有收到消息、并具有投票权的主节点向这个从节点投票
  4. 若主节点收到这个投票消息,并且有资格投票,并且在这个投票纪元内未投给其他从节点,那么主节点返回给从节点CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示支持此从节点成为新的主节点
  5. 每个参与选举的从节点接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK,最终收集到大于半数投票的从节点当选为主节点。
  6. 如果一个配置纪元内未收集到足够的投票,计数器归0,重新开始新一轮投票。

发布与订阅

Redis的基本数据类型PUBLISH,SUBSCRIBE,PSUBSCRIBE命令可以实现发布订阅功能。

事务

Redis提供了事务的原子性功能,但没有提供回滚功能。实现方式是把一个或多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且保证在命令执行过程中,不会中断去执行其他客户端的命令。

事务命令以MULTI命令开始,以EXEC命令结束。

WATCH命令是通过加乐观锁的方式监视指定的键是否发生修改,如果在执行EXEC命令时,有key发生修改,服务器会拒绝执行事务,并向客户端返回失败。

任何对数据库进行修改的命令,在执行之后都会对watched_keys字典做检查,看是否有客户端正在监视被修改的数据库键。