redis笔记

SDS

说一下sds是怎么做的?

sds保存三个字段,len,free,char数组。length记录char数组的长度,free记录char数组的空闲长度,char数组记录字符串

sds有什么特点?

sds通过len和free能在O(1)复杂度获取字符串长度

通过字符串的长度,sds不必通过判断空字符来确定字符串末尾,所以可以存二进制数据。

通过空闲空间的设计,不用在每次增删字符的时候都重新创建数组,可以减少创建数组的次数

char数组仍然以空字符结尾,这样能复用C字符串的一些特性。

sds提供避免缓冲区溢出的api

LinkedList

数据结构为无环双端链表,冗余了length字段,这样获取链表长度时的时间复杂度是O(1)

Dict

字典是怎样实现的?

字典使用了两个哈希表,一般情况下只用其中一个哈希表,在扩容时同时使用两个,使用链表法解决hash冲突。

字典的扩容原理?

扩容时采用渐进式的扩容,每次请求字典时,会将老哈希表的部分数据迁移新哈希表,新增的数据会直接放到新哈希表,扩容过程中查询时会查两个哈希表。迁移数据是通过rehashidx实现的,非扩容状态值是-1,扩容时将值设置为0,然后将老哈希表中rehashIdx索引上的键值对迁移到新哈希表,当迁移完成后,rehashidex自增1

SkipList

说一下跳表?

跳表,保存了表头,表尾,长度,再同一个跳表中可以有相同得分的值,但是值是唯一的,节点按照得分大小进行排序,跳跃表的高度为1-32之间的随机数,查找数据时由高层到低层去查找

整数集合

有序无重复的方式存数据,通过type保存最大的整数值类型,可以动态调整数组大小,避免空间浪费

压缩列表

为了节约内存而开发的顺序性的结构

对象

简介

RedisObject,包含type,encoding, ptr。

type,redis五大对象,字符串,列表,哈希,集合,有序集合

encoding,记录了对象使用的编码,不同的场景使用不太的编码提高效率

字符串对象

字符串编码可以是int,raw,embstr。int,存整数型。raw保存32字节以上的字符串,embstr保存小于等于32字节的字符串。embstr将redisObject和sdshdr结构保存到一块连续的空间。这样在分配内存释放内存时都只需要调用一次函数,并且由于在一块连续内存,能更好的利用高速缓存。在进行浮点数保存和操作时,底层保存的是字符串,操作前转换成浮点数存储时在转成字符串

列表对象

列表对象的实现可以是压缩列表或者链表

哈希对象

哈希对象的实现可以是ziplist或者hashtable,ziplist,使用ziplist时,先将键加入到队列,再将值放入队列

集合对象

集合对象的实现是intset或者hashtable,采用intset时直接全部存到intset里,使用hashtable时将值设置为Null

有序集合对象

有序对象的实现是ziplist或者skiplist+hashtable,ziplist保存时,第一个元素保存值,第二个元素保存分值。skiplist+hashtable,使用hash表的目的是将查找分值时的时间复杂度降到O(1)

类型检查的实现

redis在执行命令时,会检查RedisObject的type属性,如果属性不支持这个命令,会返回一个类型错误

多态命令实现

由于一个对象会有多种不同的数据结构实现,所以在调用对象的不同方法时,内部会根据编码使用不同的方法

内存回收

Redis使用C语言实现,C语言不具备自动内存回收的功能,Redis在自己的对象系统中构建了一个引用计数实现了内存回收机制

对象共享

使用共享对象时要判断对象完全相等,判断字符串相等的时间复杂度是O(N),判断整数的时间复杂度为O(1),为了节省CPU,Redis只对包含整数值的字符串对象进行共享,包含0-9999的整数值。

对象的空转时长

RedisObject包含一个lru属性,表示键的空转时长,在进行lru回收时回用到这个属性

数据库

数据库的组成

数据库主要由dict和expires两个字典构成,其中dict字典负责保存键值对,而expires字典保存键的过期时间。exipires字典的键指向数据库中的某个键,而值是过期时间,过期时间是一个以毫米为单位的unix时间戳。

数据库过期策略

Redis使用惰性删除和定期删除两种策略来删除过期的键,惰性删除只在请求访问到过期键时才进行删除,定期删除是每隔一段时间主动查找并删除,定期删除是在规定时间内,一直不断的随机从expires字典获取数据,如果发现过期就删除,直到expires中没有数据或者达到时间限制。从服务器及时发现过期键不会主动删除,而是等主节点的命令,这样可以保证主从数据的一致性

RDB

服务器会优先使用AOF还原数据库状态。SAVE命令会使服务器阻塞,将导致所有客户端请求都会被拒绝。BGSAVE命令会在子进程中进行,通过周期性的检查在一定时间内redis数据库状态修改的次数,决定是否执行BGSAVE。

AOF

AOF写入

AOF持久化功能的实现可以分为命令追加,文件写入,文件同步三个步骤。当服务端收到写入命令时,会将命令追加到AOF缓冲区,文件写入是将AOF缓冲区的内容同步到AOF文件,但是不刷盘,文件同步会将AOF文件中的内容写到磁盘。redis会在事件循环时尝试将缓冲区中的数据写入文件,redis支持三种写入方式。always,每次写到AOF文件后立刻同步。everysec,写入文件,如果距离上次刷盘超过了一秒,进行刷盘,no,不主动同步,何时同步由操作系统决定

AOF数据还原

AOF文件里保存了所有的写命令,所以将AOF所有的命令执行一遍就能恢复数据。

AOF重写

Redis文件重写并没有对AOF文件进行任何读取,分操作,而是通过读取服务器当前的数据库状态实现的。redis在子进程里进行AOF重写,子进程带有服务器进程的数据副本,然后设置了一个AOF重写缓冲区,将新到达的命令放到重写缓冲区里,等到重写AOF结束后,会将AOF重写缓冲区里的数据写到重写后的AOF文件中,然后让新的AOF覆盖掉现有的AOF文件

事件

重点

Redis服务器是一个事件驱动程序,服务器处理的事件分为文件事件和时间事件两类

文件事件处理器是基于Reactor模式实现的网络通信程序

文件事件是对套接字操作的抽象,每次套接字变为可应答,可写,可读时相应的文件事件就会产生

时间事件分为定时和周期性事件,服务器一般情况下只执行serverCron这个周期事件

服务器会轮流处理文件事件和时间事件,两种事件也是串行执行的

时间事件在文件事件之后执行,所以通常会比设定的时间晚一点

文件事件

一次完成的客户端与服务器连接过程,Redis服务器监听服务端socket的读事件,该事件关联连接应答处理器,当客户端发起连接时,服务器就会监听到读事件,然后和这个客户端进行连接,建立连接后,会监听客户端socket,当客户端发命令过来时,服务端会监听到读事件,然后将事件发送给命令请求处理器,命令请求处理器会将命令传给相关程序去执行,执行命令后会产生对应的回复,服务器会将客户端socket的写事件与命令回复处理器关联,之后触发写事件的处理,将数据写到客户端后,服务器会解除客户端socket的写事件与命令回复处理器的关联

时间事件

一般情况下只执行serverCron,主要作用是更新服务器统计信息,清理过期键值对,关闭失效的客户端,尝试进行AOF,RDB,如果是主服务器需要与从服务器定期同步,集群模式需要对集群进行定期同步

事件调度

获取距离当前事件最短的事件事件,并获取距离当前事件差多少毫秒,根据这个毫秒值,计算出等待文件事件的事件,之后阻塞等待文件事件,直到事件到来或者超时,之后处理文件事件,然后处理时间事件

服务器

重点

一个命令请求从发送到完成主要包括以下步骤,客户端将命令请求发送给服务器,服务器读取命令请求,并分析出命令参数,命令执行器根据参数查找命令的实现函数,然后执行函数并得到命令回复,服务器将命令回复返回给客户端

serverCron函数默认100ms执行一次,主要用于更新服务器状态信息,管理客户端资源,检查并进行持久化等

命令请求的执行过程

客户端将命令请求转换成协议格式然后发送到服务端,服务端检测到客户端可读,调用命令请求处理器来进行处理,读取套接字中协议的命令请求,并将数据保存到客户端的输入缓冲区里,对输入缓冲区的命令进行分析,提取命令参数,调用命令执行器,执行客户端指定的命令,命令执行器首先查找命令实现,然后进行校验,比如命令参数是否正确,客户端是否鉴权,之后调用命令的实现函数,执行后续工作,比如将命令写入到AOF,之后将命令恢复写到输出缓冲区,将客户端socket设置为可写,然后将缓冲区中的数据发送给客户端

 

复制

重点

Redis 2.8以前的复制功能不能高效的处理断线后重复制的情况,2.8新添加的部分重同步可以解决这个问题

部分重同步通过复制偏移量,复制积压缓冲器,服务器运行ID来实现

主服务器通过向从服务器传播命令来更新从服务器状态,保持主从服务器一直,而从服务器通过向主服务器发送命令来进行心跳检测,以及命令丢失检测

复制流程

从服务器发送psync命令,如果是首次复制,主服务器执行BgSave命令开始生成RDB文件,并且将之后的写命令保存起来,等RDB生成后,将RDB文件和保存的所有写命令发送给从服务器,之后主服务器主动将执行的命令传给从服务器。如果是断线后重复制,主服务器会将从服务器断开期间执行的写命令发送给从服务器

部分重同步的实现

主从服务器分别维护一个复制偏移量,主服务器维护一个复制积压缓冲区,是一个先进先出队列,当主服务器进行命令传播时,会将命令写到复制积压缓冲区里,并且记录命令的偏移量,如果从服务器重连时,从服务器的复制偏移量在积压缓冲区里,那么就可以将从服务器断线期间产生的命令发送到从服务器,实现部分重同步

哨兵

故障转移

当哨兵将一个主服务器判断为主观下线时,会向其它哨兵进行询问,如果有足够多的哨兵标位了主观下线,那么进行客观下线,客观下线后选举领头哨兵,当一个哨兵超过半数哨兵支持会当选为领头哨兵,之后发起一次故障转移操作,选出一个从服务器作为主服务器,当选择主服务器时首先过滤掉一些下线的和长时间没有成功通信的,过滤后的列表按照从服务器优先级,复制偏移量,运行id,这三个数据进行排序,排序的优先级依次降低。最后选出排序第一的从服务器,然后将这个从服务器设置为主服务器,其它的从服务器作为它的从服务器

集群

简介

Redis集群采用主从节点的方式

加入集群

通过cluster meet命令,可以让节点加入集群,比如客户端向A发出请求,请求将B加入集群,当B被加入集群后,A通过gossip协议传播给集群中的其它节点。Redis集群通过分配的方式保存数据库中的键值对,集群的整个数据库被分为16384个槽,当16384个槽都有节点处理时,集群处于上线状态。节点会将自己处理的槽告知其它节点,这样每个节点都知道16384个槽都由哪些节点处理。

处理请求

当节点收到客户端的请求时,如果发现键所在的槽在当前节点直接处理,否则向客户端返回一个MOVED错误,让客户端转向正确的节点。在进行重新分片的时候,节点会迁移数据到其它节点,在迁移过程中,如果客户端请求源节点,源节点会先在自己的数据库里找指定的键,如果找不到时返回一个ASK错误,让客户端请求转移的目标节点。

故障转移

集群中的每个节点都会定期的向集群中其它节点发送ping消息,以此来检测对方是否在线,对超时的节点标记为疑似下线,半数以上的主节点将一个节点标记为疑似下线时,标记这个主节点下线。之后从节点像所有主节点发起请求,尝试获得选票,如果有从节点获得一半以上主节点的选票,那么当选新的主节点,如果一次没有选出会再次进行选举

 

posted @ 2023-01-09 15:36  刘皓lh  阅读(24)  评论(0编辑  收藏  举报