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的基本数据类型
字符串
- 缓冲区剩余长度
- 字符串长度
- 字节数组(char类型的数组)
通过惰性释放,避免频繁分配内存空间
链表
链表
- 表头节点
- 表尾节点
- 链表包含的节点数量
- 节点值的复制函数
- 节点值的释放函数
- 节点值的对比函数
节点
- 前置节点
- 后置节点
- 节点的值
字典
用于保存键值对
底层通过哈希表实现,一个哈希表中可以有多个哈希表节点,每个节点保存一个键值对
字典
- 类型特定函数数组指针(每个特定函数类型保存用于操作特定类型键值对的函数)
- 私有数据(保存需要传给特定类型键值对的可选参数)
- 哈希表(size为2,一般只用第0个,当需要做rehash的时候会用到第1个)
- rehash索引
哈希表
- 哈希表节点数组
- 哈希表大小
- 哈希表大小的掩码(用来计算索引值)
- 该哈希表已有节点的数量
哈希表节点
- 键
- 值
- 下一个哈希表节点(连接key的hash值相同的节点,解决键冲突问题)
什么时候做rehash
- 服务器没有执行
BGSAVE
或BGREWRITEAOF
,并且哈希表的负载银子大于等于1 - 服务器正在执行
BGSAVE
或BGREWRITEAOF
,并且哈希表的负载因子大于等于5
以上两个条件中的任意一个被满足,都会做rehash。
在做rehash的时候,可以采用渐进式rehash,也就是对key和value的删除、查找、更新操作会同时在两个hash表中进行,但不会再对第0个哈希表做添加操作,直到第0个哈希表最终变为空表。
跳跃表
通过在每个节点中维持多个指向其他节点的指针,达到快速方位节点的目的。Redis通过跳跃表作为有序集合的底层实现之一。
跳跃表
- 指向跳跃表的表头节点
- 指向跳跃表的表尾节点
- 目前跳跃表内,层数最大的那个节点的层数
- 跳跃表的长度,也就是目前跳跃表包含节点的数量
###跳跃表节点
整数集合
保存整数集合,且集合中不包含重复数据
只支持升级,不支持降级
压缩列表
列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么是小整数值,要么是长度比较短的字符串,那么Redis会使用压缩列表来做列表键的底层实现。
对象
对过期键的处理
当服务器运行在复制模式下时,对过期键的删除由主服务器来控制,当主服务器删除一个过期键时,会向从服务器发送DEL命令,对从服务器的过期键做删除操作;而从服务器不会主动删除过期键。
删除的策略有几种:
- 定时扫描
- 查询时做检查
- 设置定时器,在键过期的时候对键做删除操作
持久化
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服务器可以执行的命令只有:PING
,SENTINEL
,INFO
,SUBSCRIBE
,UNSUBSCRIBE
,PSUBSCRIBE
,PUNSUBSCRIBE
集群
通过设置集群,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消息。
集群的选举
选举过程
- 集群配置一个计数器,初始值为0.当集群中的节点开始故障转移操作时,这个计数器加一
- 在每个配置纪元,每个主节点只有一个投票机会
- 发现主节点下线的从节点向广播中广播一条信息,要求所有收到消息、并具有投票权的主节点向这个从节点投票
- 若主节点收到这个投票消息,并且有资格投票,并且在这个投票纪元内未投给其他从节点,那么主节点返回给从节点
CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK
消息,表示支持此从节点成为新的主节点 - 每个参与选举的从节点接收
CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK
,最终收集到大于半数投票的从节点当选为主节点。 - 如果一个配置纪元内未收集到足够的投票,计数器归0,重新开始新一轮投票。
发布与订阅
Redis的基本数据类型PUBLISH,SUBSCRIBE,PSUBSCRIBE命令可以实现发布订阅功能。
事务
Redis提供了事务的原子性功能,但没有提供回滚功能。实现方式是把一个或多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且保证在命令执行过程中,不会中断去执行其他客户端的命令。
事务命令以MULTI命令开始,以EXEC命令结束。
WATCH命令是通过加乐观锁的方式监视指定的键是否发生修改,如果在执行EXEC命令时,有key发生修改,服务器会拒绝执行事务,并向客户端返回失败。
任何对数据库进行修改的命令,在执行之后都会对watched_keys字典做检查,看是否有客户端正在监视被修改的数据库键。