redis存储原理与数据模型学习笔记

这篇具有很好参考价值的文章主要介绍了redis存储原理与数据模型学习笔记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1 redis线程模型

1.1 线程组成

redis存储原理与数据模型学习笔记
redis-server
命令处理
网络事件的监听
bio close file 异步关闭大文件
bio aof fsync 异步 aof 刷盘
bio lazy free 异步清理大块内存
io thd * io 多线程
emalloc bg thd jemalloc 后台线程

1.2 redis命令处理是单线程

redis存储原理与数据模型学习笔记

单线程为什么快?

redis存储原理与数据模型学习笔记

2 redis db 存储分析

2.1 先了解代码

server.h

/* Redis database representation. There are multiple databases identified
 * by integers from 0 (the default database) up to the max configured
 * database. The database number is the 'id' field in the structure. */
typedef struct redisDb {
    dict *dict;                 /* The keyspace for this DB */
    dict *expires;              /* Timeout of keys with a timeout set */          ---->  自动过期的key
    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 */   ---->  事物操作时有用到的  watch
    int id;                     /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
    clusterSlotToKeyMapping *slots_to_keys; /* Array of slots to keys. Only used in cluster mode (db 0). */
} redisDb;

dict *dict:指向字典对象的指针,用于存储键值对数据。字典是Redis中的核心数据结构,用于快速查找和访问键值对。
dict *expires:指向字典对象的指针,用于存储键的过期时间。当键设置了过期时间时,会将键和其对应的过期时间存储在这个字典中。
dict *blocking_keys:指向字典对象的指针,用于存储被阻塞的键。当某个客户端在执行阻塞操作时,将被阻塞的键存储在这个字典中。
dict *ready_keys:指向字典对象的指针,用于存储状态已改变的键。当某个键的值在被其他操作修改后,将把该键存储在这个字典中。
dict *watched_keys:指向字典对象的指针,用于存储被监视的键。当某个事务中的键被监视时,将被监视的键存储在这个字典中。
int id:表示数据库的ID。
long long avg_ttl:表示数据库中所有键的平均过期时间。
unsigned long expires_cursor:用于过期键的迭代游标。
list *defrag_later:指向链表对象的指针,用于存储需要碎片整理的键。
clusterSlotToKeyMapping *slots_to_keys:用于集群模式下的槽位和键的映射。

dict.h

struct dict {
    dictType *type;

    dictEntry **ht_table[2];
    unsigned long ht_used[2];

    long rehashidx; /* rehashing not in progress if rehashidx == -1 */

    /* Keep small vars at end for optimal (minimal) struct padding */
    int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
    signed char ht_size_exp[2]; /* exponent of size. (size = 1<<exp) */
};

注意 dictEntry **ht_table[2];

2.2 从kv存储分析

怎么从key定位到value?
哈希原理:
数组 + hash(key) % 数组长度 ===>确定value存储位置 参考https://blog.csdn.net/qq43645149/article/details/131242533 的3.3节

  1. 字符串key经过 hash 函数运算得到 64 位整数;
  2. 相同字符串key多次通过 hash 函数得到相同的 64 位整数;
  3. 整数对 取余可以转化为位运算;
  4. 抽屉原理 n+1个苹果放在 n 个抽屉中,苹果最多的那个抽屉至少有 2 个苹果;
    64位整数远大于数组的长度,比如数组长度为 4,那么 1、5、9、1+4n 都是映射到1号位数组;所以大概率会发生冲突;

ht_table二维指针,这里可以理解为指针数组,对应哈希存储数组 ht 的每一个槽位ht[i] 挂的是一个链表
ht_table[2]中的2,怎么理解?ht的槽位成对出现,ht[1]是为扩容备用的。
redis存储原理与数据模型学习笔记
避免冲突,使用强随机函数siphash, 然后就是 扩容
redis存储原理与数据模型学习笔记
unsigned long ht_used[2]; 用来保存ht[0], ht[1]的长度,用2的n次幂表示size,
这样hash(key) % size 取余运算就可以优化为位运算:hash(key) & (2^-1) 。

怎么判断扩容呢?

2.3 负载因子

负载因子 = used / size;used 是数组存储元素的个数,
size 是数组的长度;
负载因子越小,冲突越小;负载因子越大,冲突越大;
redis 的负载因子是 1;

如果负载因子 > 1,则会发生扩容;扩容的规则是翻倍;
如果正在 fork (在 rdb、aof 复写以及 rdb-aof 混用情况下)时,会阻止扩容;但是此时若负载因子 > 5,
索引效率大大降低, 则马上扩容;这里涉及到写时复制原理;

如果负载因子 < 0.1,则会发生缩容;缩容的规则是恰好包含used 的 ;
恰好的理解:假如此时数组存储元素个数为 9,恰好包含该元素的就是 ,也就是 16;

这里有一个问题,当执行scan命令的时候,突然发生扩容或者缩容怎么办?

127.0.0.1:6379> SET key1 "value1"
OK
127.0.0.1:6379> SET key2 "value2"
OK
127.0.0.1:6379> SET key3 "value3"
OK
127.0.0.1:6379> SCAN 0 MATCH key* COUNT 100       
1) "13"    // 游标值
2) 
   1) "key3"
   2) "key2"
   3) "key1"    // 匹配模式为key*,返回所有以key开头的键

2.4 渐进式rehash机制

源码位置:
./redis-main/src/dict.c:211:int dictRehash(dict *d, int n)

当 hashtable 中的元素过多的时候,不能一次性 rehash 到ht[1];这样会长期占用 redis,其他命令得不到响应;所以需
要使用渐进式 rehash;
rehash步骤:
将 ht[0] 中的元素重新经过 hash 函数生成 64 位整数,再对ht[1] 长度进行取余,从而映射到 ht[1];
渐进式规则:

  1. 分治的思想,将 rehash 分步到之后的每步“增、删、查”的操作当中;
  2. 在定时器中,最大执行一毫秒 rehash ;每次步长 100 个数组槽位; (避免命令单线程阻塞)
    处于渐进式rehash阶段不会发生扩容缩容。
    对应源码:
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree)
dictEntry *dictFind(dict *d, const void *key)
dictEntry *dictGetRandomKey(dict *d)

/* Rehash in ms+"delta" milliseconds. The value of "delta" is larger 
 * than 0, and is smaller than 1 in most cases. The exact upper bound 
 * depends on the running time of dictRehash(d,100).*/
int dictRehashMilliseconds(dict *d, int ms) {
    if (d->pauserehash > 0) return 0;

    long long start = timeInMilliseconds();
    int rehashes = 0;

    while(dictRehash(d,100)) {
        rehashes += 100;
        if (timeInMilliseconds()-start > ms) break;
    }
    return rehashes;
}

数据访问scan

通过往高位加1,往低位进1的方式遍历,如下图
redis存储原理与数据模型学习笔记
为什么2对应2和6?因为6%4=2, 6%8=6, 当key时2,6的时候,size=4, key%size = 2。
为什么要用这种访问方式呢?==> 可以确保“扩容”或"缩容的时候“ 可以访问正确遍历完所有的数据(两次缩容或扩容处理不了)。

27.0.0.1:6379> set aa 1
OK
127.0.0.1:6379> set bbb 2
OK
127.0.0.1:6379> set ccccc 3
OK
127.0.0.1:6379> set ddddddddddddd 4
OK
127.0.0.1:6379> keys *
1) "ddddddddddddd"
2) "ccccc"
3) "aa"
4) "bbb"
127.0.0.1:6379> scan 0 match * count 1
1) "2"  # 这里提示下一个是2
2) 1) "ddddddddddddd"
# 这个时候,在另外一个终端 执行:127.0.0.1:6379> set ffffffffffffffffff 5
# OK
127.0.0.1:6379> scan 2 match * count 1
1) "6"  # 这里提示下一个是6,假装扩容了
2) 1) "ffffffffffffffffff"
127.0.0.1:6379> scan 6 match * count 1
1) "1"
2) 1) "bbb"
127.0.0.1:6379> scan 1 match * count 1
1) "5"  # ? aa ccccc 一起出来了?
2) 1) "aa"
   2) "ccccc"
127.0.0.1:6379> scan 5 match * count 1
1) "0"
2) (empty array)

aa 与 ccccc 一起出来,aa, ccccc这里是什么存储结构呢?

127.0.0.1:6379> keys *
1) "aa"
2) "ccccc"
3) "ffffffffffffffffff"
4) "ddddddddddddd"
5) "bbb"

3 数据模型分析

数据量小或少的时候,用简单的数据结构,反之用较为复杂的数据结构。
redis存储原理与数据模型学习笔记

以zset为例

redis存储原理与数据模型学习笔记
在 redis.conf 可以查找到相关的定义:
zset-max-listpack-entries 128
zset-max-listpack-value 64
然后在t_zset.c中找到 createZsetObject 与 createZsetListpackObject,可加断点调试。

 /* Lookup the key and create the sorted set if does not exist. */
    zobj = lookupKeyWrite(c->db,key);
    if (checkType(c,zobj,OBJ_ZSET)) goto cleanup;
    if (zobj == NULL) {
        if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */
        if (server.zset_max_listpack_entries == 0 ||
            server.zset_max_listpack_value < sdslen(c->argv[scoreidx+1]->ptr))
        {
            zobj = createZsetObject();           // OBJ_ENCODING_SKIPLIST
        } else {
            zobj = createZsetListpackObject();  // OBJ_ENCODING_LISTPACK
        }
        dbAdd(c->db,key,zobj);
    }

当元素过大,或者list过长,转换为跳表。

 /* check if the element is too large or the list
             * becomes too long *before* executing zzlInsert. */
            if (zzlLength(zobj->ptr)+1 > server.zset_max_listpack_entries ||
                sdslen(ele) > server.zset_max_listpack_value ||
                !lpSafeToAdd(zobj->ptr, sdslen(ele)))
            {
                zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);  // 转换为跳表
            } else {
                zobj->ptr = zzlInsert(zobj->ptr,ele,score);
                if (newscore) *newscore = score;
                *out_flags |= ZADD_OUT_ADDED;
                return 1;
            }

对应1.2节中的:
redis存储原理与数据模型学习笔记

跳表

方便范围查询,多层级有序链表,让其实现二分查找的效率,缺点占用的内存空间会多50%。

这里用一个比较特殊的例子(完美跳表,第二层是从第一层每隔一个元素取一个,第三层也是从第二层每隔一个元素取一个。。。)理解一下,如下图,要访问节点12,第一层常规的遍历方法用的次数明显比蓝色箭头用的次数要多。
但是优势不明显,但如果是找节点10,跳表只需比较一次。平均下来接算法复杂度接近于 O ( log ⁡ 2 n ) O(\log_2n) O(log2n)
注意第一层是有序链表,跳表是从最高层往底层跳,从而找到目标。
redis存储原理与数据模型学习笔记
上面的跳表在增加或删除元素时,需要重新构建跳表,效率比较低。
非完美跳表,插入节点会用随机层数的方式,这样第二层对于第三层就可能不会是“每隔一个节点提取一个元素了”,相邻的元素可能会被直接提取到上一层,更高层也是。
另外一个例子:插入17
redis存储原理与数据模型学习笔记
另外,为了快速的索引到某个节点,zset还引入了字典功能,帮助快速的索引到节点。结构图如下:
redis存储原理与数据模型学习笔记
文章参考与<零声教育>的C/C++linux服务期高级架构系统教程学习:链接文章来源地址https://www.toymoban.com/news/detail-499312.html

到了这里,关于redis存储原理与数据模型学习笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • redis 存储结构原理 1

    关于 redis 相信大家都不陌生了,之前有从 0 -1 分享过 redis 的基本使用方式,用起来倒是都没有啥问题了,不过还是那句话, 会应用之后,我们必须要究其原理,知其然知其所以然 今天我们来分享一下关于 redis 的存储结构的原理 我们都知道 redis 是一个 K-V 内存数据库,类似

    2024年02月12日
    浏览(32)
  • redis 存储结构原理 2

    咱们接着上一部分来进行分享,我们可以在如下地址下载 redis 的源码 : https://redis.io/download 此处我下载的是 redis-6.2.5 版本的,xdm 可以直接下载上图中的 **redis-6.2.6 **版本, redis hash 表的数据结构定义在: redis-6.2.5srcdict.h 哈希表的结构,每一个字典都有两个实现从旧表到新

    2024年02月12日
    浏览(36)
  • Redis数据结构学习笔记

    图文主要参考小林Coding的图解redis数据结构 除了它是内存数据库,使得所有的操作都在内存上进⾏之外,还有⼀个重要因素,它实现的数据结构,使 得我们对数据进⾏增删查改操作时,Redis 能⾼效的处理。 :::tips redisDb 结构 ,表示 Redis 数据库的结构,结构体⾥存放了指向了

    2024年02月02日
    浏览(45)
  • openGauss学习笔记-60 openGauss 数据库管理-逻辑存储结构

    openGauss的数据库节点负责存储数据,其存储介质也是磁盘,本节主要从逻辑视角介绍数据库节点都有哪些对象,以及这些对象之间的关系。数据库逻辑结构如 图1 。 图 1 数据库逻辑结构图 说明: Tablespace,即表空间,是一个目录,可以存在多个,里面存储的是它所包含的数据

    2024年02月09日
    浏览(50)
  • 《区块链原理与技术》学习笔记(四) ——以太坊的基本架构、账户模型和智能合约

    《区块链原理与技术》学习笔记 第四部分 三、以太坊 1. 以太坊简介 1.1 以太坊发展的阶段 1.2 以太坊与比特币对比 2. 以太坊的基本架构及原理 2.1 基本概念 2.2 状态转移 2.3 基本架构 3. 账户模型与转账 3.1 账户模型 4. 智能合约 4.1 合约账户与数据存储 4.2 驱动智能合约 以太坊

    2024年02月13日
    浏览(46)
  • 【手写数据库】从零开始手写数据库内核,行列混合存储模型,学习大纲成型了

    ​ 专栏内容 : 参天引擎内核架构 本专栏一起来聊聊参天引擎内核架构,以及如何实现多机的数据库节点的多读多写,与传统主备,MPP的区别,技术难点的分析,数据元数据同步,多主节点的情况下对故障容灾的支持。 手写数据库toadb 本专栏主要介绍如何从零开发,开发的

    2024年02月04日
    浏览(57)
  • [Android Studio]Android 数据存储-文件存储学习笔记-结合保存QQ账户与密码存储到指定文件中的演练

     🟧🟨🟩🟦🟪 Android Debug 🟧🟨🟩🟦🟪 Topic   发布安卓学习过程中遇到问题解决过程,希望我的解决方案可以对小伙伴们有帮助。 🪁文件存储 💾内部存储 📀存储数据到文件 💿从文件中读取数据 💯实战演练--保存QQ账号与密码 📖acticity_main.xml布局文件  📖 FileSave

    2023年04月14日
    浏览(50)
  • Redis原理 - Redis网络模型

    原文首更地址,阅读效果更佳! Redis原理 - Redis网络模型 | CoderMast编程桅杆 https://www.codermast.com/database/redis/redis-netword-model.html Redis 到底是单线程还是多线程? 如果仅仅针对 Redis 的核心业务部分(命令处理部分),则是单线程 如果针对 Redis 整体,那么就是多线程 在 Redis 的版

    2024年02月11日
    浏览(36)
  • docker 笔记5:redis 集群分布式存储案例

    尚硅谷Docker实战教程(docker教程天花板)_哔哩哔哩_bilibili 目录 1.cluster(集群)模式-docker版哈希槽分区进行亿级数据存储  1.1面试题 1.1.1  方案1 哈希取余分区 1.1.2 方案2 一致性哈希算法分区  原理 优点 一致性哈希算法的容错性  一致性哈希算法的扩展性  缺点  一致性哈希算

    2024年02月09日
    浏览(42)
  • 【Redis】Redis 高性能IO模型原理

    在面试的时候遇到Redis肯定会问,Redis单线程为什么那么快呀?你可以说下你对IO多路复用的机制嘛。但是仔细一想Redis真的是单线程在运行处理嘛,其实这个单线程主要指的Redis的网络IO和键值对读写是由一个线程来完成的,Redis在处理客户端的请求,包括socket读取、解析、执

    2024年02月04日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包