`
DiaoCow
  • 浏览: 244350 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

《Redis源码学习笔记》数据结构-字典

阅读更多
《Redis源码学习笔记》文章列表

由于图片较大,缩放较为模糊,请双击打开查看原图 ^_^

要看懂redis代码,其中重要的一步就是要看懂它里面所使用的数据结构,而在这不算少的数据结构中,最重要的就是字典,它几乎就是redis实现各种功能的骨架,所以理解好字典至关重要!

redis作为一个nosql数据库,所有的key-value都是存储在一个字典中,而字典则是用哈希表实现的;关于哈希表原理,随便上网查一下都能找到一大堆资料,因此这里我也不想做过多赘述,直接开门见山,看下在redis中哈希表是什么样的:


上图所示结构对应代码如下:
// 字典
typedef struct dict {
    // 哈希表(2个)
    dictht ht[2]; 
    // rehash索引,若rehashidx == -1,则表示未开始进行rehash,
    // 否则rehashidx的值表示rehash正进行到ht[0]这个hash表上哪个索引节点
    int rehashidx;  
    // 安全迭代器数量
    int iterators; 
} dict;

// 哈希表
typedef struct dictht {
    // 哈希表 (指向一个dictEntry*数组,俗称“桶”)
    dictEntry **table;      
    // 哈希表大小
    unsigned long size;     
    // 位掩码,通过hash_value & sizemask得出节点在哈希表索引
    unsigned long sizemask; 
    // 哈希表中节点数量
    unsigned long used;     
} dictht;

// 哈希节点
typedef struct dictEntry {
    // 键
    void *key;
    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;
    // 指向下一个节点
    struct dictEntry *next; 
} dictEntry;


关于redis中的字典,最特别的无疑是dict中维护着两个哈希表(ht[0],ht[1]),为什么要有两个呢?在解释这个之前我们先看下哈希表的rehash;

rehash目的:
当我们不断的往哈希表(ht[0])插入新的键值对,如果两个键的hash值相同,那么它们将以链表的形式放入到同一个“桶”中,如下图key1和key4:



这样带来的问题就是,随着我们往哈希表里插入越来越多的键值对,哈希表性能会急剧下降(查找操作都退化成链表查找);所以,我们就需要扩大原来的哈希表,使得哈希表大小和哈希表中的节点数的比例能够维持在1:1(dictht.size:dictht.used),这时候哈希表才能达到最佳查询性能O(1)

rehash过程:
创建一个新的哈希表,大小是当前的两倍(准确说还必须是2的幂次),然后把全部键值对重新散列到新的哈希表中,最后再用它替换原来的哈希表;

rehash问题:
我们考虑下面一种情况:客户端A插入一个键值对,这时候发现dictht.used与dictht.size的比例大于1(查询性能开始下降),于是执行rehash操作,假设目前哈希表中有10万个键值对,那么redis就会一直埋头苦干,直到完成对这个10万个键值对的rehash操作,并且在这个过程中,其他客户端请求都会被阻塞(因为redis是单线程);很显然我们是无法忍受这种情况的发生,那redis是如何解决这个问题呢?

渐进式rehash:
“渐进式”意味着rehash过程不是一次做完而是每次做一点,这样就可以避免由于rehash过程太久导致其他客户端请求被阻塞,具体过程如下:

1. 在ht[1]上分配一个更大的哈希表;
2. “分多次”把ht[0]上的键值对重新散列到ht[1]上;
3. 当处理完所有键值对时,让ht[0]指向新的哈希表;



现在还有一个问题,我们说了“分多次把ht[0]上的键值对重新散列到ht[1]上”,那么这个分多次究竟是多少次?并且每次处理多少键值对才最合适?

redis准备rehash时,会把dict.rehashidx置为0(标示rehash开始),然后当执行任意一个哈希表操作(添加,删除,查找等),就会执行一次_dictRehashStep函数;

_dictRehashStep函数每次rehash把ht[0]上的第一个不为空索引上的全部键值对迁移到ht[1]上,并且用dict.rehashidx的值标示当前rehash正进行到了哪个索引;

也就是说按照上图,第一次迁移key1,key4键值对(这时候dict.rehashidx的值为0),第二次迁移key2键值对(这时候dict.rehashidx的值为1),第三次迁移key3键值对(这时候dict.rehashidx的值为2),至此rehash完毕(dict.rehashidx被复位成-1),相关伪代码:
# 任意dict操作(添加,删除,查找)
def anyDictOperation(dict):
    # 如果正在进行rehash
    if dict.rehashidx != -1: 
        _dictRehashStep(dict)
    # 执行字典操作
    dictOperation(dict)
        
def _dictRehashStep(dict):
    # 如果当前安全迭代器数量不为0,暂停此次rehash
    if dict.iterators > 0 : return

    idx = dict.rehashidx
    # 获取第一个不为空的索引
    while len(dict.ht[0].table[idx]) <= 0:
        idx++

    # 迁移该索引节点上的所有键值对到ht[1]上 
    for key, val in dict.ht[0].table[idx].getKeyValuePairs:
        redisServer.ht[0].table.used--
        redisServer.ht[1].table.used++
        redisServer.ht[1].table.hash(key, val)  
    
    # 当所有键值对迁移完毕,用新的哈希表替换老的,并且重置ht[1]
    if dict.ht[0].table.used == 0:
        dict.ht[0] = dict.ht[1]
        dict.ht[1].reset()
        dict.rehashindx = -1 # 复位

另外,redis在服务器执行例行任务时(serverCron),也会定期去做一部分rehash操作,伪代码:
def serverCron():
    # 循环redis所有数据库
    for num in redisServer.dbnums:
        if redisServer.db[num].dict.rehashidx != -1 :
            # 第二个参数用来限制rehash执行多久,单位毫秒
            dictRehashMilliseconds(redisServer.db[num].dict, 1)
    # 其他例行任务...

def dictRehashMilliseconds(dict, timeout_ms):
    start_time =  now_time()
    while now_time() - start_time < timeout_ms:
        _dictRehashStep(dict)

至此,我们已经分析完:为什么要在dict中维护两个哈希表(ht[0],ht[1]);
关于redis字典的更多细节,请参看:dict.h和dict.c代码以及redis.c/serverCron函数

总结:
1.了解redis中字典是如何设计的以及这样设计的原因;
2.了解哈希表的rehash过程以及rehash时机;
  • 大小: 13.4 KB
  • 大小: 15.2 KB
  • 大小: 116.3 KB
分享到:
评论

相关推荐

    flink-connector-redis-2.10-1.1.5-API文档-中文版.zip

    赠送jar包:flink-connector-redis_2.10-1.1.5.jar; 赠送原API文档:flink-connector-redis_2.10-1.1.5-javadoc.jar; 赠送源代码:flink-connector-redis_2.10-1.1.5-sources.jar; 赠送Maven依赖信息文件:flink-...

    redis-6.2.6-x64-windows.zip

    4. **安装与配置**:解压后,用户需要将`redis-server.exe`作为服务启动,可以通过`redis.windows.conf`配置文件进行定制化设置,如端口号、日志文件位置、数据持久化策略等。 5. **命令行客户端**:压缩包可能还...

    spring-data-redis-1.8.1.RELEASE-sources.jar(源码)

    spring-data-redis-1.8.1.RELEASE-sources.jar(spring-data-redis-1.8.1.RELEASE-sources.jar()

    redis-7.2-x64-for-windows-bin.zip

    8. **redis-server.exe**: Redis服务器的可执行文件,负责处理客户端请求并管理数据存储。 9. **redis-cli.exe**: Redis命令行界面工具,用户可以通过它与Redis服务器交互,执行读写操作、查看键空间、监控服务器...

    redis 免安装 redis客户端 redis-desktop-manager-0.8.8.384

    1. **键值存储**:Redis 支持多种数据结构,如字符串、哈希、列表、集合、有序集合等,这些数据结构为开发提供了极大的便利。 2. **持久化**:Redis 可以通过 RDB (Snapshot) 和 AOF (Append Only File) 两种方式...

    Redis-x64-7.0.5-windows11

    2. **下载源码**:从Redis官网获取最新版本的源代码,或者直接使用提供的Redis-x64-7.0.5编译好的二进制文件。 3. **配置环境变量**:将Redis的bin目录添加到系统PATH环境变量中,以便于命令行启动Redis服务。 4. **...

    Redis全套学习笔记-带章节目录-114页.pdf

    Redis全套学习笔记 Redis是一种基于内存的NoSQL数据库,具有高性能、可扩展性和灵活性等特点。以下是Redis的详细知识点: 安装和启动 * 安装Redis可以通过下载软件包或使用yum、apt-get等安装工具进行安装。 * ...

    redis+redis-desktop-manager-0.8.3.3850+笔记

    1. 下载源码包:`redis-2.8.13.tar.gz` 是Redis的源码包,解压后进行编译和安装。 2. 解压:`tar -zxvf redis-2.8.13.tar.gz` 3. 编译:`cd redis-2.8.13`,然后`make` 4. 安装:`sudo make install` 5. 启动Redis...

    redis-5.0.7-x64-for-windows-bin.rar

    redis-5.0.7-x64-for-windows编译-bin.rar Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类keyvalue存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Python...

    Redis-6.2.6-x64-Windows

    Windows下的Redis6.2.6版本 由于目前6版本以上Redis的windows版不好找到,故此上传至CSDN,方便大家下载使用。

    Redis全套学习笔记 (带章节目录) 完整版pdf

    本文是一篇关于Redis全套学习笔记的文章,主要介绍了Redis的基础知识、数据结构、持久化、集群、高可用、性能优化等方面的内容。通过本文的学习,读者可以全面掌握Redis的使用和应用,提高自己的技术水平和实践能力...

    Redis windows下载 Redis-x64-3.2.100.zip

    redis-server --service-install redis.windows-service.conf --loglevel verbose 2 常用的redis服务命令。 卸载服务:redis-server --service-uninstall 开启服务:redis-server --service-start 停止服务:redis-...

    spring-session-data-redis-2.0.4.RELEASE-API文档-中文版.zip

    赠送jar包:spring-session-data-redis-2.0.4.RELEASE.jar; 赠送原API文档:spring-session-data-redis-2.0.4.RELEASE-javadoc.jar; 赠送源代码:spring-session-data-redis-2.0.4.RELEASE-sources.jar; 赠送...

    C# windows redis-2.4.5-win32-win64.rar和redis服务安装软件

    综上所述,"C# windows redis-2.4.5-win32-win64.rar"提供的内容是Windows环境下安装和使用Redis的基础,配合C#的客户端库,可以在.NET项目中充分利用Redis的特性,实现高效的数据存储和访问。尽管Redis 2.4.5相对较...

    Redis笔记-尚硅谷周阳V1.3-脑图

    根据《Redis笔记-尚硅谷周阳V1.3》整理,脑图、思维导图xmind

    RedisDesktopManager Windows版 redis-desktop-manager-0.9.3.817.zip

    通过这个压缩包中的"redis-desktop-manager-0.9.3.817.exe"文件,用户可以安装和运行RedisDesktopManager。该可执行文件是经过编译的Windows程序,包含了所有必要的库和资源,使得用户无需额外配置环境即可直接使用...

    redis的桌面管理工具redis-desktop-manager-0.7.6.15

    在这个标题为“redis-desktop-manager-0.7.6.15”的资源中,我们找到了该工具的一个特定版本——0.7.6.15,特别指出它在Windows 7操作系统上已经过测试并可以正常运行。 Redis Desktop Manager的主要功能包括: 1....

    redis-windows-x64--4.0.2.3.rar

    总结,`redis-windows-x64--4.0.2.3.rar`提供的Windows版Redis是一个轻量级、高性能的数据存储解决方案,适合开发者在Windows环境中快速搭建测试环境或开发应用。理解其核心概念、安装配置以及在Windows上的特殊考虑...

    redis-stack-server-6.2.6-v7.rhel7.x86-64.tar.gz

    总之,`redis-stack-server-6.2.6-v7.rhel7.x86-64.tar.gz` 是一个包含全套 Redis 功能的打包软件,对于需要强大、灵活和高度可扩展的数据存储和处理解决方案的开发者来说,这是一个理想的选择。通过熟练掌握 Redis ...

    redis学习笔记+练习springboot-redisdemo

    本资源包包含了关于Redis的学习笔记以及一个基于SpringBoot整合Redis的实战项目——"springboot-redisdemo",旨在帮助你深入理解和应用Redis。 首先,让我们详细探讨Redis的核心知识点: 1. **Redis的数据类型**:...

Global site tag (gtag.js) - Google Analytics