数据结构和对象
一、Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。redis是一个key-value存储系 统,高性能的key-value数据库。 Redis支持主从同步,提供五种数据类型:string,hash,list,set及zset(sorted set)。
二、简单动态字符串
(1)Redis没有直接使用C语言字符串,而是自定义了字符串SDS。
数据结构:struct sdsstr{ int len ; int free; char buf[] }; 属性含义:len表示已使用的字节数,free表示未使用的字节数,buf表示字节数组,用于保存字符串。
(2)SDS与C字符串的区别:
1)SDS结构体中记录字符串的长度,而C中需要遍历计数。
2)SDS可以杜绝缓冲区溢出。当SDS修改时,SDS API会检查SDS的空间是否满足需求,若不满足需求,会根据空间分配策略修改空间大小。
3)SDS可以减少修改字符串时带来的内存重分配次数。C中字符串修改时,都会对字符串进行重分配,而SDS使用未使用空间,并通过空间预分配和惰性 空间释放的策略来实现。
4)二进制安全:C中会对一些字符进行处理,比如不能包含空字符,以“\n”为结束符等等,这样就不能保存图片、音频、视频等信息了。而SDS不会对字符串中 的任何数据限制、过滤。所以SDS不仅可以保存文本数据,也能保存任意格式的二进制数据。
5)空间预分配:当SDS进行修改时,程序不仅会为SDS分配必须要的空间,还会为SDS分配额外的未使用空间。
分配未使用空间:当修改后的SDS小于1MB,分配同样大小的未使用空间;当修改后的大小大于1MB,分配1MB的未使用空间。
6)惰性空间释放:用于优化SDS字符串缩短的操作。当SDS缩短时,程序不立即使用内存重分配回收缩短后多出来的字节,而是使用free属性记录这些数量, 等待将来使用。
三、链表
(1)链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以灵活地增删节点。C中没有这种数据结构,所以Redis构建了自己的链表实现。
(2)链表应用广泛,如用在链表键、发布与订阅、慢查询、监视器等等。
(3)数据结构:
链表节点: typedef struct listNode { struct listNode * prev; struct listNode * next; void * value; } listNode; 属性说明:prev 前置节点,next 后置节点, value 节点的值。
链表: typedef struct list { listNode * head; listNode * tail; unsigned long len; .... } list ;属性说明:head 表头结点,tail 表尾节点,len 节点数量,以及一 些操作的函数。
(4)链表特性:双端、无环、带表头结点和表尾节点、带链表长度计数器、多态。
四、字典
(1)字典有称为符号表、关联数组或映射,是一种用于保存键值对的数据结构。C中没有内置这种数据结构,所以Redis构建了自己的字典实现。Redis数据库使用字典作为底层实现的,字典也是哈希键的底层实现之一。
(2)哈希表数据结构: typedef struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask;unsigned long used; } dictht; 属性说明:table 哈希表数组,size 哈希表大小,sizemark 哈希表大小掩码,用于计算索引值,used哈希表已有的节点数量。
(3)哈希表节点数据结构:typedef struct dicEntity {void * key; union { void * val; uint64_t u64; int64_t s64}v ; struct dictEntry * next ; } dicEntity; 属性说明:key 键 ,val 值,next 指向下一个哈希节点。
(4)字典的数据结构:typedef struct dict {dictType * type; void * privdate; dictht ht[2]; int rehashidx;} 属性说明:type 类型特定函数,privdata 私有数据,ht[2] 哈希表,rehashidx rehash索引,不再rehash时,值为-1。
(5)哈希算法:当要将一个新的键值对添加到字典里面时,程序需要先根据键值对的键计算出哈希值和索引值,然后再根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上面。
(6)解决键冲突:当有两个或以上数量的键被分配到了哈希表数组的同一个索引上面时,就会发生键冲突。Redis的哈希表采用链地址法来解决键冲突。
(7)rehash:当哈希表保存的键值不断增多或减少时,为了将哈希表的负载因子维持在一个合理的范围,需要进行重新散列。
(8)每个字典带有两个哈希表,一个是平时使用,另一个仅在进行rehash时使用。
五、跳跃表
(1)跳跃表是一个有序的数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。
跳跃表是有序集合键的底层实现之一。
(2)跳跃表节点数据结构:
typedef struct zskiplistNode { struct zskiplistLevel { zskiplistNode * forward; unsigned int span;} level[]; struct zskiplistNode * backward; double score; robj * obj } zskiplistNode; 属性说明:level[] 层,forward 前进指针,span 跨度(节点之间距离),backward 后退指针,score 分值(用来排序)
六、整数集合
(1)整数集合是集合键的底层实现之一,当一个集合只包含整数值元素并且元素不多时,Redis就会使用整数集合作为集合键的底层实现。
(2)整数类型有int16_t,int32_t,int64_t。
(3)将一个新元素添加到整数集合中时,如果新元素比整数集合中的元素类型都要长时,需要先将整数升级。整数集合不支持降级操作。
(4)升级策略的好处:提升整数集合的灵活性;尽可能的节约内存(集合中能同时保存三种不同类型的值)。
七、压缩列表
(1)压缩列表是列表键和哈希键的底层实现之一,是为了节约内存而开发的数据结构。当列表键只包含少量列表项,并且每个列表项是小整数项或比较短的字符串,就可用压缩列表实现。
(2)压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
八、对象
(1)Redis不会直接使用数据结构来实现键值对数据库,而是会先创建基于数据结构的对象来表示数据库中的键和值。
(2)对象的数据结构:typedef struct redisObject {unsigned type :4 ; unsigned encording : 4; void * ptr ; ...} robj;
属性说明:type 类型,encoding 编码,ptr 指向底层实现的数据结构的指针。
(3)类型有REDIS_STRING (字符串对象)、REDIS_LIST(列表对象)、REDIS_HASH(哈希对象) ,REDIS_SET(集合对象),REDIS_ZSET(有序集合对象),可使用TYPE命令查看
(4)encoding属性决定ptr指针指向哪些数据结构。使用OBJECT ENCODING命令查看。
(5)字符串的编码可以是int、raw或者embstr(保存短字符串),编码可以进行转换。
(6)列表对象的编码可以是ziplist或者linkedlist。
(7)Redis命令分为两种类型:一是对任意类型都可以执行,比如DEL命令、EXPIRE命令等等,另一种只对特定类型的键执行,比如SET只适合字符串键,RPUSH只能对列表键执行等等。
(8)执行一个命令前,Redis会先检查输入键的类型是否正确,然后再决定是否执行给定的命令,也可以根据值对象的编码方式来选择执行命令。
(9)Redis中对象的生命周期分为创建对象、操作对象、释放对象三个阶段。
(10)C语言中不会自动内存回收,所以Redis自己实现了内存回收机制。采用引用计数法实现,当技术值变为0时,对象所占用的内存会被释放。
(11)对象共享:多个键共享一个值对象(将键的值指针指向一个现有的值对象,将值对象的引用计数增一)。
(12)对象的属性lru表示最后一次被命令程序访问的时间。空转时长=当前时间- 值对象的lru。
单机数据库的实现
一、数据库
(1)服务器中的数据库、切换数据库(默认为0号数据库,使用SELECT切换)、键空间(与用户所见的数据库直接对应)
(2)对键空间的操作:添加键、删除键、更新键、对键取值,设置键的生存时间或过期时间(使用TTL、PTTL查看键剩余时间,过期的键会从数据库中删除)。
(3)过期键的判定:
1)检查给定键是否存在于过期字典:如果存在,那么取得键的过期时间。
2)检查当前的时间是否大于键的过期时间:如果是,那么键过期,否则键未过期。
(4)过期键删除策略:
1)定时删除:给定一个定时器,在过期时间来临时,立即执行对键的删除操作。对内存友好,对CPU时间不友好,因为会抢占一部分CPU时间。
2)惰性删除:对过期的键不管,只有取键时才去检查键是否过期,若过期则删除。对内存不友好,因为键会累积,对CPU时间友好。
3)定期删除:每隔一段时间,对数据库进行一次检查,对过期的键检查,若过期则删除,是折中的方案。
二、RDB持久化
(1)Redis是内存数据库,它将自己的数据状态存储在内存里面。但是当服务器退出或断电等,服务器中的数据库状态会消息,所以需要进行持久化。Redis使用RDB进行持久化,保存所有数据库中的键值对数据。
(2)生成RDB文件:使用SAVE命令(阻塞服务器进程),或BGSAVE命令(派生出一个子进程,服务器进程继续执行)。
(3)自动间隔性保存:可以为save设置保存条件 比如 save 900 1 (服务器900秒内对数据库修改1次,就执行BGSAVE)
(4)dirty计数器:记录距离上一次SAVE或BGSAVE之后有多少次修改,lastsave:记录上一次SAVE或BGSAVE的时间。
(5)RDB文件结构:包括db_version、databases、key_value_pairs等部分。RDB文件检查工具:redis-check-dump。
三、AOF持久化
(1)AOF持久化通过保存Redis服务器所执行的写命令记录数据库状态。服务器启动时,可以通过载入和执行AOF文件中保存的命令来还原服务器关闭之前的数 据库状态。
(2)AOF持久化功能的三个步骤:命令追加、文件写入、文件同步。
(3)命令追加:当服务器执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器的aof_buf缓冲区的末尾。
(4)fsync和fdatasync两个同步函数,可以强制让操作系统立即将缓冲区的数据写到硬盘里面,以确保数据的安全性。
(5)AOF中包含了重建数据库状态所需的所有写命令,所以服务器只要读入并执行AOF中的写命令,就可以还原服务器关闭之前的数据库状态。
(6)AOF重写功能:可以创建一个新的AOF文件来替代现有的AOF文件,新的文件保持相同的数据库状态,不包含冗余命令,文件体积小。
四、事件
(1)Redis服务器是一个事件驱动程序,包含文件事件、事件事件。
(2)文本事件:使用文件处理器通过I/O多路复用程序来监听多个套接字。
(3)时间事件:定时事件(让程序在指定的时间执行),周期性事件(让程序每隔一段时间执行)。
(4)事件的调度和执行,按一定的规则执行。
五、客服端
(1)Redis服务器是典型的一对多服务器程序:一个服务器可以与多个客户端建立网络连接,客户端向服务器发送请求,服务器接收请求并处理后,并向客户端返回命令回复。
(2)服务器端数据结构:struct redisServer{ list * clients};
(3)客户端属性:包括通用的属性、和特定功能相关的属性。一些属性如:名字、标志、套接字描述符等。
(4)一些关键词:输入缓冲区、输入缓冲区、身份验证、客户端的创建和关闭。
六、服务端
(1)命令请求的执行过程:
客户端发送命令请求、服务器读取命令请求、服务器端使用命令执行器执行相关操作、将命令回复发送给客户端、客户端接收并打印命令回复。
(2)serverCron函数:默认每个100毫秒执行一次,这个函数负责管理服务器的资源,并保持服务器自身的良好运作。
执行的操作有:更新服务器时间缓存、更新LRU时钟、更新服务器每秒执行命令次数、管理客户端资源等等。
多机数据库实现
一、复制
(1)将从服务器从主服务器上复制数据,两者之间保存相同的数据。
(2)旧版复制功能的实现:
同步:将从服务器的数据库状态更新到主服务器当前的数据库状态。
命令传播:当主服务器的数据库状态改变时,导致主从数据库状态不一致时,让主从服务器数据库状态重回一致。
(3)新版复制功能:解决旧版复制功能在处理断线重复制情况下的低效问题。包含完整重同步、部分重同步。
(4)部分重同步实现:复制偏移量、复制积压缓冲区、服务器运行ID。
(5)复制的实现:设置主服务器的地址和端口、建立套接字连接、发送PING命令、身份验证、发送端口信息、同步、命令传播。
(6)心跳检测:检查主从服务器的网络连接状态、检测命令丢失等等。
二、Sentinel
(1)Sentinel(哨兵)是Redis的高可用性解决方法。Sentinel系统可以监控任意多个主服务器,当被监视的服务器下线了,自动将下线主服务器下的从服务器升级为新的主服务器。
(2)Sentinel中一些关键词:启动并初始化Sentinel、获取主服务器信息、获取从服务器信息、向主服务器和从服务器发送信息、接收来自主服务器和从服务器的频道信息、检测主观下线状态、选举领头Sentinel、故障转移。
(3)选举Sentinel:当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个Sentinel会进行协商,选举一个领头的Sentinel,并由领头Sentinel对下线主服务器执行故障转移工作。
(4)故障转移:选出新的主服务器、修改从服务器的复制目标、将旧的主服务器变为从服务器。
三、集群
(1)Redis集群是Redis提供的分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移功能。
(2)Redis集群通常由多个节点组成。
(3)连接各个节点使用CLUSTER MEET,可以让node节点与ip和port所指定的节点进行握手,当握手成功时,将指定的node节点添到集群中。
(4)一个节点就是一个运行在集群模式下的Redis服务器,该服务器会根据cluster-enabled的配置来决定是否开启服务器的集群模式。
(5)一些常用关键字:集群数据结构、槽指派(通过分片的方式来保存数据库的键值对)、计算键属于哪个槽、重新分片、ASK错误和MOVED错误、设置从节 点、选举新的主节点、消息(节点之间通过发送和接收消息来进行通信)。
独立功能的实现
一、发布与订阅
(1)通过执行SUBSCRIBE命令,客户端可以订阅一个或多个频道,每当有其它客户端向被订阅的频道发送消息时,频道的所有订阅者都会收到这条消息。
(2)频道的订阅和退订:订阅频道、退订频道。
(3)模式的订阅和退订:订阅模式、退订模式。
(4)发送消息:将消息发送给频道订阅者,将消息发送给模式订阅者。
二、事务
(1)通过MULTI、EXEC、WATCH(乐观锁)实现事务功能。
(2)事务经历的阶段:事务开始;命令入队;事务执行。
(3)事务的ACID性质:原子性;一致性;隔离性;耐久性;
三、Lua脚本
(1)创建并修改Lua环境、Lua环境协作组件(伪客户端、lua_scripts字典)。
(2)EVAL、EVALSHA命令的实现,脚本管理命令的实现、脚本复制。
四、排序
(1)Redis的SORT命令可以对键列表、集合键或者有序集合键的值进行排序。
(2)一些常见的关键词:SORT<key>、ALPHA、ASC、DSC、BY、LIMIT、GET、STORE
(3)多个选项的执行顺序。
五、二进制位数组
(1)位数组表示、GITBIT、SETBIT、BITCOUNT、BITOP实现。
(2)二进制位统计算法。
六、慢查询日志
(1)用于记录执行时间超过给定时长的命令请求、通过这个功能产生的日志来监视和优化查询速度。
(2)慢查询记录的保存、慢查询日志的阅览和删除、添加新日志。
七、监视器
成为监视器、向监视器发送命令信息。