• Redis


    详解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进行持久化的时候会以子进程或者子线程的方式执行

  • 相关阅读:
    老鼠出迷宫
    Ipad电容笔买原装还是平替?高性价比的ipad平替电容笔推荐
    C语言二叉树的建立和遍历
    Ettus USRP X410 配件讲解,如何选择对应的配件
    Jetpack Compose 教程之 从一开始就投资于良好的导航框架将帮助您在之后节省大量的迁移工作
    vue3 - vue3中使用ref来获取dom节点
    无胁科技-TVD每日漏洞情报-2022-8-24
    30天自制操作系统(第21天)
    记一次生产大对象及GC时长优化经验
    基于探针的分布式追踪工具
  • 原文地址:https://blog.csdn.net/y1250056491/article/details/125475253