Redis核心技术与实战学习笔记

news2024/11/14 18:36:37

Redis核心技术与实战学习笔记

  • 最近想沉下心来看下redis,买了蒋德钧老师的《Redis 核心技术与实战》,这里记录一些学习笔记 希望能够坚持下去
  • 有想一起学习的童鞋,可以点击跳转到文章尾部获取学习资源,仅供学习不要用于任何商业用途!!!

redis知识全景图

  • 学习要有一个全局的系统观念

  • 在这里插入图片描述

redis问题画像图

  • redis问题可以多套到这个图中分析形成统一的印象
  • 问题 --> 主线 --> 技术点
  • 在这里插入图片描述

SimpleKV需要考虑的问题

  • 存什么样的数据

    • KV对 key为string,value为基本类型
  • 数据有哪些操作

    • 增删改查 PUT/GET/DEL/Scan
    • 不论是增删改查都涉及到索引问题,因为要先定位到key对应的内存位置再读写 这里有涉及到索引 可以考虑全局hash
      • 这里如果value是复杂结构 hash后还要再次用到不同的索引算法
    • 如果是增和删除 又涉及到内存管理 因为需要分配内存或者释放内存,如果value大小不一致 内存分配算法至关重要
  • 数据存在哪里

    • 存在内存比较快 但内存没了容易丢失,所以还要解决持久化的问题,每次都落地则性能不行,可定时落地到文件
  • 客户端怎么访问

    • .so动态连接库 单机访问
    • socket连接 可以跨机器 但要解决连接管理,协议解析
    • 多个客户端并发访问 如何高性能 涉及到IO模型
  • 容灾怎么来做

    • 涉及到主从或者集群
  • 重启后怎么快速初始化或者恢复

    • 也是涉及到持久化

    • 在这里插入图片描述

redis数据结构

值的数据类型

  • String、List、Hash、Set、sortedSet

KV保存用到的数据结构

整体图示

  • 在这里插入图片描述

  • 在 Redis 3.0 版本中 List 对象的底层数据结构由「双向链表」或「压缩表列表」实现,但是在 3.2 版本之后,List 数据类型底层数据结构是由 quicklist 实现的;

  • 在最新的 Redis 代码(还未发布正式版本)中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了

全局Hash索引示意

  • 在这里插入图片描述

hash冲突解决办法

  • 链式hash 冲突的时候用链表的方式保存冲突的数据,所以多个冲突的数据要遍历就要逐个遍历了

  • 在这里插入图片描述

  • 2个全局hash,渐进式rehash

    • 直接全量rehash涉及大量内存复制操作,可能阻塞客户端请求处理,所以采取的是渐进式rehash

    • 在这里插入图片描述

redis数据结构全景图_xiaolin

  • 在这里插入图片描述

  • redisDb 结构,表示 Redis 数据库的结构,结构体里存放了指向了 dict 结构的指针;

  • dict 结构,结构体里存放了 2 个哈希表,正常情况下都是用「哈希表1」,「哈希表2」只有在 rehash 的时候才用,具体什么是 rehash,我在本文的哈希表数据结构会讲;

  • ditctht 结构,表示哈希表的结构,结构里存放了哈希表数组,数组中的每个元素都是指向一个哈希表节点结构(dictEntry)的指针;

  • dictEntry 结构,表示哈希表节点的结构,结构里存放了 void * key 和 void * value 指针, *key 指向的是 String 对象,而 *value 则可以指向 String 对象,也可以指向集合类型的对象,比如 List 对象、Hash 对象、Set 对象和 Zset 对象

  • void * key 和 void * value 指针指向的是 Redis 对象

redisObject数据结构示意
  • 在这里插入图片描述

  • type,标识该对象是什么类型的对象(String 对象、 List 对象、Hash 对象、Set 对象和 Zset 对象);

  • encoding,标识该对象使用了哪种底层的数据结构;

  • ptr,指向底层数据结构的指针

简单字符串SDS(simple dynamic string)

SDS数据结构

struct SDS{
 len   //这样获取字符串长度的时候,只需要返回这个成员变量值就行,时间复杂度只需要 O(1)
 alloc //分配空间长度 类似capicity 分配给字符数组的空间长度。这样在修改字符串的时候,
 				可以通过 alloc - len 计算出剩余的空间大小,可以用来判断空间是否满足修改需求,如果不满足的话,就会自动将 SDS 的空间				扩展至执行修改所需的大小,然后才执行实际的修改操作,所以使用 SDS 既不需要手动修改 SDS 的空间大小,也不会出现前面所说				的缓冲区溢出的问题
 				当判断出缓冲区大小不够用时,Redis 会自动将扩大 SDS 的空间大小(小于 1MB 翻倍扩容,大于 1MB 按 1MB 扩容
 flag  //用来表示不同类型的 SDS。一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64
 			 每种类型占用内存不一样 更加灵活 而且是禁止内存字节对齐的,节省内存
 			 
 buf[] 字符数组,用来保存实际数据。不仅可以保存字符串,也可以保存二进制数据  
}

链表List

typedef struct listNode {
    //前置节点
    struct listNode *prev;
    //后置节点
    struct listNode *next;
    //节点的值
    void *value;
} listNode;
在这个基础上增加
typedef struct list {
    //链表头节点
    listNode *head;
    //链表尾节点
    listNode *tail;
    //节点值复制函数
    void *(*dup)(void *ptr);
    //节点值释放函数
    void (*free)(void *ptr);
    //节点值比较函数
    int (*match)(void *ptr, void *key);
    //链表节点数量
    unsigned long len;
} list;

  • 在这里插入图片描述

Redis 的链表实现优点如下:

  • listNode 链表节点的结构里带有 prev 和 next 指针,获取某个节点的前置节点或后置节点的时间复杂度只需O(1),而且这两个指针都可以指向 NULL,所以链表是无环链表
  • list 结构因为提供了表头指针 head 和表尾节点 tail,所以获取链表的表头节点和表尾节点的时间复杂度只需O(1)
  • list 结构因为提供了链表节点数量 len,所以获取链表中的节点数量的时间复杂度只需O(1)
  • listNode 链表节使用 void* 指针保存节点值,并且可以通过 list 结构的 dup、free、match 函数指针为节点设置该节点类型特定的函数,因此链表节点可以保存各种不同类型的值

链表的缺陷也是有的:

  • 链表每个节点之间的内存都是不连续的,意味着无法很好利用 CPU 缓存。能很好利用 CPU 缓存的数据结构就是数组,因为数组的内存是连续的,这样就可以充分利用 CPU 缓存来加速访问。
  • 还有一点,保存一个链表节点的值都需要一个链表节点结构头的分配,内存开销较大

因此,Redis 3.0 的 List 对象在数据量比较少的情况下,会采用「压缩列表」作为底层数据结构的实现,它的优势是节省内存空间,并且是内存紧凑型的数据结构。

不过,压缩列表存在性能问题(具体什么问题,下面会说),所以 Redis 在 3.2 版本设计了新的数据结构 quicklist,并将 List 对象的底层数据结构改由 quicklist 实现。

然后在 Redis 5.0 设计了新的数据结构 listpack,沿用了压缩列表紧凑型的内存布局,最终在最新的 Redis 版本,将 Hash 对象和 Zset 对象的底层数据结构实现之一的压缩列表,替换成由 listpack 实现。

压缩列表

压缩列表的最大特点,就是它被设计成一种内存紧凑型的数据结构,占用一块连续的内存空间,不仅可以利用 CPU 缓存,而且会针对不同长度的数据,进行相应编码,这种方法可以有效地节省内存开销。

但是,压缩列表的缺陷也是有的:

  • 不能保存过多的元素,否则查询效率就会降低;
  • 新增或修改某个元素时,压缩列表占用的内存空间需要重新分配,甚至可能引发连锁更新的问题。

因此,Redis 对象(List 对象、Hash 对象、Zset 对象)包含的元素数量较少,或者元素值不大的情况才会使用压缩列表作为底层数据结构。

  • 在这里插入图片描述

  • *zlbytes*,记录整个压缩列表占用对内存字节数;

  • *zltail*,记录压缩列表「尾部」节点距离起始地址由多少字节,也就是列表尾的偏移量;

  • *zllen*,记录压缩列表包含的节点数量;

  • *zlend*,标记压缩列表的结束点,固定值 0xFF(十进制255)。

  • *prevlen*,记录了「前一个节点」的长度;

  • *encoding*,记录了当前节点实际数据的类型以及长度;

  • *data*,记录了当前节点的实际数据;

    当我们往压缩列表中插入数据时,压缩列表就会根据数据是字符串还是整数,以及数据的大小,会使用不同空间大小的 prevlen 和 encoding 这两个元素里保存的信息,这种根据数据大小和类型进行不同的空间大小分配的设计思想,正是 Redis 为了节省内存而采用的

    分别说下,prevlen 和 encoding 是如何根据数据的大小和类型来进行不同的空间大小分配。

    压缩列表里的每个节点中的 prevlen 属性都记录了「前一个节点的长度」,而且 prevlen 属性的空间大小跟前一个节点长度值有关,比如:

    • 如果前一个节点的长度小于 254 字节,那么 prevlen 属性需要用 1 字节的空间来保存这个长度值;
    • 如果前一个节点的长度大于等于 254 字节,那么 prevlen 属性需要用 5 字节的空间来保存这个长度值;

    encoding 属性的空间大小跟数据是字符串还是整数,以及字符串的长度有关:

    • 如果当前节点的数据是整数,则 encoding 会使用 1 字节的空间进行编码。
    • 如果当前节点的数据是字符串,根据字符串的长度大小,encoding 会使用 1 字节/2字节/5字节的空间进行编码。
    连锁更新

    压缩列表除了查找复杂度高的问题,还有一个问题。

    压缩列表新增某个元素或修改某个元素时,如果空间不不够,压缩列表占用的内存空间就需要重新分配。而当新插入的元素较大时,可能会导致后续元素的 prevlen 占用空间都发生变化,从而引起「连锁更新」问题,导致每个元素的空间都要重新分配,造成访问压缩列表性能的下降

    前面提到,压缩列表节点的 prevlen 属性会根据前一个节点的长度进行不同的空间大小分配:

    • 如果前一个节点的长度小于 254 字节,那么 prevlen 属性需要用 1 字节的空间来保存这个长度值;
    • 如果前一个节点的长度大于等于 254 字节,那么 prevlen 属性需要用 5 字节的空间来保存这个长度值;

    现在假设一个压缩列表中有多个连续的、长度在 250~253 之间的节点

    ​ 因为这些节点长度值小于 254 字节,所以 prevlen 属性需要用 1 字节的空间来保存这个长度值

    这时,如果将一个长度大于等于 254 字节的新节点加入到压缩列表的表头节点,即新节点将成为 e1 的前置节点

    • 在这里插入图片描述

e1 原本的长度在 250~253 之间,因为刚才的扩展空间,此时 e1 的长度就大于等于 254 了,因此原本 e2 保存 e1 的 prevlen 属性也必须从 1 字节扩展至 5 字节大小。

正如扩展 e1 引发了对 e2 扩展一样,扩展 e2 也会引发对 e3 的扩展,而扩展 e3 又会引发对 e4 的扩展…. 一直持续到结尾。

这种在特殊情况下产生的连续多次空间扩展操作就叫做「连锁更新」,就像多米诺牌的效应一样,第一张牌倒下了,推动了第二张牌倒下;第二张牌倒下,又推动了第三张牌倒下….,

压缩列表的缺陷

空间扩展操作也就是重新分配内存,因此连锁更新一旦发生,就会导致压缩列表占用的内存空间要多次重新分配,这就会直接影响到压缩列表的访问性能

所以说,虽然压缩列表紧凑型的内存布局能节省内存开销,但是如果保存的元素数量增加了,或是元素变大了,会导致内存重新分配,最糟糕的是会有「连锁更新」的问题

因此,压缩列表只会用于保存的节点数量不多的场景,只要节点数量足够小,即使发生连锁更新,也是能接受的。

虽说如此,Redis 针对压缩列表在设计上的不足,在后来的版本中,新增设计了两种数据结构:quicklist(Redis 3.2 引入) 和 listpack(Redis 5.0 引入)。这两种数据结构的设计目标,就是尽可能地保持压缩列表节省内存的优势,同时解决压缩列表的「连锁更新」的问题。

仅供学习 切记用于任何商业用途

关注 _微_信_公_众_号 疯子爱淡定 回复 Redis核心技术学习

  • 在这里插入图片描述

Hash

typedef struct dict {
    …
    //两个Hash表,交替使用,用于rehash操作
    dictht ht[2]; 
    …
} dict;
---------------------------->
typedef struct dictht {
    //哈希表数组
    dictEntry **table;
    //哈希表大小
    unsigned long size;  
    //哈希表大小掩码,用于计算索引值
    unsigned long sizemask;
    //该哈希表已有的节点数量
    unsigned long used;
} dictht;
---------------------------->
typedef struct dictEntry {
    //键值对中的键
    void *key;

    //键值对中的值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    //指向下一个哈希表节点,形成链表
    struct dictEntry *next;
} dictEntry;
负载因子=已保存节点数量/hash表大小
  • 当负载因子大于等于 1 ,并且 Redis 没有在执行 bgsave 命令或者 bgrewiteaof 命令,也就是没有执行 RDB 快照或没有进行 AOF 重写的时候,就会进行 rehash 操作。
  • 当负载因子大于等于 5 时,此时说明哈希冲突非常严重了,不管有没有有在执行 RDB 快照或 AOF 重写,都会强制进行 rehash 操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1944859.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024-07-22 Unity AI行为树1 —— 框架介绍

文章目录 1 行为树2 行为树驱动方式3 行为树结点分类3.1 控制节点3.2 执行节点 4 行为树与状态机比较 本文章参考 B 站唐老狮 2023年直播内容。 点击前往唐老狮 B 站主页。 1 行为树 ​ 行为树(Behavior Tree,BT)在游戏 AI 中是一种用于控制…

精益管理意识与企业战略规划的匹配度如何评估?

众所周知,精益管理不仅要求企业在生产运营过程中实现资源的最大化利用,同时也强调企业战略规划的精准性和前瞻性。因此,评估精益管理意识与企业战略规划的匹配度,对于企业的长期发展至关重要。具体方法如深圳天行健企业管理咨询公…

springboot 项目整合 AI (文心一言)

百度智能云网址:https://cloud.baidu.com/?fromconsole 注册——个人认证——登录成功 第一步:点击千帆大模型平台 ​ 第二步:点击应用接入——创建应用 ​ 第三步:点击接口文档——API列表——可以点击指定模型进行查看调…

【中项】系统集成项目管理工程师-第4章 信息系统架构-4.4数据架构

前言:系统集成项目管理工程师专业,现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试,全称为“全国计算机与软件专业技术资格(水平)考试”&…

hamcrest 断言框架使用示例和优势分析

引言 在软件测试领域,断言是验证代码行为是否符合预期的关键环节。Hamcrest 断言框架,以其独特的匹配器(Matcher)概念和清晰的失败信息,赢得了广泛的赞誉。尽管 Python 标准库中没有内置的 Hamcrest 库,但…

【前端新手小白】学习Javascript的【开源好项目】推荐

目录 前言 1 项目介绍 1.1 时间日期类 1.2 网页store类 1.3 事件类 1.4 Number类 1.5 String类 1.6 正则验证类 1.7 ajax类 1.8 data数据类 1.9 browser浏览器类 2 学习js-tool-big-box开源项目时有哪些收获 2.1 你可以这样做 2.2 如果你需要使用本项目 2.3 你…

开局一个启动器:从零开始入坑ComfyUI

前几天刷某乎的时候看到了一位大佬写的好文,可图 IP-Adapter 模型已开源,更多玩法,更强生态! - 知乎 (zhihu.com) 久闻ComfyUI大名,决定试一下。这次打算不走寻常路,不下载现成的一键包了,而是…

矩估计与最大似然估计的通俗理解

点估计与区间估计 矩估计与最大似然估计都属于点估计,也就是估计出来的结果是一个具体的值。对比区间估计,通过样本得出的估计值是一个范围区间。例如估计一个人的年龄,点估计就是最终直接估计年龄为50岁,而区间估计是最终估计的…

Qt源码交叉编译带openssl的Qt版本

一.背景 近期项目由于对接的后台服务是https的,之前交叉编译的Qt是不带openssl的,为了能支持https,必须要重新编译Qt。 二.环境 环境准备: Ubuntu版本 :18.04; openssl 版本:1.1.1.g&#xff1b…

go-kratos 学习笔记(5) 删除hellword从新生成服务

go-kratos 删除hellword模版里的服务,从新生成服务users 直接删除api/helloword 目录,在api目录下新建 users目录;使用命令生成新的proto #项目根目录执行 kratos proto add api/users/user.protobuf generate生成的user.proto需要根据自己…

CCS光源的高输出TH2系列平面光源

光源在机器视觉系统中起着重要作用,不同环境、场景及应用合适光源都不一样,今天我们来看看高输出TH2系列平面光源。它可以对应高速化的生产线,为提高生产效率做出贡献。 TH2系列光源的特点: 1、实现了更高一级的高亮度 实现了更…

Ubuntu22.04系统安装nodejs 14 保姆级教程

下载软件包 从NodeSource 的官方源下载并安装 Node.js 14.x 版本的软件包,适用于 Debian 和 Ubuntu 系统: curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash - 更新软件源 更新软件源 sudo apt-get update 下载bodejs14 下载nodejs14 sud…

Doris全方位教程+应用实例

Impala性能稍领先于presto,但是presto在数据源支持上非常丰富,包括hive、图数据库、传统关系型数据库、Redis等 缺点:这两种对hbase支持的都不好,presto 不支持,但是对hdfs、hive兼容性很好,其实这也是顺理成章的&…

单片机学习(18)--红外遥控器

红外遥控器 17.1红外遥控的基础知识1.红外遥控简介2.硬件电路3.基本发送和接收4.NEC编码5.遥控器键码6.51单片机的外部中断7.外部中断寄存器 17.2红外遥控的程序代码1.红外遥控(1)工程目录(2)main.c函数(3)…

ubuntu在命令行输出里查找内容,dmesg

直接执行查看日志指令会出来很多页。dmesg为开机日志信息。记录了开机时硬件的过程 sudo dmesg 执行结果: 可以用竖号“|”,在前一条命令返回的内容进行查找。下图为查找bluetooth sudo dmesg |grep -i bluetooth

ufw命令简介

正文共:3999 字 41 图,预估阅读时间:4 分钟 我们之前在CentOS系统中研究了iptables命令的使用方法(iptables命令简介),还做了简单的应用实验(如何通过iptables配置URL过滤黑名单?、如…

DevExpress WinForms自动表单布局,创建高度可定制用户体验(二)

使用DevExpress WinForms的表单布局组件可以创建高度可定制的应用程序用户体验,从自动安排UI控件到按比例调整大小,DevExpress布局和数据布局控件都可以让您消除与基于像素表单设计相关的麻烦。 P.S:DevExpress WinForms拥有180组件和UI库&a…

LeetCode:删除排序链表中的重复元素(C语言)

1、问题概述:给定一个已排序链表的头,删除重复元素,返回已排序的链表 2、示例 示例 1: 输入:head [1,1,2] 输出:[1,2] 示例 2: 输入:head [1,1,2,3,3] 输出:[1,2,3] 3…

【Python】一文向您详细介绍 K-means 算法

【Python】一文向您详细介绍 K-means 算法 下滑即可查看博客内容 🌈 欢迎莅临我的个人主页 👈这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地!🎇 🎓 博主简介:985高校的普通本硕&#xff…

set 与 map底层实现

目录 set与map底层基本介绍​ 红黑树的迭代器 operator 情况一:右不为空 情况二:右为空 operator-- 情况一:end()-- 情况二:左子树不为空 情况三:右子树不为空 项目代码 RBTree.h myMap.h mySet.h set与m…