首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >第九章 源码分析(一)

第九章 源码分析(一)

原创
作者头像
RookieCyliner
发布2025-06-14 21:17:23
发布2025-06-14 21:17:23
2830
举报
文章被收录于专栏:redisredis

Redis C语言源代码的核心部分

  • git源码地址:git clone https://github.com/redis/redis.git (redis-6.0.8\redis-6.0.8\src)
redis开发语言
redis开发语言
redis文件
redis文件

Redis基本的数据结构

  1. 简单动态字符串sds.c
  2. 整数集合intset.c
  3. 压缩列表ziplist.c
  4. 快速链表quicklist.c
  5. 字典dict.c
  6. Streams的底层实现结构listpack.c和rax.c(了解)

Redis数据类型的底层实现

  1. Redis对象object.c
  2. 字符串t_string.c
  3. 列表t_list.c
  4. 字典t_hash.c
  5. 集合及有序集合t_set.c和t_zset.c
  6. 数据流t_stream.c

Redis数据库的实现

  1. 数据库的底层实现db.c
  2. 持久化rdb.c和aof.c

Redis服务端和客户端实现

  1. 事件驱动ae.c和ae_epoll.c
  2. 网络连接anet.c和networking.c
  3. 服务端程序server.c
  4. 客户端程序redis-cli.c

其他

  1. 主从复制replication.c
  2. 哨兵sentinel.c
  3. 集群cluster.c
  4. 其他数据结构,如hyperloglog.c、geo.c等
  5. 其他功能,如pub/sub、Lua脚本

KV键值对到底是什么

  • 总结语:redis是key-value存储系统,其中key类型一般为字符串, value类型则为redis对象(redisObject)
5大数据结构说明
5大数据结构说明

5大类型说明

新介绍的3大类型(bitmap、hyperLogLog:实质String | GEO:实质Zset)

三大类型演示
三大类型演示

图解

redis结构图解
redis结构图解

C语言struct结构体简介

struct结构体
struct结构体
typedef关键字
typedef关键字

字典、KV是什么:每个键值对都会有一个dictEntry

dicentry
dicentry
redisObject
redisObject

redisObject +Redis数据类型+Redis 所有编码方式(底层实现)三者之间的关系

redis数据类型与结构关系
redis数据类型与结构关系

从set hello world说起

  1. set hello word为例,因为Redis是KV键值对的数据库,每个键值对都会有一个dictEntry(源码位置:dict.h)里面指向了key和value的指针,next指向下一个dictEntry
  2. key是字符串,但是Redis没有直接使用C的字符数组,而是存储在redis自定义的SDS中
  3. value既不是直接作为字符串存储,也不是直接存储在SDS中,而是存储在redisObject中
  4. 实际上五种常用的数据类型的任何一种,都是通过redisObject来存储的

11112222222222

redis看看类型:type key | 看看编码:object encoding hello | debug结构:debug object person

代码语言:txt
复制
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> type hello 
string
127.0.0.1:6379> object encoding hello 
"embstr"
127.0.0.1:6379> debug object hello 
Value at:0x7fa41f80e3a0 refcount:1 encoding:embstr serializedlength:6 lru:891806 lru_seconds_idle:61
127.0.0.1:6379> 

Redis采用redisObjec结构来统一五种不同的数据类型

为了便于操作,Redis采用redisObjec结构来统一五种不同的数据类型,这样所有的数据类型就都可以以相同的形式在函数间传递而不用使用特定的类型结构。同时,为了识别不同的数据类型, redisObjec中定义了type和encoding字段对不同的数据类型加以区别。简单地说,redisObjec就是string、hash、list、set、zset的父类 ,可以在函数间传递时隐藏具体的类型信息,所以作者抽象了redisObjec结构来到达同样的目的

代码语言:txt
复制
struct redisObject {
    /*对象的类型,包括:OBJECT_STRING、OBJECT_LIST、OBJECT_HASH、OBJECT_SET、OBJECT_ZSET */
    unsigned type:4;
    /*具体的数据结构*/ 
    unsigned encoding:4;
    /*24位,对象最后一次被命令程序访问的时间,与内存回收有关*/
    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). */
    /*引用计数,当refcount=0的时候,表示该对象已经不被任何对象引用,则可以进行垃圾回收了*/
    int refcount;
    /*指向对象实际的数据结构*/
    void *ptr;
};
redisObject结构
redisObject结构

RedisObject各字段的含义

  1. 4位的type表示具体的数据类型
  2. 4位的encoding表示该类型的物理编码方式见下表,同一种数据类型可能有不同的编码方式((比如String就提供了3种:int embstr raw))
  3. lru字段表示当内存超限时采用LRU算法清除内存中的对象
  4. refcount表示对象的引用计数
  5. ptr指针指向真正的底层数据结构的指针

redisObject字段介绍
redisObject字段介绍
举例说明
举例说明

String - 3大编码格式

int

  1. 保存long型(长整型)的64位(8个字节)有符号整数
  2. 9223372036854775807
  3. 上面数字最多19位
  4. 只有整数才会使用 int,如果是浮点数, Redis 内部其实先将浮点数转化为字符串值,然后再保存

embstr

代表embstr格式的SDS(Simple Dynamic String 简单动态字符串),保存长度小于44字节的字符串。EMBSTR顾名思义即:embedded string,表示嵌入式的String

raw

保存长度大于44字节的字符串

String - 重新设置SDS

C语言中字符串展示

C语言中字符串展示
C语言中字符串展示

Redis没有直接复用C语言的字符串,而是新建了属于自己的结构-----SDS

  • 在Redis数据库里,包含字符串值的键值对都是由SDS实现的(Redis中所有的键都是由字符串对象实现的即底层是由SDS实现,Redis中所有的值对象中包含的字符串对象底层也是由SDS实现)
  • redis会兼顾很多语言,比如java、py等,比如现在我们存的音频、视频文件中以二进制方式存储,有包含\0(123456\0789456),用C的字符串,就会导致789456丢失,导致数据丢失
SDS结构
SDS结构
代码语言:txt
复制
# redis 3.2 以前
struct sdshdr {
    int len;
    int free;
    char buf[];
};

Redis为什么重新设计一个SDS数据结构?

提供内存预分配,避免频繁内存分配

代码语言:txt
复制
# redis 3.2 以前
struct sdshdr {
    int len;
    int free;
    char buf[];
};
# 1. 比如我现在set name tang
struct sdshdr {
    int len = 4
    int free = 0
    char buf[]= [tang]
};
# 2. append name zhi
# 预分配规则: [4(tang) + 3(zhi)] * 2 = 14长度
struct sdshdr {
    int len = 7
    int free = 7
    char buf[]= [tangzhi]
};
# 3. append name 123
# 这个时候在2时候已经预分配了14 > [4(tang) + 3(zhi) + 3(123)],不需要再额外分配
struct sdshdr {
    int len = 10
    int free = 4
    char buf[]= [tangzhi123]
};

C语言字符串和SDS对比
C语言字符串和SDS对比

redis3.2以前和之后的变化,为什么要有这些变化情况存在?- redis最终理念内存

在3.2以前,假设,我们的buf[]很小,特别小,int len是用4个字节进行存储的,太浪费了

针对于这种特别小的情况,3.2之后优化了代码,使用1个字节的flags,中的5位来表示长度,也就是2^5表示长度,达到内存优化的目的

代码语言:txt
复制
# redis 3.2 以前
struct sdshdr {
    int len;
    int free;
    char buf[];
};
# redis 3.2 后
1. len:记录buf数组中已使用字节数量
2. flags:当前类型(不同的sdshdr)
3. alloc:不包括头和空结束符的字节数量
typedef **char** *sds;
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 {
代码语言:txt
复制
static inline char sdsReqType(size_t string_size) {
    if (string_size < 32)  
        return SDS_TYPE_5;
    if (string_size < 0xff) //2^8 -1  
        return SDS_TYPE_8;
    if (string_size < 0xffff) // 2^16 -1  
        return SDS_TYPE_16;
    if (string_size < 0xffffffff)  // 2^32 -1 
        return SDS_TYPE_32;
    return SDS_TYPE_64;
}

sdshdr5
sdshdr5
sdshdr8
sdshdr8
sdshdr16
sdshdr16

三大编码 - INT编码格式

当字符串键值的内容可以用一个64位有符号整形来表示时,Redis会将键值转化为long型来进行存储,此时即对应 OBJ_ENCODING_INT 编码类型。内部的内存结构表示如下:

命令示例: set k1 123

set k1 123存储数据结构
set k1 123存储数据结构

Redis启动时会预先建立10000个分别存储0 - 9999的redisObject变量作为共享对象,这就意味着如果set字符串的键值在0~10000之间的话,则可以直接指向共享对象而不需要再建立新对象,此时键值不占空间

代码语言:txt
复制
set k1 123
set k2 123

0 - 9999的redisObject变量共享
0 - 9999的redisObject变量共享

redis源代码:object.c

int编码
int编码

三大编码 - EMBSTR编码格式

  • ①. redis源代码:object.c

embstr编码
embstr编码

对于长度小于44的字符串,Redis对键值采用OBJ_ENCODING_EMBSTR方式,EMBSTR顾名思义即:embedded string,表示嵌入式的String。从内存结构上来讲即字符串sds结构体与其对应的 redisObject对象分配在同一块连续的内存空间,字符串sds嵌入在redisObject对象之中一样

embstr
embstr
embstr
embstr

redis源代码:object.c(embStr不会重新再次开辟空间,而是会嵌入在redisObject里面)

embstr编码数据存储
embstr编码数据存储

三大编码 - RAW编码格式

当字符串的键值为长度大于44的超长字符串时,Redis则会将键值的内部编码方式改为OBJ_ENCODING_RAW格式,这与OBJ_ENCODING_EMBSTR编码方式的不同之处在于,此时动态字符串sds的内存与其依赖的redisObject的内存不再连续了

row编码
row编码

明明没有超过阈值,为什么变成raw了?

  • 只有整数才会使用int,如果是浮点数, Redis内部其实先将浮点数转化为字符串值,然后再保存

embstr与raw类型底层的数据结构其实都是SDS(简单动态字符串,Redis内部定义sdshdr一种结构)

三种编码方式对比
三种编码方式对比
三种编码方式存储结构
三种编码方式存储结构

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Redis C语言源代码的核心部分
  • Redis基本的数据结构
  • Redis数据类型的底层实现
  • Redis数据库的实现
  • Redis服务端和客户端实现
  • 其他
  • KV键值对到底是什么
    • 5大类型说明
    • 图解
    • C语言struct结构体简介
      • 字典、KV是什么:每个键值对都会有一个dictEntry
      • redisObject +Redis数据类型+Redis 所有编码方式(底层实现)三者之间的关系
    • 从set hello world说起
  • 11112222222222
    • redis看看类型:type key | 看看编码:object encoding hello | debug结构:debug object person
    • Redis采用redisObjec结构来统一五种不同的数据类型
    • RedisObject各字段的含义
    • String - 3大编码格式
      • int
      • embstr
      • raw
    • String - 重新设置SDS
    • 三大编码 - INT编码格式
    • 三大编码 - EMBSTR编码格式
    • 三大编码 - RAW编码格式
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档