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

Redis 数据库源码分析

Redis 数据库源码分析

我们都知道Redis是一个 <key,value> 的键值数据库,其实也就是一个 Map。如果让我来实现这样一个 Map,我肯定是用数组,当一个 key 来的时候,首先进行 hash 运算,接着对数据的 length 取余,把这个键值对放在对应位置。

设计与分析

我们来看一下 Redis 的设计:

typedef struct redisDb {
    dict *dict;                 /* The keyspace for this DB */
    dict *expires;              /* Timeout of keys with a timeout set */
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;                     /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    unsigned long iterators; /* number of iterators currently running */
} dict;

typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

以上是 Redis 的相关源代码,接下来分别对其进行分析:

redisDb其实就是我们说的数据库,redis 有16个这样的数据库,其中的 dict *dict 就是我们数据存放的字段

typedef struct redisDb {
    dict *dict;                 /* The keyspace for this DB */
    dict *expires;              /* Timeout of keys with a timeout set */
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;                     /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

再看一下 dict 结构体的设计:

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    unsigned long iterators; /* number of iterators currently running */
} dict;

dictType *type里有一堆函数指针,当有 key 来时,需要通过函数指针里的 hash 函数求 hash 值,对数组长度取余后还要调用比较函数判断对应位置的 key 与当前 key 是否相等(原因是会有 hash 冲突,redis 首先使用链表法解决冲突,头插法)

dictht ht[2],一般情况下 ht[1] 都是指向 NULL,只有在 rehash 的时候才有值。Redis 使用两个数组,当需要扩容时,读请求首先从 ht[0] 读,没有的话再去 ht[1],写请求直接写 ht[1],更新请求后续再说。(TBD:什么时候扩容?扩容时的扩容大小?rehash 时的请求处理?)

rehashidx,rehash 的索引,如果是-1,代表此时没有进行 rehash,否则代表需要进行迁移的数组下标。

再看一下dictht结构体的设计,ht,即 hashtable,学 java 的应该很熟悉

typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

dictEntry **table:一个dictEntry指针数组。key的哈希值最终映射到这个数组的某个位置上(对应一个bucket)。如果多个key映射到同一个位置,就发生了冲突,那么就拉出一个 dictEntry 链表。

size:标识dictEntry指针数组的长度。它总是2的指数。

sizemask:key 的 hash 值对 size 取模的结果其实等于 hash 值 & (size - 1),而且%运算会一直除,而&运算一次到位。

used:记录dict中现有的数据个数。它与size的比值就是装载因子(load factor)。这个比值越大,哈希值冲突概率越高。

最终的数据是在 dictEntry 中,一起来看下:

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

一个 key 的指针,一个 value 的指针,由于 hash 函数可能会冲突,所以还有 next 指针用来解决冲突(链表法)。

图示

经过上述分析,我们可以得到这样的一个 Redis 结构图

image-20250106162507568

至于其中的 SDSredisObject,其实是 Redis 的内部数据结构,如果有机会的话后续再写。

参考资料

【1】Redis(5.0.8)源码

【2】哔哩哔哩-Redis底层设计与源码分析


http://www.kler.cn/a/470782.html

相关文章:

  • 调整Python+Pytest+Allure+Yaml+Pymysql框架中需要执行的用例顺序
  • 【Cesium】自定义材质,添加带有方向的滚动路线
  • PySide6的样式表
  • PCL 分段线性函数
  • ECharts系列:echart中折线图折线设置不平滑显示
  • 域名注册网国际域名与国内域名的区别
  • Opencv实现Sobel算子、Scharr算子、Laplacian算子、Canny检测图像边缘
  • stm32 移植RTL8201F(正点原子例程为例)
  • Easyexcel-4.0.3读取文件内容时遇到“java.lang.ClassNotFoundException”
  • 《从入门到精通:蓝桥杯编程大赛知识点全攻略》(二)-递归实现组合型枚举、带分数问题
  • libaom 源码分析线程结构
  • uni-app 页面生命周期及组件生命周期汇总(Vue2、Vue3)
  • 特征点检测与匹配——MATLAB R2022b
  • 2025资源从哪里来!
  • vue3-dom-diff算法
  • Postman接口测试02|接口用例设计
  • 云原生周刊:K8s 生态系统的五大趋势预测
  • IDEA中Lombok不能使用,找不到get方法
  • 乾元通渠道商中标玉溪市自然灾害应急能力提升项目
  • 【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
  • Flink-CDC 全面解析
  • 【pytorch-lightning】架构一览
  • 复杂园区网基本分支的构建
  • 工控主板ESM7000/6800E支持远程桌面控制
  • GolangWeb开发-好用的HTTP客户端httplib(beego)
  • 对智能手表进行逆向工程