Redis

Redis底层是怎样保存一个key和value的?_Torry__的博客-CSDN博客_redis 存放key

详解Redis数据结构及应用场景

1、数据结构

Redis中的每个键值对的键和值都是一个redisObject。

共有五种类型的对象:字符串(String)、列表(List)、字典(Hash)、集合(Set)、有序集合(SortedSet/ZSet)

字符串(String)

Redis并没有直接使用 C 语言自带的字符串,而是使用了 SDS 来管理字符串,通过空间预分配、惰性空间释放等内存优化手段,来达到字符操作的高性能

ZSet

一个 zset 结构同时包含一个字典和一个跳跃表:

typedef struct zset{ //跳跃表 zskiplist *zsl; //字典 dict *dice; } zset;

字典的键保存元素的值,字典的值则保存元素的分值;跳跃表节点的 object 属性保存元素的成员,跳跃表节点的 score 属性保存元素的分值。

这两种数据结构会通过指针来共享相同元素的成员和分值,所以不会产生重复成员和分值,造成内存的浪费。而且两者的结合,可以使精确查找的算法复杂度为O(1) ,范围查找的算法复杂度为O(logn)

跳跃表(SkipList)

跳跃表是一种单向的分层的有序链表,借助于概率统计插入算法,比如我们在L1插入一个元素后,我们抛个硬币,如果是正面则向上一层(L2)插入该元素,并建立链接;如果再抛一次还是正面那么再往上一层(L3)插入该元素,如果是反面则停止插入,依次类推,当数据量足够大的时候,就会无限趋向于一个平衡二叉树。查找的方式也是可以接近于二分查找。所以跳跃表的增删改查的算法复杂度都接近于O(logn)

字典(Hash)

类似于Java中的HashMap,有一个hash数组,数组中是一个DictEntry链表

列表(List)

对于List链表,它的本质是一个双向链表的结构,每个元素都是一个节点,可以将Redis中的list列表结构,看做是Java中的LinkedList结构。

由于Redis的List结构的是双向链表结构,所以这也代表了它的插入和删除操作非常快,时间复杂度为 O(1),查询很慢,时间复杂度为 O(n)。

同时,Redis的List列表,也是可以作为队列和栈来使用

集合(Set)

类似java中的HashSet(底层HashMap实现),底层用字典实现,只用到了key,value都为null

2、持久化

RDB持久化

DB持久化方式能够在指定的时间间隔能对你的数据进行快照存储。在默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。

在 Redis 运行时, RDB 程序将当前内存中的数据库快照保存到磁盘文件中, 在 Redis 重启动时, RDB 程序可以通过载入 RDB 文件来还原数据库的状态。

Redis还提供了自动生成RDB的方式。你可以通过配置文件对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动进行数据集保存操作。

优点:

(1)性能较好--RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能。

(2)大数据恢复快--与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些。

缺点:

(1)大数据量持久化耗时长--RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求。

(2)不可控、丢失数据--你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据。

AOF持久化

从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。

打开AOF后, 每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾(先追加到aof_buf缓冲区末尾,默认配置下会每隔1s把aof_buf中的内容同步到AOF文件中)。这样的话, 当 Redis 重新启时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的(相当于mysql的binlog)。

AOF文件中会存在冗余命令导致体积膨胀,因此需要对AOF文件进行重写,也就是根据数据库当前的值,来生成新的一条命令来替代之前的多条指令

优点:

(1)更可靠--每秒fsync,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据。

(2)顺序易读性--AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。

缺点:

(1)占用较大体积--对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。

(2)速度较RDB慢--根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

3、集群

主从复制

Redis2.8之后采用PSYNC命令来完成复制时的同步操作,主要分为完整重同步和部分重同步

(1)完整重同步

主要用于处理初次复制的情况,主要通过让主服务器创建并发送RDB文件,以及向从服务器发送保存在缓冲区里面的写命令来进行同步

(2)部分重同步

主要用于断线重连后的复制情况,当从服务器在断线重连后,主服务器可以只把断线期间的写命令发送给从服务器即可

原理:

当主服务器进行命令传播时,它不仅会将写命令发送给从服务器,还会将写命令入队到复制积压缓冲区里,当从服务器重新连上主服务器时,从服务器会通过PSYNC命令将自己的复制偏移量以及之前主服务器的运行ID,发送给主服务器。

主服务器会根据这个复制偏移量以及运行ID来决定对从服务器执行不同的同步操作

1》如果offset偏移量之后的数据已经不存在于复制积压缓冲区,或者运行ID不同,那么主服务器将对从服务器进行完整重同步

2》若offset偏移量之后的数据存在且运行ID和主服务器运行ID相同,那么主服务器将对从服务器进行部分重同步

哨兵模式

(1)sentinel集群用来监控主服务器和从服务器

(2)sentinel集群通过Raft 协议进行领头sentinel选举

(3)领头sentinel负责选举新的主服务器(拥有最大偏移量的优先级最高),并进行故障转移(通知其它从服务器与新的主服务器建立连接)

集群模式(Gossip协议)

(1)槽指派

redis集群可以被分为16384个槽,只有这些槽全被指派了处理节点的情况下,集群的状态才是上线的状态。通过key找到对应的槽的主节点来实现所有的存储操作,从而达到水平扩展

(2)重新分片

将已经指派给某节点的槽重新分配给新的节点

(3)故障转移

通过ping-pong指令发现下线的主节点;选举(半数原则)主节点(自己发起选举请求);故障迁移--把老节点的槽指派给自己,并广播自己是主节点

4、Redis为什么这么快

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

4、使用多路I/O复用模型,非阻塞IO;

5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

以上几点都比较好理解,下边我们针对多路 I/O 复用模型进行简单的探讨:

(1)多路 I/O 复用模型

多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。

这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。

5、注意点

1、我们知道Redis是用"单线程-多路复用IO模型"来实现高性能的内存数据服务的,这种机制避免了使用锁,但是同时这种机制在进行比较耗时的命令时会使redis的并发下降。因为是单一线程,所以同一时刻只有一个操作在进行,所以,耗时的命令会导致并发的下降,不只是读并发,写并发也会下降。而单一线程也只能用到一个CPU核心,所以可以在同一个多核的服务器中,可以启动多个实例,组成master-master或者master-slave的形式,耗时的读命令可以完全在slave进行。

2、这里我们一直在强调的单线程,只是在处理我们的网络请求的时候只有一个线程来处理,一个正式的Redis Server运行的时候肯定是不止一个线程的,这里需要大家明确的注意一下!例如Redis进行持久化的时候会以子进程或者子线程的方式执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值