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

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.xdnf.cn/news/1486689.html

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

相关文章

ROS2入门到精通—— 2-8 ROS2实战:机器人安全通过狭窄区域的方案

0 前言 室内机器人需要具备适应性和灵活性,以便在狭窄的空间中进行安全、高效的导航。本文提供一些让机器人在狭窄区域安全通过的思路,希望帮助读者根据实际开发适当调整和扩展 1 Voronoi图 Voronoi图:根据给定的一组“种子点”&#xff0…

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…

Jenkins卡在等待界面解决方法

一、问题 部署jenkins服务器出现Please wait while Jenkins is getting ready to work。 二、原因分析 jenkins里面文件指向国外的官网,因为防火墙的原因连不上。 三、解决方法 将配置文件里面的url换成国内镜像: (1)修改配…

WEB攻防-通用漏洞-SQL 读写注入-MYSQLMSSQLPostgreSQL

什么是高权限注入 高权限注入指的是攻击者通过SQL注入漏洞,利用具有高级权限的数据库账户(如MYSQL的root用户、MSSQL的sa用户、PostgreSQL的dba用户)执行恶意SQL语句。这些高级权限账户能够访问和修改数据库中的所有数据,甚至执行…

2024/7/23 英语每日一段

As malware has improved and evolved, it has pushed defense software to require constant connection and more extensive control. That deeper access also introduces a far higher possibility that security software—and updates to that software—will crash the …

RabbitMQ入门详解

前言 本篇文章将详细介绍rabbitmq的基本概念知识,以及rabbitmq各个工作模式在springboot中如何使用。 文章目录 介绍 简介 RabbitMQ 核心 生产者与消费者 Exchange Queue 工作模式 简单模式 工作队列模式 发布订阅模式 路由模式 主题模式 SpringBoot中…

java算法day21

java算法day21 77 组合216 组合总和Ⅲ17 电话号码的数字组合39 组合总和 这个阶段所要解决的问题都是有关回溯算法。 所以说解法基本上都是围绕回溯算法模板来完成。回溯算法可以这样总结,树形结构,横向遍历,纵向递归。递归出口收货结果并终…

(十九)原生js案例之h5地里位置信息与高德地图的初使用

h5 地里位置信息 1. 获取当前位置信息 window.onload function () {const oBtn document.querySelector("#btn");const oBox document.querySelector("#box");oBtn.onclick function () {window.navigator.geolocation.getCurrentPosition(function (…

c++----模版进阶

c----模版初阶:http://t.csdnimg.cn/PiYoD 一.非类型模版参数 模板参数除了可以是类型,还可以是常量。 例如: 这样就可以在类中使用这个n常量。 -------------------------------------------------------------------------------------…

鸿蒙OS物联网创新应用实训解决方案

摘要: 随着物联网技术的飞速发展,各种智能设备和传感器正在以前所未有的速度融入我们的日常生活。华为推出的鸿蒙操作系统(HarmonyOS)作为一款面向全场景、多设备、无缝连接的分布式操作系统,为物联网领域带来了全新的…

机器学习 | 回归算法原理——最小二乘法

Hi,大家好,我是半亩花海。很早便想学习并总结一本很喜欢的机器学习图书——立石贤吾的《白话机器学习的数学》,可谓通俗易懂,清晰形象。那就在此分享并作为学习笔记来记录我的学习过程吧!本章的回归算法原理基于《基于…

【时序约束】读懂用好Timing_report

一、静态时序分析: 静态时序分析(Static Timing Analysis)简称 STA,采用穷尽的分析方法来提取出整个电路存在的所有时序路径,计算信号在这些路径上的传播延时,检查信号的建立和保持时间是否满足时序要求&a…

centos系统mysql主从复制(一主一从)

文章目录 mysql80主从复制(一主一从)一、环境二、服务器master1操作1.开启二进制日志2. 创建复制用户3. 服务器 slave1操作4. 在主数据库中添加数据 mysql80主从复制(一主一从) 一、环境 准备两台服务器,都进行以下操…

linux系统安装python3和pip

一、安装python 1、安装依赖环境 yum install gcc -y yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel yum install zlib zlib-devel openssl -y yum install openssl…

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

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

vscode 搭建 golang 开发环境

介绍 在 vscode 搭建 go 的开发环境需要区分两个方向: go 1.19.0 及其更高版本go 1.19.0 之前的版本 为什么这么分,因为 vscode-go 插件自带的工具安装脚本全部都是装最新版的各类工具,这些工具中有部分要求 go 1.19.0 以上才能安装成功。…

手写RPC-令牌桶限流算法实现,以及常见限流算法

为什么需要服务限流、降级 分布式架构下,不同服务之间频繁调用,对于某个具体的服务而言,可能会面临高并发场景。在这样的情况下,提供服务的每个服务节点就都可能由于访问量过大而引起一系列问题,比如业务处理耗时过长、…

SpringBoot把nacos配置注入时数据注入时出现莫名错误

一、错误详情 我在nacos的配置a是003457 但是注入的数据是1839 二、解决方法 通过加号可以解决这个问题: 数据正确了:

【BUG】已解决:SyntaxError: invalid syntax

SyntaxError: invalid syntax 目录 SyntaxError: invalid syntax 【常见模块错误】 【解决方案】 常见原因及解决方法 解决步骤 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页,我是博主英杰,211科班出身,就职…

Ubuntu22.04离线安装nginx

下载安装包 nginx nginx下载地址,选stable的即可,传到服务器上面,记住上传路径 提示: 下面的openssl,zlib,pcre也可以不下载也可以,我这里是考虑到完全离线下载的情况openssl 这个是https需要弄得,如果生产…