当前位置: 首页 > ai >正文

Redis对象编码

前言

Redis中提供多种数据结构:string、list、map、set、zset等,关于上述多种数据类型的底层实现原理,Redis针对不同的数据类型对应的不同使用场景从时间和空间上进行平衡选择不同的对象编码方式。本文大致介绍一些Redis对象编码方式以及在上述五种数据类型上的应用。

一、统一对象结构:redisObject

在Redis中所有的数据都通过redisObject结构体封装,其核心字段定义如下:

typedef struct redisObject {unsigned type:4;            // 数据类型(string/list/hash/set/zset)unsigned encoding:4;        // 底层编码(如int/ziplist/skiplist)unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or* LFU data (least significant 8 bits frequency* and most significant 16 bits access time). */// 内存淘汰信息int refcount;               // 引用计数void *ptr;                  // 指向实际数据的指针
} robj;

通过redisObject结构体中type字段和encoding字段分离,实现对不同的数据类型动态适配合适的编码方式。用户使用方式上,无需考虑底层编码方式只需要按照需求选择对应数据类型。

二、核心数据类型

1、简单动态字符串(Simple Dynamic String - SDS)

Redis中描述字符串使用SDS,对比C语言传统字符串,SDS在获取字符串长度上有更快的速度优势,同时保证二进制安全。C语言字符串以'\0'结尾,SDS通过len字段标识字符串长度,因此在存储二进制数据时保证二进制安全。并且SDS兼容C语言字符串,在buf字段末尾保留'\0',可以兼容部分C语言字符串操作函数。具体实现如下:

struct __attribute__ ((__packed__)) sdshdr5 {unsigned char flags; /* 3 lsb of type, and 5 msb of string length */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; /* used */uint8_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {uint16_t len; /* used */uint16_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {uint32_t len; /* used */uint32_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {uint64_t len; /* used */uint64_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */char buf[];
};
  • len :已使用字节长度(字符串实际长度,不包括'\0')

  • alloc:分配的总字节长度(不包括header)

  • flags:SDS类型标识

  • buf[]:柔性数组,存放具体的字符串 + '\0'

2、字典(Dict)

Redis中dict数据类型是使用拉链法处理hash冲突的hash表结构。其中关键数据结构为:dict、dictht、dictEntry。具体实现以及层级结构如下:

typedef struct dictEntry {void *key;                  //  key值union {void *val;uint64_t u64;int64_t s64;double d;} v;                        //  value值struct dictEntry *next;     //  指向下个元素,拉链法处理hash冲突
} dictEntry;                    //  元素
​
typedef struct dictType {uint64_t (*hashFunction)(const void *key);void *(*keyDup)(void *privdata, const void *key);void *(*valDup)(void *privdata, const void *obj);int (*keyCompare)(void *privdata, const void *key1, const void *key2);void (*keyDestructor)(void *privdata, void *key);void (*valDestructor)(void *privdata, void *obj);
} dictType;                     // dict操作函数集合
​
/* This is our hash table structure. Every dictionary has two of this as we* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {dictEntry **table;          //  元素数组unsigned long size;         //  hash表长度unsigned long sizemask;     //  用于映射位置的掩码,值永远等于 (size - 1),索引值=Hash值&掩码值unsigned long used;         //  hash表当前包含的节点数量
} dictht;                       //  具体hash表结构
​
typedef struct dict {dictType *type;             //  该字典对应的特定操作函数 (hash, keyDup, keyCompare, ...)void *privdata;             //  上述类型函数对应的可选参数dictht ht[2];               //  两张hash表,方便rehah操作long rehashidx; /* rehashing not in progress if rehashidx == -1 */      //  rehash标识unsigned long iterators; /* number of iterators currently running */    //  当前运行的迭代器数量
} dict;                         //  hash表

3、双向链表(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;

4、压缩列表(ziplist)- Redis 7.0 起替换为 listpack

ziplist结构是一块连续内存存储一个字节数组,其中顺序存储多个元素,每个节点元素存储一个字节数组或者一个整数。

  • zlbytes: 4 字节,列表总字节数(字节长度)。

  • zltail: 4 字节,列表尾节点偏移量。

  • zllen: 2 字节,节点数量 (超过 65535 时需遍历)。

  • entry: 若干节点。每个节点包含:

    • prevlen: 前一个节点的长度 (1 或 5 字节)。

    • encoding: 内容编码 (类型和长度,1/2/5 字节)。

    • content: 实际数据。

  • zlend: 1 字节,结束标志 0xFF(255)

对于节点的数据结构中prevlen记录的是前一个节点的长度,此时如果调整当前节点,会影响后面一个节点的prevlen,此时如果是发生的扩展操作,那么可能会导致连续重新分配多个节点。此时该数据结构效率严重下降。因此这也是listpack替代它的主要原因。

5、紧凑列表(listpack) - Redis5.0引入,7.0开始列表/哈希/有序集合的ziplist实现替换为listpack

listpack是对ziplist的优化。其结构也是一块连续内存。与ziplist的主要不同点为在节点元素中ziplist的节点长度信息保存的是前一个节点的长度,而ziplist保存的是自身的长度信息。

6、整数集合(intSet)

整数集合是一块连续内存,其元素保存的是一个整数,其中通过encoding字段中标识了当前元素的整数类型,length保存了当前的元素数量,然后后续就挨个保存每个元素。

7、快速列表(quicklist)

快速列表是结合list与ziplist/listpack。list是一个双向链表,ziplist/listpack是一块连续的内存区域高效的保存数据。list每个节点的值保存一个ziplist/listpack结构。在具体操作时,插入/删除操作多数集中在list节点内部,即ziplist/listpack中。当节点过大/过小时进行节点分裂/合并操作。

typedef struct quicklistNode {struct quicklistNode *prev;struct quicklistNode *next;unsigned char *zl;unsigned int sz;             /* ziplist size in bytes */unsigned int count : 16;     /* count of items in ziplist */unsigned int encoding : 2;   /* RAW==1 or LZF==2 */unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */unsigned int recompress : 1; /* was this node previous compressed? */unsigned int attempted_compress : 1; /* node can't compress; too small */unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;
​
typedef struct quicklist {quicklistNode *head;quicklistNode *tail;unsigned long count;        /* total count of all entries in all ziplists */unsigned long len;          /* number of quicklistNodes */int fill : QL_FILL_BITS;              /* fill factor for individual nodes */unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */unsigned int bookmark_count: QL_BM_BITS;quicklistBookmark bookmarks[];
} quicklist;

8、跳表(skiplist)

Redis中跳表主要用在zset的实现中。跳表数据结构是对时间和空间上的综合。跳表是在链表基础上提供更高效的查找效率(O(logN)),并且在实现上更简单。

 更多资料:0voice · GitHub

http://www.xdnf.cn/news/17246.html

相关文章:

  • 微算法科技(NASDAQ:MLGO)使用循环QSC和QKD的量子区块链架构,提高交易安全性和透明度
  • 如何 让ubuntu 在root 下安装的docker 在 普通用户下也能用
  • 基于大数据的地铁客流数据分析预测系统 Python+Django+Vue.js
  • element plus table 表格操作列根据按钮数量自适应宽度
  • 并发编程(五)ThreadLocal
  • 智慧工业设备缺陷检测准确率↑32%:陌讯多模态融合算法实战解析
  • 微软XBOX游戏部门大裁员
  • 6.Linux 系统上的库文件生成与使用
  • 谷粒商城:检索服务
  • 解决Ollama外部服务器无法访问:配置 `OLLAMA_HOST=0.0.0.0` 指南
  • 深度剖析主流AI大模型的编程语言与架构选择:行业实践与技术细节解读
  • 苹果iPhone 17系列将发售,如何解决部分软件适配问题引发讨论
  • 《Hive、HBase、StarRocks、MySQL、OceanBase 全面对比:架构、优缺点与使用场景详解》
  • k8s调度问题
  • Charles中文版抓包工具功能解析,提升API调试与网络性能优化
  • ArgoCD 与 GitOps:K8S 原生持续部署的实操指南
  • 微软披露Exchange Server漏洞:攻击者可静默获取混合部署环境云访问权限
  • 31-数据仓库与Apache Hive-Insert插入数据
  • 悬赏任务系统网站兼职赚钱小程序搭建地推抖音视频任务拉新源码功能详解二开
  • 人工智能与交通:出行方式的革新
  • Ubuntu 22.04 安装 Docker 完整指南
  • [激光原理与应用-183]:测量仪器 - 光束型 - 光束参数乘积(BPP)的本质与含义,聚焦能力与传输稳定性的物理矛盾。
  • 深入解析C++流运算符(>>和<<)重载:为何必须使用全局函数与友元机制
  • 【开源工具】网络交换机批量配置生成工具开发全解:从原理到实战(附完整Python源码)
  • AI赋能6G网络安全研究:智能威胁检测与自动化防御
  • 【新启航】旋转治具 VS 手动翻转:三维扫描中自动化定位如何将单件扫描成本压缩 75%
  • WinForm利用 RichTextBox组件实现输出各种颜色字体日志信息
  • React 原生部落的生存现状:观察“Hooks 猎人“如何用useEffect设陷阱反被依赖项追杀
  • HarmonyOS 设备自动发现与连接全攻略:从原理到可运行 Demo
  • FreeRTOS入门知识(初识RTOS)(二)