redis数据结构的底层实现

news2024/9/29 18:10:04

文章目录

      • 一.引言
      • 二.redis的特点
      • 三.Redis的数据结构
        • a.字符串
        • b.hash
        • c.list
        • d.set
        • e.zset(有序集合)

一.引言

redis是一个开源的使用C语言编写、支持网络、可基于内存亦可持久化的日志型、key-value的NoSQL数据库。

通常使用redis作为缓存中间件来降低数据库的压力,除此之外,redis还有很多使用场景,比如分布式锁、计数、队列等等。

二.redis的特点

  • 读写速度快。每秒10w次左右。原因
    • 数据存储在内存中,访问速度快
    • 采用单线程的架构,避免上下文的切换和多线程带来的竞争,不存在加锁和释放锁的操作,减少了CPU的消耗
    • 采用了非阻塞IO多路复用机制
  • 数据结构丰富。redis不仅仅支持简单的key-value类型的数据,同时还提供list、set、zset、hash等数据结构
  • 支持持久化。RDB和AOF两种持久化策略
  • 支持高可用。可以使用主从复制,并且提供哨兵机制,保证服务器的高可用
  • 客户端语言多。涵盖了所有主流编程语言

三.Redis的数据结构

常见的五种数据类型:string、list、set、zset、hash

redis的这些数据结构,在底层都是使用redisObject来进行表示的。redisObject有三个重要的属性,分别是type、encoding、ptr。

type代表保存的value的类型。通常有以下五种:

  • 字符串REDIS_STRING
  • 列表 REDIS_LIST
  • 集合 REDIS_SET
  • 有序集合 REDIS_ZSET
  • 字典 REDIS_HASH

encoding表示保存的value的编码,通常有以下几种:

  • 字符串:REDIS_ENCODING_RAW
  • 整数:REDIS_ENCODING_INT
  • 哈希表:REDIS_ENCODING_HT
  • zipmap:REDIS_ENCODING_ZIPMAP
  • 双端链表:REDIS_ENCODING_LINKEDLIST
  • 压缩列表:REDIS_ENCODING_ZIPLIST
  • 整数集合:REDIS_ENCODING_INTSET
  • 跳跃表:REDIS_ENCODING_SKIPLIST

ptr是一个指针,指向实际保存的value的数据结构

数据类型和编码方式是有一定关系的,所以数据类型和编码方式是可以确定底层采用什么数据结构的。

img

a.字符串

字符串对象的encoding有三种,分别是:int、raw、embstr

常用命令有:set、get、decr、incr、mget

底层不是使用c语言的字符串动态类型,而是自己开发了一种数据类型SDS(Simple Dynamic String:动态字符串)进行存储:

struct sdshdr{
 int len;/*字符串长度*/
 int free;/*未使用的字节长度*/
 char buf[];/*保存字符串的字节数组*/
}

SDS与C语言的字符串有什么区别?

  • C语言获取字符串长度是从头到尾遍历,时间复杂度是O(n),而SDS有len属性记录字符串长度,时间复杂度尾O(1)
  • 避免缓冲区溢出。SDS在需要修改时,会先检查空间是否满足大小,如果不满足,则先拓展至所需大小再进行修改操作。
  • 空间预分配。当SDS需要进行扩展时,redis会为SDS分配好内存,并且根据特定的算法分配多余的free空间,避免连续执行字符串带来的内存分配的消耗
  • 惰性释放。如果需要缩短字符串,不会立即回收多余的内存空间,而是用free近路剩余的空间,以便下次扩展时使用,避免了再次分配内存的操作
  • 二进制安全。c语言存储字符串是采用N+1的字符串数据,末尾使用‘\0’标识字符串的结束,如过我们存储的字符串中出现‘\0’,那么就会出现识别出错,而SDS因为记录了字符串的长度len,则没有这个问题。

redis规定了字符串的长度不得超过512M

b.hash

哈希对象的编码方式有两种:ziplist和hashtable

当哈希对象保存的键值对数量小于512,并且所有键值对的长度都小于64自己饿时,使用ziplist存储;否则使用hashtable存储。

redis中的hashtable跟java中的hashMap类似,都是通过数组+链表的实现方式解决部分的哈希冲突。

typedf struct dict{
    dictType *type;//类型特定函数,包括一些自定义函数,这些函数使得key和value能够存储
    void *private;//私有数据
    dictht ht[2];//两张hash表 
    int rehashidx;//rehash索引,字典没有进行rehash时,此值为-1
    unsigned long iterators; //正在迭代的迭代器数量
}dict;

typedef struct dictht{
     //哈希表数组
     dictEntry **table;
     //哈希表大小
     unsigned long size;
     //哈希表大小掩码,用于计算索引值
     //总是等于 size-1
     unsigned long sizemask;
     //该哈希表已有节点的数量
     unsigned long used; 
}dictht;

typedf struct dictEntry{
    void *key;//键
    union{
        void val;
        unit64_t u64;
        int64_t s64;
        double d;
    }v;//值
    struct dictEntry *next;//指向下一个节点的指针
}dictEntry;

img

扩容和收缩的过程:

  • 如果执行扩展操作,会基于哈希表创建一个大小等于ht[0].used*2n的哈希表(每次都是根据原哈希表已使用的空间扩大一倍创建另一个哈希表)。相反如果执行的是收缩操作,每次收缩是根据已使用精简缩小一倍创建一个新的哈希表。
  • 重新利用hash算法,计算索引值,然后将键值对放到新的哈希表位置上。
  • 所有键值对都迁徙完毕后,释放原哈希表的内存空间。

在redis执行扩容和收缩的规则是:

  • 服务器目前没有执行bgsave或bgrewrite命令,并且负载因子大于等于1
  • 服务器目前没有执行bgsave或bgrewrite命令,并且负载因子大于等于5

负载因子=哈希表已保存节点数量/哈希表大小

渐进式rehash

扩容和收缩不是一次性集中式完成,而是通过多次逐渐地完成的。为什么要采用这种方式呢?在键值对数量达到几十万,几百万的键值对要一次性进行rehash,势必会导致redis性能严重下降,自然而然地redis开发者就想到采用渐进式rehash,过程如下:

  • 使用rehashindex字段保存迁移的进度,从0开始
  • 在迁移过程中ht[0]和ht[1]同时保存数据,ht[0]指向旧哈希表,ht[1]指向新hash表,每次对字典进行添加、删除、查找或更新操作是,程序除了执行指定的操作外,还会顺带将ht[0]的元素迁移到ht[1]中
  • 迁移完成后,rehashindex设置为-1
  • rehash完成后,ht[0]指向的旧表会被释放,之后会将新表的持有权交给ht[0],再重置ht[1]指向NULL

渐进式rehash的优缺点

优点:

  • rehash操作分散到每一个字典操作和定时函数上,避免了一次性集中式rehash带来的服务器压力

缺点:

  • rehash期间需要使用两个hash表,内存占用稍大

常用命令

hget、hset、hgetall

c.list

列表对象的编码有两种,分别是:ziplist、linkedlist。当列表的长度小于512,并且所有元素的长度都小于64字节时,使用ziplist存储;否则使用linkedlist存储

redis中的linkedlist类似于java的linkedlist,是一个双向链表,插入和删除操作效率比较快,时间复杂度是O(1)

typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

typedef struct listIter {
    listNode *next;
    int direction;
} listIter;

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;

常用命令:

lpush、rpush、lpop、rpop、lrange

d.set

特点:无序、不重复,跟java的hashset类似。它的编码有两种,分别是intset和hashtable。如果value可以转换成整数值,并且长度不超过512的话就使用intset存储,否则采用hashtable

typedef struct intset{
    uint32_t encoding;//编码方式

    uint32_t length;//集合包含的元素数量

    int8_t contents[];//保存元素的数组
}intset;

encoding的值有三种,分别是INTSET_ENC_INT16、INTSET_ENC_INT32、INTSET_ENC_INT64,代表着整数的取值范围。redis会根据添加进来的元素的大小,选择不同的类型进行存储。尽可能地节省内存空间。

INTSET_ENC_INT16–>INTSET_ENC_INT32–>INTSET_ENC_INT64的升级过程:

  • 根据新元素的类型扩展数组contents的空间
  • 从尾部将数据插入
  • 根据新编码格式重置之前的值,因为这是的contents存在着两种编码的值。从插入的数据的位置,也就是尾部,从后到前将之前的数据按照新的编码格式进行移动和设置。从后往前是为了防止数据被覆盖

优点:节省内存;缺点:升级会消耗系统资源。而且升级是不可逆的,一旦升级,编码就会一直保存升级后的状态

set常见的命令有:

sadd、spop、smembers、sunion

redis为set类型提供了求交集、并集、差集的操作,可以非常方便地实现比如共同关注、共同爱好、共同好友等功能

e.zset(有序集合)

zset和set一样是不可重复的,区别在于多了score值,用来代表排序的权重。也就是当你需要一个有序的,不可重复的集合列表时,就可以考虑使用这种数据类型。

zset的编码方式有两种,分别是:ziplist、skiplist。当zset的长度小于128,并且所有元素的长度都小于64字节时,使用ziplist存储,否则使用skiplist存储。

img

跳远表的数据结构设计如上,设计的好处是什么呢?

查询的时候可以减少时间复杂度,如果是链表,我们要插入并且保持有序的话,那就要从头节点开始遍历,遍历到合适的位置,然后插入,如果这样性能肯定是不理想的。

而使用跳表可以像二分查找一样定位到插入的点,查找过程:

  • L4,查询87,查询一次
  • L3,查询到在24、87之间,需要查询2次
  • L2,查询到48,查询1次
  • L1,查询到37、48,查询两次,确定插入点在37、48之间

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

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

相关文章

CV学习笔记-MobileNet

MobileNet 文章目录MobileNet1. MobileNet概述2. 深度可分离卷积(depthwise separable convolution)2.1 深度可分离卷积通俗理解2.2 深度可分离卷积对于参数的优化3. MobileNet网络结构4. 代码实现4.1 卷积块4.2 深度可分离卷积块4.3 MobileNet定义4.4 完…

linux下使用vscode和cmake高效管理c++项目简明教程

安装vscode及c环境配置可以参见:https://blog.csdn.net/fangshuo_light/article/details/123635576   首先,创建工程目录,并在vscode中打开该文件夹,在里面创建如下文件夹: include:用于存放.h文件src&a…

HBase 一文读懂

本文基于《尚硅谷大数据技术之HBase》编写。HBase 简介HBase定义HBase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库。HBase数据模型HBase的数据模型同关系型数据库(RDMS)很类似,数据存储在一张表中,有行有列。但从HBase的…

c语言tips-大端小端存储介绍和使用union判断大小端

1. 大小端介绍 大端(Big Endian)和小端(Little Endian)是两种CPU或者计算机系统存储数据的方式。 在大端系统中,数据的高位字节(MSB)存储在内存地址的低位,低位字节(LSB…

Linux系统下搭建maven环境

文章目录前述从官网下载安装包安装 maven修改maven配置修改环境变量测试前述 安装 maven 环境前,需要先安装 java 环境,如果没有安装 java 环境,可以参考:https://blog.csdn.net/weixin_45583303/article/details/118631855 从官…

maven的仓库配置、指定jdk编译版本、相关编译命令简介、scope依赖的范围以及依赖的传递性

目录 1、配置阿里云提供的镜像仓库 2、指定jdk编译版本 3、执行 Maven 的构建命令 3.1、清理操作 3.2、编译操作 3.3、测试操作 3.4、打包操作 3.5、安装操作 4、scope依赖的范围 5、依赖的传递性 5.1、概念 5.2、传递的原则 1、配置阿里云提供的镜像仓库 将下面 m…

C++STL之list的模拟实现

目录 一.list准备 二. iterator迭代器 1._list_iterator 2.begin()、end() 3.const_begin()、const_end() 4.!&& 5. && -- 6.operator* 7.operator-> 三.Modify(修改) 1.insert() 2.erase() 3.push_back() && push_front() 4.pop_bac…

MySql触发器学习

文章目录1 触发器1.1介绍1.2 创建触发器1.2 删除触发器1.3查看触发器1 触发器 1.1介绍 触发器是与表有关的数据库对象,指在 insert/update/delete 之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的…

(十八)操作系统-进程互斥的软件实现方法

文章目录一、知识总览二、单标志法三、双标志先检查法四、双标志后检查法五、Peterson算法六、总结一、知识总览 二、单标志法 算法思想:两个进程在访问临界区后,会把使用临界区的权限转交给另一个进程。也就是说每个进程进入临界区的权限只能被另一个进…

Guna Charts WinForm 1.0.8 Crack

Guna Charts 16 图表 在 16 种不同的图表类型中可视化您的数据。 Guna Charts 反应灵敏 轻松响应屏幕尺寸的变化。 Guna Charts 实时图表 创建实时数据仪表板现在非常容易。 Guna Charts 混合图表类型 混合多种图表类型,例如条形图和折线图/面积图。 Guna Charts…

MS9122是一款USB单芯片投屏器,内部集成了USB2 0 控制器和数据收发模块、HDMI 数据接口和音视频处理模块。MS9122可以通过USB接口显示

MS9122是一款USB单芯片投屏器,内部集成了USB2.0 控制器和数据收发模块、HDMI 数据接口和音视频处理模块。MS9122可以通过USB接口显示或者扩展PC、智能手机、平板电脑的显示信息到更大尺寸的显示设备,支持HDMI视频接口。 主要功能特征 HDMI v1.4兼容 最大…

【java基础】包装类,自动装箱和自动拆箱

文章目录基本介绍包装类自动装箱自动拆箱包装类注意事项包装类比较包装器内容不可变基本介绍 有时,需要将int这样的基本类型转换为对象。所有的基本类型都有一个与之对应的类。 例如,Integer类对应基本类型int。通常,这些类称为包装器&#…

网上这么多IT的培训机构,我们该怎么选?

说实话,千万不要把这个答案放在网上来找,因为你只能得到别人觉得合适的或者机构的广告;当然个人的培训经历可以听一听的,毕竟不靠谱的机构也有,比如让你交一两万去上线上课程或者一百号来人坐一起看视频,这…

【django】django-simpleui配置后,后台显示空白页解决方法

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 django后台显示空白页解决方法 1. 正文 添加完simpleui以后,后台显示一片空白,一脸问号??? …

MacBook Pro 休眠后五国,自动重启报错

看了网上很多情况,都说是系统的原因,2018年款的Mac已经过了保修期去过天才吧说花4k 换主板解决,花个几千块去解决这个问题不如折旧加钱再买个新的总之先用应急的办法1.电池偏好设置,接通电源时勾选“ 当显示器关闭时,防…

类的加载过程(生命周期)

类的加载过程(生命周期) 一、装载:通过一个类的全限定名获取定义此类的二进制字节流将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构在内存中生成一个代表这个类的java.lang.Class对象(将字节码加载到内存中),作为…

扫地机器人(蓝桥杯C/C++)

题目描述 小明公司的办公区有一条长长的走廊,由 NN 个方格区域组成,如下图所示。 走廊内部署了 KK 台扫地机器人,其中第 ii 台在第 A_iAi​ 个方格区域中。已知扫地机器人每分钟可以移动到左右相邻的方格中,并将该区域清扫干净。…

DevOps落地与转型:提升研发效能的方法与实践

❤️作者主页:小虚竹 ❤️作者简介:大家好,我是小虚竹。Java领域优质创作者🏆,CSDN博客专家🏆,华为云享专家🏆,掘金年度人气作者🏆,阿里云专家博主&#x1f3…

使用 OpenCV 将图像转换为铅笔素描的 8 个步骤

使用 OpenCV 从彩色图像创建铅笔素描图像这个项目是我为 LetsGrowMore 的数据科学实习生创建的任务。LetsGrowMore :https://letsgrowmore.in/vip/目录什么是 OpenCV?第 1 步:读取图像第 2 步:将图像转换为灰度第 3 步&#xff1a…

MarkDown中写UML图的方法

目录序UML图之顺序图顺序图的四个要素关于消息箭头的语法Mermaid中顺序图的简单例子样例用小人表示对象为对象设置别名激活对象UML图之类图类图中常见的关系关于不同类型关系的语法Mermaid中类图的简单例子样例类定义的两种方式为类定义成员双向关系的表示多重性关系的表示UML之…