Redis设计原理简介

学完MySQL InnoDB以后,又开始学习和研究Redis。redis

首先介绍下书:《Redis设计与实现》第二版 黄健宏著,机械工业出版社,388页,基于redis3.0版本。版本有点低,这个影响不大,基本面变化不大,而变化的部分网上查资料能够弥补。算法

1、概述

Redis服务器是一个键值对(key-value pair)类型数据库服务器,属于NoSQL。Redis源码使用ANSI C语言编写而成。数据库

它最大的特色数据所有放缓存,主要用于读写操做频繁的,如秒杀系统。我联想的是咱们监控系统中的秒级回放,特别是分布式系统中,或许能够考虑。数组

2、数据结构

包括动态字符串、链表、字典、跳跃表、整数集合、压缩列表共六种。
redis虽然是C语言实现的,可是它的数据结构和平时用的不同,有些改进和封装,更适用于数据库;这应该也是它的创新之处。缓存

动态字符串SDS和Java  StringBuffer类十分类似:带缓冲区的字符串,值能够被改变。服务器

 印象最深仍是跳跃表,书上介绍说实现比平衡树简单,可是效率却能够和它媲美;从结构设计看,很是优秀。网络

3、对象类型

包括字符串对象、列表对象、哈希对象、集合对象、有序集合对象共五种。对象是对基础数据结构的再封装,从而供Redis直接使用,即Redis并不直接操做上述基本的数据结构。而每种对象都用到了至少一种上面的数据结构。数据结构

这个五大对象类型,才Redis数据库中,对应五个常量,即对象类型常量。而上述六种的数据结构也有对应的常量,叫编码常量。可是编码常量共有八种,其余两种是整型字符串和短字符串。当字符串长度小于等于39字节时,采用embstr编码,即短字符串编码。架构

那么对象类型常量和编码常量是一个固定的对应关系,除了字符串对象类型常量对应三种编码常量外,其余都是对应两种编码常量。app

注:一个对象类型常量对应的 两个或者说三个编码常量在必定条件下系统会自动切换的,条件包括字符串长度变化、元素个数变化、追加不一样类型等等;Redis称为编码转换

 Redis的键和值都是对象,因此Redis基于对象操做的,而对象的内存回收经过引用计数技术。

4、数据库

1. 数据库

Redis服务器的全部数据库都保存在redisServer.db数组中,而数据库的数量则由redisServer.dbnum属性保存,默认会建立16个数据库。

数据库由字典构成,咱们将这个字典称为键空间(key space);键老是一个字符串对象,而值能够是任意一种对象类型,如上描述的五种对象类型之一。

数据库主要由dict和exprires两个字典构成,其中dict字典负责保存键值对,而 exprires字典负责键的过时时间。

2 .键的生命周期

Redis设置键的过时时间管理键的生存时间,设置过的键同时保存在exprires字典中,对键的生存时间的设置有以下命令。

expire------设置过时时间,第二个参数是TTL(单位秒)。

pexpire------设置过时时间(单位豪秒)。

TTL------查看剩下多少时间(单位秒),返回负数则key失效,key不存在了。

PTTL------功能同TTL,单位对应毫秒。

expireat------设置过时时间,第二个参数是时间戳(单位秒)。
pexpireat------功能同上(单位豪秒)。

persist ------取消过时时间,将键值对从exprires字典中移除。

 3.过时键的删除

删除策略有三种方式:

(1)定时删除:建立定时器,只要存在键过时则立刻删除。

(2)惰性删除:获取键的时候才删除。

(3)按期删除:每隔一段时间检测,删除过时键。但不是全部过时键,随机抽取的,由系统算法决定。

Redis实际采用第二种和第三种。

SAVE命令 和 BGSAVE命令所产生的新的RDB文件不会包含过时键。

BGREWRITEOF 命令所产生的AOF重写文件不会包含过时键。

主从复制时,从节点不会主动删除过时键,而是等待主节点发送DEL命令,以保证数据的一致性。

4.其余常见命令

set------设置 key 对应的值为 string 类型的 value。

setnx------设置 key 对应的值为 string 类型的 value。若是 key 已经存在,返回 0,nx 是 not exist 的意思。

setex------设置 key 对应的值为 string 类型的 value,并指定此键值对应的有效期。

del------删除某个key,第一次返回1 删除了 第二次返回0。

mset------一次设置多个 key 的值,成功返回 ok 表示全部的值都设置了,失败返回 0 表示没有任何值被设置。

getset------设置 key 的值,并返回 key 的旧值。

mget------一次获取多个 key 的值,若是对应 key 不存在,则对应返回 nil。

append------给指定 key 的字符串值追加 value,返回新字符串值的长度。

strlen------取指定 key 的 value 值的长度。

randomkey------随机返回一个key

rename------重命名

type ------返回数据类型

select 0 ------选择数据库(0-15库)

move age 1------把age 移动到1库

FLUSHDB------清空整个数据库

思考:

Redis主要是内存资源使用多,但据网上资料,支持较短长度字符串时上百万和千万级不成问题。

5、独立功能

1.事件

分为时间事件和文件事件两类。

时间事件指服务器的一些定时操做。

文件事件是服务器与客户端或者其余服务器之间的通讯。文件事件处理器基于Reactor模型实现的网络通讯,反应器模型;IO多路复用采用相对的还有proactor模型,主动器模型。

2.客户端

Redis服务器是典型的一对多程序,能够同时与多个客户端进行通讯,同时保存每一个客户端相关信息。

客户端结构体最重要的两部分是一个输入缓冲区和一个输出缓冲区,用于保存接收和发送的内容。输入缓冲区,大小不能超过1GB;输出缓冲区分红两部分,固定的和可变的,固定的大小16KB。

客户端分为普通客户端,伪客户端。其中伪客户端包括LUA脚本伪客户端和AOF文件伪客户端。普通客户端的fd属性值大于-1,伪客户端该值为-1。

3.服务器

服务器常见功能是处理客户端输入的命令外,还有serverCron函数,它是一个时间事件,每隔100毫秒运行一次。

serverCron函数的工做主要包括更新服务器状态信息,处理服务器接收的SIGTERM信号,管理客户端资源和数据库状态,检查并执行持久化操做等。

SIGTERM信号主要做用是服务器关闭前不会因其余事件如持久化阻塞而能及时退出。

补充:LRU缓存淘汰算法
在redis缓存机制中,提到了LRU,least recently used最近最少使用,这个在MySQL也有。可是二者描述的不是不少。
感受这个算法很好,在网上查资料,不少互联网的技术也使用了该算法。其实对热点数据,例如咱们的经常使用的摄像机的搜索、回放和即时回放能够考虑。

最新版本Redis还有LFU(least frequently used最不常用)缓存技术。

4.事务

相对MySQL很是简单,ACID都没有MySQL那么强大;也不支持回滚。可能定位不同,redis自己是单线程。

5.发布与订阅

对操做和消息而言,客户端和服务器之间使用。

6.查找排序

经过SORT命令对数据的查找和排序。

7.LUA脚本

Redis内嵌的脚本语言,方便实现批量操做等。常见的有EVAL命令和EVALSHA命令,EVALSHA命令能够直接执行由EVAL命令生成的SHA1校验和。

补充:Lua是一种内嵌的脚本语言,轻量小巧,标准C语言编写,开源。目前常(嵌入)用在游戏开发中。

8.二进制位数组

 Redis支持二进制位数组的操做,这种类型能够大大节约内存空间,并且查找和统计都很是方便。
补充:实际应用中,有不少的大型系统中将二进制位数组用于查询和统计用户的登陆状态。

9.慢查询日志

保存在结构体slowlogEntry为元素的slowlog链表中。能够设置超时时间单位毫秒,和保存条数。

10.监视器

 客户端可经过monitor命令将本身变成监视器,监视服务器处理的每一个命令。

6、持久化

redis数据都是放内存,持久化即将数据写入磁盘,如下两种都是以日志的形式存放。

1.RDB持久化

RDB是Redis DataBase缩写,存的最终的真实的数据,二进制文件,用redis自带命令能够解析和读取。

功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数。

SAVE命令 和 BGSAVE命令用于持久化操做,后者表示子进程在后台运行。BGSAVE模式下,服务器根据配置文件或者save(小写)命令设置得参数自动间隔执行一次BGSAVE。save参数第一个是间隔时间,第二个是修改次数;知足其中之一就会执行。

2.AOF持久化

AOF是Append Only File缩写,存的是增删改的操做命令,ASCII字符格式,可读。

数据修改时,先写入AOF缓冲区,再WRITE写入AOF文件(此时还在文件系统缓存中),最后由系统调用 fsync 或 fdatasync 函数将系统缓存数据保存到磁盘即同步。什么时候同步,有三个选项:always立刻,everysec隔约一秒,no交给操做系统;默认值everysec。

BGREWRITEOF 命令实现AOF重写的功能,即对最终数据读取再添加的命令写入文件,减小中间操做和最终文件的大小。一样使用子进程执行。运行的同时新写的数据放在AOF重写缓冲区,即服务器父进程将写命令同时写入AOF缓冲区和AOF重写缓冲区;待重写工做完成后,再将AOF重写缓冲区内容写入新的AOF文件,继续后面结尾工做。

 注意:当服务器开启了AOF功能时,服务器优先使用AOF文件来还原数据;而AOF默认是关闭的。

并且,BGSAVE 和 BGREWRITEOF  两个命令不能同时运行。若是BGREWRITEOF 在运行,BGSAVE 会被拒绝;而BGSAVE 运行时,BGREWRITEOF 会被延迟到BGSAVE 完成以后。

7、集群

1.主从复制

(1)主从服务器架构中,从服务器发送SLAVEOF命令给主服务器启动复制。从服务器收到OK返回消息后,再次发送SYNC命令表示请求同步数据。主服务器先发送RDB文件给从服务器,再将缓冲区全部写命令也发送给从服务器,从而实现主从数据一致。可是若是同步后,在中间过程从服务器断开,再链接上,将重复上面的过程,即彻底增量的同步。

(2)Redis从2.8版本开始用PSYNC代替SYNC,来实现部分重同步。

(3)部分重同步经过复制偏移量、复制积压缓冲区、服务器运行ID三个部分来实现。

2.Sentinel

Sentinel哨兵模式是Redis的高可用解决方案。

(1)由一个或者多个Sentinel实例组成Sentinel系统,监视多个主服务器以及服务器下的全部从服务器。当一套主从服务器集群中,主节点出现下线或者故障时,将从从服务器中选举一个做为该集群的主服务器,若是之前的主服务器再次上线时,自动变成从服务器,将重新的主服务器复制数据保持同步。

(2)上述过程当中从服务器是如何选出的?领头Sentinel先剔除离线的,较长时间没有回复INFO命令的,再根据优先级,复制偏移量来选择的;即选择状态良好的、数据完整的从服务器。

(3)Sentinel是一个系统,自身每一个节点都是特定模式下的redis服务器,和普通Redis服务器使用的命令不一样。

(4)Sentinel经过向主服务器发送INFO命令来获取主服务器和从服务器的相关信息。再每十秒一次向全部记录的主从服务器发送INFO命令,当集群在主节点故障转移过程当中,将改成一秒一次

(5)Sentinel以每秒一次向全部实例(全部主从服务器和其余Sentinel)发送PING命令,再根据回复是否有效,若是必定时长都收到无效回复,Sentinel认为该实例主观下线。随后该Sentinel会向其余Sentinel进行询问,是否赞成该实例是否进入主观下线,若是赞成的数量达到足够时,将判断该实例为客观下线。若是该实例是主服务器,即将开始故障转移操做。

(6)故障转移操做必须是领头Sentinel来下发,当存在多个Sentinel时需选举其中一个做为领头。

每一个发现主服务器进入客观下线的sentinel均可以要求其余sentinel选本身为领头sentinel,选举是先到先得。同时每一个sentinel每次选举都会自增配置纪元,每一个纪元中只会选择一个领头sentinel。若是全部超过一半的sentinel选举某sentinel领头sentinel。若是在给定时间内,未选出,将重来直到选出为止。

补:选举方法是参考的raft协议(一致性分布式协议),被如今不少分布式系统采用。

(7)存在多个Sentinel时,任意Sentinel都需每两秒一次发现hello频道消息给其余Sentinel以宣告本身的存在。

3.Redis Cluster

Redis3.0上加入了cluster模式,实现的redis的分布式存储。

(1)每一个主节点还能够再加入本身的从节点,主节点兼有Sentinel特性,从而也能够实现复制和故障转移的功能。若是主节点A和它的从节点A1都宕机了,那么该集群就没法再提供服务了。

(2)集群即主主之间经过分片(sharding)来分配和共享数据。集群的整个数据库被分为16384个槽(slot),因此每一个主节点能够处理0-16383个槽。

(3)槽被分配(槽指派)的信息存在一个数组下,数组下标为16384/8=2048,即二进制位数组,每一个位表明被分配的状态。二进制位数组前面已有讲述,使用在这里很是优秀。

(4)每一个键都属于这16384个哈希槽的其中一个,公式 CRC16(key) % 16384来计算键key属于哪一个槽。

 (5)集群的各个节点经过Gossip协议来交换各自信息,该协议由MEET、PING、PONG三种消息实现。

补充:Gossip来源于流行病学的研究(ps,当前正是疫情之下,让人瑟瑟发抖)

  • 一个节点状态发生变化,并向临近节点发送更新信息
  • 对于节点状态变化的信息随机发送给b个节点
  • 随着时间推移,信息可以传达到全部的节点

Gossip简单、高效,同时具备很好的可扩展性和鲁棒性,很是适合大规模、动态、资源受限的网络环境。

(6)节点的FAIL是经过集群中超过半数的节点检测失效时才生效。

 

集群的演进:主从复制实现了读写分离,哨兵模式实现了故障转移,集群模式实现了分布式存储。

 

我的收获总结:读书除了能够系统的了解一个key-value数据库的实现外,还能够看到版本的演进例如集群。还能够见识和学习巩固不少策略和算法,例如跳跃表、二进制位数组、LRU、raft、LUA脚本、Gossip协议等等。开卷有益。若是再深一步,看源码我想收获会更大吧。

相关文章
相关标签/搜索