Redis 原理

news2025/1/16 3:51:30

Redis 原理

动态字符串SDS

  • Redis中保存的key时字符串,value往往是字符串或字符串集合,字符串是Redis中常见的数据结构

  • Redis没有直接使用C语言中的字符串,因为C语言字符串存在很多问题,使用起来不方便

  • Redis构建了一种新型的字符串数据结构,称为简单动态字符串(Simple Dynamic String),简称SDS,是使用C语言实现的结构体

    struct __attribute__ ((__packed__)) sdshdr8 {
        uint8_t len; //buf已保存的字符串长度,不包含结束标识‘\0’
        uint8_t alloc; //buf申请的总的字节数,不包含结束标识
        unsigned char flages; //不同SDS的头类型,用来控制SDS的头大小有 8为、16为、32为、64为
        char buf[];
    };
    
  • SDS具备自动扩容能力,如果要在原字符串基础上追加字符串,首先会申请内存空间,如果新字符串小于1M,则新空间为扩展后字符串的两倍+1;如果新字符串大于1M,则新空间为扩展后字符串长度+1M+1,称为内存预分配。

  • 优点:获取字符串长度的时间复杂度为O(1)、支持动态扩容、减少内存分配次数、二进制安全

IntSet

  • IntSet是Redis中Set集合的一种实现方式,基于整数数组来实现,具备长度可变、有序等特性

    typedef struct intset {
      	uint32_t encoding; //编码格式,存放格式包括:16位、32位、64位整数
        uint32_t length; //元素个数
        int8_t contents[]; //整型数组,保存集合数据
    } intset;
    
  • 为了方便查找,Redis会将IntSet中所有的整数按升序排列,保存在contents数组中

  • 如果添加的元素超过了存储范围,intSet会自动升级编码格式,找到合适大小,并按照新的编码方式及元素个数扩容数组,倒序依次将数组中的数据拷贝到扩容后的正确位置,并将待添加的数据放入数组末尾

  • intSet特点:确保元素的唯一、有序性;具备类型升级机制,可以节省内存空间;底层采用二分查找方式查询;

  • 因为intSet采用数组,内存空间是连续的,在数据量较大的时候,是不方便的,查找效率也会下降,所以适合在数据量较小时使用

Dict 字典

  • Redis是一个键值型的数据库,然后根据键实现快速的增删改查,而键值的映射关系是通过Dict实现的

  • Dict有三部分组成:哈希表(DictHashTable)、哈希节点(DictEntry)、字典(Dict);与HashMap思想类似

    typedef struct dictht {
        dictEntry **table; //entry数组,数组中存放的是指向entry的指针
        unsigned long size; //哈希表大小
        unsigned long sizemask; //哈希表大小的掩码,总等于size-1,计算hash值用
        unsigned long used; //entry 个数
    } dictht;
    
    typedef struct dictEntry {
        void *key; //键
        union {
            void *val;
            uint64_t u64;
            int64_t s64;
            double d;
        } v; //值
        //下一个Entry的指针
        struct dictEntry *next;
    } dictEntry;
    
  • 当向Dict中添加键值对时,Redis首先根据key计算出hash值h,然后利用h & sizemask(和取余效果相同)来计算元素应该存储到数组中的哪个索引位置

    typedef struct dict {
        dictType *type; //dict类型,内置不同的hash函数
        void *privdata; //私有数据,在做特殊hash运算时使用
        dictht ht[2]; //一个Dict包含两个hash表,其中一个是当前数据,另一个一般是空数据,rehash使用
        long rehashidx; //rehash的进度,-1表示为进行
        int16_t pauserehash; //rehash是否暂停
    } dict;
    

Dict扩容及rehash

  • 当集合元素较多时,导致哈希表冲突增多,链表过长,查询效率降低
  • Dict在每次新增键值对时都会检查负载因子(LoadFactor=used/size),满足一下两种情况会触发哈希表扩容:
    • 哈希表 LoadFactor >= 1 ,并且服务器没有执行 bgsave 或 bgrewriteaof 等后台进程
    • 哈希表 LoadFactor >= 5
  • 扩容也会扩容到大于容量最小的2的n次方
  • Dict除了扩容,每次删除元素时,也会对负载因子做检查,当LoadFactor<0.1时,会做哈希表收缩
  • rehash并不是一次性完成的,因为dict的数据量如果是百万级别的Entry,依次rehash极有可能导致主线程阻塞,因此采用渐进式的rehash过程
    • 不管扩容还是收缩,必定会创建新的哈希表,导致哈希表的size和sizemask变化,而key的查询跟sizemask有关,因此必须对哈希表中的每一个key重新计算索引,插入新的哈希表
    • 首先会计算hash表的realSize,值取决于扩容还是收缩
    • 按照新的realSize申请空间,创建Dictht,并赋值给dict.ht[1]
    • 设置dict.rehashidx=0,表示开始rehash
    • 每次增删改查时,都检查一下dict.rehashidx是否大于-1,如果是,则将dict.ht[0].table[rehashidx]的Entry链表rehash到dict.ht[1],并且将rehashidx++;直至dict.ht[0]的所有数据都rehash到dict.ht[1]
    • 将dict.ht[1]赋值给dict.ht[0],给dict.ht[1]初始化为空哈希表,释放原来的dict.ht[0]的内存
    • 将rehashidx赋值为-1,代表rehash结束
    • 在rehash过程中新增操作,直接写入ht[1],查询、修改和删除则会在dict.ht[0]和dict.ht[1]依次查询,确保ht[0]中的数据只减不增,随着rehash最终清空

ZipList

ZipList是一种特殊的“双端链表”,由一系列特殊编码的连续内存组成,可以在任意一端进行压入/弹出操作,并且该操作的时间复杂度为O(1)

在这里插入图片描述

  • ZipList 中的Entry并不像普通链表那样记录前后节点的指针,因为记录两个指针要占用16字节,浪费内存,而是采用下面的结构:
    在这里插入图片描述

    • previous_entry_length:前一节点的长度,占用1个或5个字节
    • encoding:编码属性,记录content的数据类型(字符串还是整数)以及长度,占用1个、2个或5个字节
    • content:负责保存节点数据,可以是字符串或整数
  • 特点:

    • 压缩列表可以看做是一种连续内存空间的“双向链表”
    • 列表之间不是通过指针连接,而是记录上一节点和本节点长度来寻址,内存占用较低
    • 如果列表数据过多,导致链表过长,可能影响查询性能
    • 增或删较大数据时有可能发生连续更新问题

QuickList

  • ZipList虽然节省内存,但申请内存空间必须是连续的,如果内存占用较多,申请内存效率很低,该怎么办?

  • 在Redis3.2中,引入了新的数据结构QuickList,他是一个双端链表,只不过链表中的每一个节点都是一个ZipList

  • QuickList特点:

    • 是一个节点为ZipList的双端链表
    • 节点采用ZipList,解决了传统链表的内存占用问题
    • 控制了ZipList大小,解决连续内存空间申请效率问题
    • 中间节点可以压缩,进一步节省内存

SkipList

在这里插入图片描述

SkipList称为跳表,它是一种链表,但与传统的链表相比有几点差异:

  • 元素按照升序排列存储
  • 节点可能包含多个指针,指针跨度不同

特点:

  • 跳表是一个双向链表,每一个节点都包含score和ele值
  • 节点按照score值排序,score值一样则按照ele字典排序
  • 每一个节点都可以包含多层指针,层数时1到32之间的随机数
  • 不同层指针到下一个节点的跨度不同,层级越高,跨度越大
  • 增删改查效率与红黑树基本一致,实现却更简单

RedisObject

Redis中任意数据类型的键值都会被封装为一个RedisObject,也叫Redis对象,不包含数据时占16字节

typedef struct redisObject {
    unsigned type:4; //对象类型,分别是string hash list set zset,占4个bit位
    unsigned encoding:4; //底层编码方式,有11中,占4bit位
    unsigned lru:LRU_BITS; //lru表示该对象最后一次被访问的时间,占24bit位
    int refcount; //引用计数器,计数器为0表示无引用,可以被回收
    void *ptr; //指针,指向存放实际数据的空间
} robj;

Redis中会根据存储的数据不同,选择不同的数据类型,选择不同的编码格式:

数据类型编码格式
stringint、embstr、raw
listlinkedList和ZipList(3.2以前)、QuickList(3.2以后)
setintset、hashTable(Dict)
zsetZipList、hashTable+SkipList
hashZipList、hashTable

Redis 内存回收

Redis是基于内存存储,单节点Redis内存不宜过大,会影响持久化或者主从同步性能,可以修改配置文件来设置Redis的最大内存:

maxmemory 1gb; #最大内存为 1gb

Redis 过期策略

通过expire命令给Redis的key设置TTL(存活时间),当key的TTL到期之后,对应的内存也得到释放

DB结构:Redis本身是一个典型的key-value内存存储数据库,因此所有的key、value都保存在Dict结构中,不过在database结构体中,有两个Dict:一个用来记录key-value;另一个记录key-TTL

在这里插入图片描述

typedef struct redisDb {
    dict *dict; //存放了所有key及value,也称keyspace
    dict *expires; //存放每一个key的ttl存活时间,只包含设置了ttl的key
    dict *blocking_keys;
    dict *ready_keys;
    dict *watched_keys;
    int id;
    long long avg_ttl;
    unsigned long expires_cursor;
    list *defrag_later;
} redisDb;

key的TTL到期后是否立即删除?

  • 惰性删除:并不是到期之后立刻删除,而是在访问该key的时候,检查key的存活时间,如果已经过期,则删除
  • 周期删除:通过一个定时任务,周期性的抽样部分过期的key,然后执行删除操作

Redis 淘汰策略

内存淘汰:当Redis内存使用达到设置的阈值时,Redis主动挑选部分key删除以释放更多内存的流程

Redis 支持8中淘汰策略:

  • noeviction:不淘汰任何key,但是内存满时不允许写入数据,时Redis的默认淘汰策略
  • volatile-ttl:对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰
  • allkeys-random:对全体key,随机淘汰
  • volatile-random:对设置了TTL的key,随机淘汰
  • allkeys-lru:对全体key,基于lru算法进行淘汰
  • volatile-lru:对设置了TTL的key,基于lru算法进行淘汰
  • allkeys-lfu:对全体key,基于lfu算法进行淘汰
  • volatile-lfu:对设置了TTL的key,基于lfu算法进行淘汰

LRU(Least Recently Used),最少使用,用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高

LFU(Least Frequently Used),最少使用频率,会统计每个key的访问频率,值越小,淘汰优先级越高

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

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

相关文章

Web网页制作-知识点(3)——HTML5新增标签、CSS简介、CSS的引入方式、选择器、字体属性、背景属性、表格属性、关系选择器

目录 HTML5新增标签 CSS简介 CSS概念 CSS的作用 语法 CSS的引入方式 内联样式&#xff08;行内样式&#xff09; 内部样式 外部样式&#xff08;推荐&#xff09; 选择器 全局选择器 元素选择器 类选择器 ID选择器 合并选择器 选择器的优先级 字体属性 …

Linux——文件基础IO的文件描述符和重定向实现理解

目录 前言&#xff1a; 首先来回顾一下open函数&#xff0c;即在进程中同时打开多个文件&#xff1a; Linux底层进程与文件的关系 &#xff1a; 二.重定向的实现 什么是重定向&#xff1f; 方法1&#xff1a; 2.1关闭stdin&#xff1a; 运行结果&#xff1a; ​编辑由结果知…

统计字符串数组中各元素中指定字符串出现的次数numpy.char.count()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 统计字符串数组中各元素中 指定字符串出现的次数 numpy.char.count() [太阳]选择题 下列代码最后输出的结果是&#xff1f; import numpy as np s np.array([I, Love, Python]) print("…

ChatGPT底层架构Transformer技术及源码实现(二)

ChatGPT底层架构Transformer技术及源码实现(二) Gavin大咖微信:NLP_Matrix_Space 3.2 图解Transformer精髓之架构设计、数据训练时候全生命周期、数据在推理中的全生命周期、矩阵运算、多头注意力机制可视化等 如图3-14所示,是Transformer编解码的示意图,中间有个关键内…

LFS11.3在VMware中安装后需要做的准备

参考lfs 11.3和Blfs 11.3 先简单罗列一下要做的步骤&#xff0c;后续有机会再补充一下细节&#xff0c;遇到问题欢迎读者留言。 1、配置vmware中的网络连接 使用vmware net8 net模式&#xff0c;选用VMnet 配置网络连接/etc/sysconfig/ 目录下ifconfig.*** &#xff08;***为…

fanuc机器人安装profinet IO基板产生报警

fanuc机器人安装profinet IO基板产生报警: SYST-302 请关闭电源 PRIO-397 PMIO 固件需要更新 %x %x 问题描述:新的R30iB‐Plus柜的GSDML 文件与R30iB柜的GSDML文件是不同的,GSDML文件与R834固件版本不匹配的话,会无法扫描到R834的卡,导致无法通讯 解决方法:确认 Expecte…

Diffusion Models: 方法和应用的综合调查 【01】Diffusion Models基础

Diffusion Models: 方法和应用的综合调查 【01】Diffusion Models基础 原文链接&#xff1a;Diffusion Models: 方法和应用的综合调查 【01】Diffusion Models基础 GitHub: https://github.com/YangLing0818/Diffusion-Models-Papers-Survey-Taxonomy. Paper&#xff1a; https…

MySQL学习基础篇(一)

一、数据库概述 1. 为什么要使用数据库 持久化(persistence)&#xff1a;把数据保存到可掉电式存储设备中以供之后使用。大多数情况下&#xff0c;特别是企业级应用&#xff0c;数据持久化意味着将内存中的数据保存到硬盘上加以”固化”&#xff0c;而持久化的实现过程大多通…

程序员编程效率的大敌:中断与上下文切换

程序员编程效率的大敌&#xff1a;中断与上下文切换 首先解释一下中断和上下文切换&#xff1a; 中断: 编程时被打断, 比如被聊天软件/电子邮件/电话/当面打断等&#xff1b;上下文切换&#xff1a;即任务的切换&#xff0c;有自己主动切换&#xff0c;有伴随中断的新任务&am…

C# 静态构造函数学习

静态构造函数用于初始化类中的静态数据或执行仅需一次的特定操作&#xff0c;静态构造函数将在创建第一个实例或引用类中的静态成员之前自动调用。 静态构造函数具有以下特点&#xff1a; 静态构造函数不使用访问权限修饰符修饰或不具有参数&#xff1b; 类或结构体中…

Proxmox VE 8 发布 - 开源虚拟化管理平台

Proxmox VE 8 发布 - 开源虚拟化管理平台 请访问原文链接&#xff1a;https://sysin.org/blog/proxmox-ve-8/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 宣布 Proxmox 虚拟环境的主要版本 8.0&#xff01;它基于出色的 De…

SkyWalking--用代码手动获取traceId的方法

原文网址&#xff1a;SkyWalking--用代码手动获取traceId的方法_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Java项目如何用代码手动获取SkyWalking的traceId。 引入依赖 <dependency><groupId>org.apache.skywalking</groupId><artifactId>apm-tool…

【PCB专题】如何使用Assign color在 Allegro 中快速区别不同网络?

在PCB Layout中经常要查看网络走线,比如电源路径是否合理,线宽是否合适,网络是否形成环路等等。一般我们使用的是高亮网络来查看。 困扰 如果是单一网络这样做是没有什么问题的,但如果是多条网络,就一种颜色会很难看清。就算不同的网络是不同的条纹,在布线比较密集的时…

JavaScript 手写代码 第三期

文章目录 1. 为什么要手写代码&#xff1f;2. 手写代码2.1 函数柯里化2.1.1 基本使用2.1.2 手写实现 2.2 sleep函数2.2.1 简单使用2.2.2 手写实现 2.3 Object.assign() 方法2.3.1 基本使用2.3.2 具体示例2.3.3 具体思路2.3.4 具体实现 1. 为什么要手写代码&#xff1f; 我们在…

ChatGPT底层架构Transformer技术及源码实现(三)

ChatGPT底层架构Transformer技术及源码实现(三) 贝叶斯Bayesian Transformer数学推导论证过程全生命周期详解及底层神经网络物理机制剖析 Gavin大咖微信:NLP_Matrix_Space 从数学的角度来讲,线性转换 其中函数g联合了所有头的操作结果,每个头的产生是采用一个f_att的…

RedHat红帽认证---RHCE

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; RHCE 1.安装和配置 Ansible 安装和配置 Ansible按照下方所述&#xff0c;在控制节点 control 上安装和配置 Ansible&#xff1a;安装所需的软件包创建名为 /home/gre…

认识区块链

文章目录 前言从交易说起线下交易&线上交易存在的隐患线上交易隐患引发的思考 货币发展史解决线上交易存在的隐患比特币的诞生比特币价值的产生 比特币&区块链 前言 我想大多数的 IT 人&#xff0c;即便不是 IT 人&#xff0c;或多说少都听说过“比特币”“区块链”这…

InceptionNext:当Inception遇到ConvNeXt

摘要 https://arxiv.org/pdf/2303.16900.pdf 受 Vision Transformer 长距离依赖关系建模能力的启发&#xff0c;大核卷积最近被广泛研究和采用&#xff0c;以扩大感受野和提高模型性能&#xff0c;如采用77深度卷积的杰出工作connext。虽然这种深度算子只消耗少量的flop&…

初识mysql数据库之数据库介绍

目录 一、什么是数据库 1. 数据库的概念 2. 为什么要有数据库 3. 数据库样例 二、 主流数据库 三、服务器、数据库和表之间的关系 四、mysql存储架构 五、sql语句分类 一、什么是数据库 1. 数据库的概念 如果大家现在已经安装好了mysql&#xff0c;想必大家应该也都知…