Redis 原理

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的访问频率,值越小,淘汰优先级越高

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

TypeScript 【类型推断】与【类型别名】的使用解读

什么是类型推断&#xff1f; 在 TypeScript 中&#xff0c; 如果声明变量时&#xff0c;没有明确的指定类型&#xff0c;那么 TypeScript 会依照类型推论&#xff08;Type Inference&#xff09;的规则推断出一个类型。 以下代码虽然没有明确指定类型&#xff0c;但是会在编译的…

应急响应:系统入侵排查指南

目录 系统基本信息排查 Windows系统排查 Linux系统排查 CPU信息 操作系统信息 载入模块排查 用户排查 Windows系统用户排查 排查所有账户 Linux用户排查 root账户排查 查看所有可登录账户 查看用户错误的登录信息 查看所有用户最后登录信息 排查空口令账户 启…

【Flutter】如何在 Flutter 中获取设备 ID

文章目录 一、 前言二、 设备 ID 的重要性1. 什么是设备 ID2. 设备 ID 的作用 三、 在 Flutter 中获取设备 ID1. 需要的工具和库2. 简单代码示例3. 完整可以运行的代码 四、 注意事项1. 权限问题2. 设备兼容性问题 五、 总结 一、 前言 在移动应用开发中&#xff0c;有时我们需…

面试官: 请你讲下AMS在Android起到什么作用……

心理分析&#xff1a;这道题在发生在大多数场景下。面对这道题 很多求职很茫然&#xff0c;不知道该如何说起。AMS本身比较复杂难以理解。工作多年也很难弄清AMS的作用&#xff0c;其实我们大可从以下几点入手组件启动、进程切换、Crash异常入手 求职者:AMS难以表述 我们就从最…

【机器学习】PCA案例的python实现

一、说明 虽然可以通过更改优化算法来加快机器学习算法的拟合速度&#xff0c;但加快算法速度的更常用方法是使用主成分分析 &#xff08;PCA&#xff09;。如果您的学习算法由于输入维度太高而太慢&#xff0c;那么使用 PCA 加速它可能是一个合理的选择。这可能是PCA最常见的应…

正则表达式-捕获组,命名捕获组,非捕获组

正则表达式的作用 测试目标字符串是否符合规则 返回true/false按照规则从目标字符串提取内容 返回匹配的数组 在线测试工具 regex101: build, test, and debug regexRegular expression tester with syntax highlighting, explanation, cheat sheet for PHP/PCRE, Python, …

【单片机】STM32F103C8T6 最小系统板原理图

STM32F103C8T6是一款基于ARM Cortex-M3内核的32位微控制器&#xff0c;由STMicroelectronics&#xff08;ST&#xff09;公司生产。它是STMicroelectronics的STM32系列微控制器中的一员&#xff0c;被广泛应用于嵌入式系统和电子设备中。 STM32F103C8T6单片机的主要特点和资源…

基于.Net6使用YoloV8的分割模型

前言 在目标检测一文中&#xff0c;我们学习了如何处理Onnx模型&#xff0c;并的到目标检测结果&#xff0c;在此基础上&#xff0c;本文实现基于.Net平台的实例分割任务。 执行YoloV8的分割任务后可以得到分割.pt模型。由于Python基本不用于工业软件的部署&#xff0c;最终还…

SpringBoot 如何使用 @RequestBody 进行数据校验

SpringBoot 如何使用 RequestBody 进行数据校验 在 Web 开发中&#xff0c;前台向后台发送数据是非常常见的场景。而在 SpringBoot 框架中&#xff0c;我们通常使用 RequestBody 注解来接收前台发送的 JSON 数据&#xff0c;并将其转化为 Java 对象。但是&#xff0c;接收到的…

Zookeeper集群的特点

一、Zookeeper集群的特点 Zookeeper:一个领导者 (Leader)&#xff0c;多个跟随者 (Follower) 组成的集群集群中只要有半数以上节点存活&#xff0c;Zookeeper集群就能正常服务。所以Zookeeper适合安装奇数台服务器全局数据一致:每个Server保存一份相同的数据副本&#xff0c;C…

git——使用ssh连接远程仓库

文章目录 前言一. 获取邮箱和密码1. 本地配置你的名字和邮箱2. 使用命令获取你本地的邮箱和密码 二、生成ssh公钥1.任意一个文件夹路径打开Git Bash Here并输入以下命令连按三次回车2. 根据上面红框部分的地址打开文件夹3. 打开并查看id_rsa.pub 文件 三、在GitHub上连接ssh1. …

UE5.1.1 C++从0开始(15.作业4个人作业分享)

教程链接&#xff1a;https://www.bilibili.com/video/BV1nU4y1X7iQ 好吧这个作业应该是之前写的&#xff0c;但是我发现我没写&#xff0c;后面我又回去自己写了一遍再看代码&#xff0c;感觉上大差不差&#xff0c;各位可以看着我的和老师的还有自己的对比下。 SBTService_…

[LeetCode周赛复盘] 第 107 场双周赛20230624

[LeetCode周赛复盘] 第 107 场双周赛20230624 一、本周周赛总结6898. 字符串连接删减字母1. 题目描述2. 思路分析3. 代码实现 6895. 构造最长的新字符串1. 题目描述2. 思路分析3. 代码实现 6898. 字符串连接删减字母1. 题目描述2. 思路分析3. 代码实现 6468. 统计没有收到请求…

Consul 理解

Consul是google开源的一个使用go语言开发的服务发现、配置管理中心服务。内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案&#xff0c;不再需要依赖其他工具&#xff08;比如ZooKeeper等&#xff09;。服务部署简单&#xff0c;只有一…

(转载)支持向量机(support vector machine, SVM)的分类(matlab实现)

支持向量机(support vector machine,SVM)是一种新的机器学习方法&#xff0c;其基础是Vapnik 创建的统计学习理论(statistical learning theory,STL)。统计学习理论采用结构风险最小化(structural risk minimization,SRM)准则&#xff0c;在最小化样本点误差的同时&#xff0c;…

HTML点击显示、点击隐藏details 标签

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>菜鸟教程(runoob.com)</title> </head> <body><details> <summary>Copyright 1999-2011.</summary> <p> - by Refsnes Da…

Redis数据类型

Redis数据类型 一、String数据类型1、概述2、实验 二、List数据类型1、概述2、实验 三、Hash数据类型&#xff08;散列类型&#xff09;1、概述2、实验 四、Set数据类型&#xff08;无序集合&#xff09;1、概述2、应用范围3、实验 五、Sorted Set数据类型&#xff08;zset、有…

JAVA开发与运维(怎么通过docker部署微服务jar包)

目标&#xff1a; 通过docker的方式部署微服务。 一、背景&#xff1a; 我们通过java开发的微服务可以打成jar包&#xff0c;我们可以直接通过裸机部署&#xff0c;也可以通过docker来部署&#xff0c;本文介绍通过docker来部署微服务。 二、首先我们介绍一下docker的发展过程…

2022 年第十二届 MathorCup 高校数学建模挑战赛D题思路(移动通信网络站址规划和区域聚类问题)

目录 一、前言 二、问题背景 三、问题 四、解题思路 &#xff08;1&#xff09;针对问题1&#xff1a; &#xff08;2&#xff09;针对问题2&#xff1a; &#xff08;3&#xff09;针对问题3&#xff1a; 五、附上几个典型代码 &#xff08;1&#xff09;K-means算法…

【HTTP 协议】掌握 Web 的核心技术

哈喽&#xff0c;大家好~我是你们的老朋友&#xff1a;保护小周ღ 谈起 HTTP 协议&#xff08;超文本传输协议&#xff09;&#xff0c;不知道大家第一次是从什么地方了解到这个协议的呢&#xff1f;在真实的网络环境中网络协议的种类非常多&#xff0c;其中有一些耳熟能详的…