首页 > 数据库 >[Redis]扩容

[Redis]扩容

时间:2024-07-06 22:52:11浏览次数:7  
标签:扩容 COW Redis ht 线程 内存 哈希 进程

原因

扩容原因:当hashtable存储的元素过多,可能由于碰撞也过多,导致其中某链表很长,最后致使查找和插入时间复杂度很大。因此当元素超多一定的时候就需要扩容。
缩容原因:当元素数量比较少的时候就需要缩容以节约不必要的内存。为了让哈希表的负载因子(load factor)维持在一个合理的范围内,会使用rehash(重新散列)操作对哈希表进行相应的扩展或收缩。

负载因子的计算公式:哈希表已保存节点数量 / 哈希表大小
== load_factor = ht[0].used / ht[0].size ==

扩容条件(满足任意一个即可)

  • Redis服务器目前没有在执行BGSAVE或BGREWRITEAOF命令,并且哈希表的负载因子大于等于1。
  • Redis服务器目前在执行BGSAVE或BGREWRITEAOF命令,并且哈希表的负载因子大于等于5。

为什么BGSAVE或BGREWRITEAOF命令是否在执行,Redis服务器哈希表执行扩容所需的负载因子不相同(1或5)?

BGSAVE:用于在后台异步保存当前数据库的数据到磁盘。
BGREWRITEAOF:用于异步执行一个 AOF( Append Only File ) 文件重写操作。
因为当执行BGSAVE或BGREWRITEAOF命令过程中,Redis需要创建服务器进程的子进程,操作系统采用的是COW,即 写时复制 copy-on-write的技术来优化子进程的使用效率。所以在子进程存在时,服务器会提高执行扩容所需的负载因子,从而尽可能避免在子进程存在期间进行扩容,可以避免不必要的内存写入操作,最大限度节约内存。

PS:COW

“写时复制”(Copy-On-Write,简称 COW)是一种用于资源管理和优化的技术,主要应用在内存管理和系统设计中。它的基本思想是:如果有多个进程或线程需要读取同一个资源(如内存块或数据结构),它们可以共享该资源的单一副本;但是,当其中任何一个进程或线程尝试修改资源时,系统会为其创建一个资源的独立副本,这样可以确保其他进程或线程看到的仍然是原始未修改的资源。

应用场景

  1. 内存管理

    • 操作系统中的进程创建

      • 当操作系统使用 fork() 系统调用创建一个新的进程时,新的进程通常是父进程的一个副本。为了节省内存和提高效率,子进程会共享父进程的内存空间。在这种情况下,COW 技术允许子进程和父进程在读操作时共享同一块内存,只有当某个进程尝试写入内存时,才会为该进程创建内存的副本。
    • 虚拟内存管理

      • 在操作系统的虚拟内存管理中,COW 可以用于延迟分配物理内存。初始时,所有进程共享同一块物理内存,当有进程尝试写操作时,操作系统才分配新的物理内存页。
  2. 数据结构和算法

    • 在某些数据结构(如字符串、数组、列表等)中,COW 可以用来优化修改操作。对于不可变的数据结构或在多线程环境中,COW 能够提供一种安全且高效的方法来处理修改操作。

工作原理

以下是 COW 的基本工作原理:

  1. 初始状态:多个进程或线程共享同一个资源(如内存块)。此时,资源的引用计数器可能为多个。
  2. 读操作:所有进程或线程可以自由地读取共享的资源,而不需要创建副本。
  3. 写操作:当某个进程或线程需要修改资源时,系统会执行以下步骤:
    • 检查资源的引用计数器。如果引用计数器大于1,意味着资源被多个进程或线程共享。
    • 创建资源的一个独立副本,仅供当前进行写操作的进程或线程使用。
    • 将引用计数器减1。
  4. 更新引用:修改后的副本成为当前进程或线程的资源,其他进程或线程仍然引用原始资源。

优缺点

优点

  • 节省内存:多个进程或线程共享资源时,只需要存储一份资源副本,直到需要写操作时才创建副本。
  • 提高性能:减少不必要的资源复制操作,提高了系统的整体性能。

缺点

  • 写操作开销:首次写操作需要进行复制操作,这会引入额外的内存和时间开销。
  • 复杂性增加:实现 COW 机制需要更复杂的内存管理逻辑,增加了系统的复杂性。

示例

以下是一个简单的示例,演示 COW 在内存管理中的应用:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>

int main() {
    pid_t pid;
    int *shared_memory = malloc(sizeof(int));
    *shared_memory = 42;

    printf("Original value: %d\n", *shared_memory);

    pid = fork();

    if (pid < 0) {
        perror("fork failed");
        exit(1);
    } else if (pid == 0) {
        // Child process
        *shared_memory = 100; // This triggers COW
        printf("Child process value: %d\n", *shared_memory);
        exit(0);
    } else {
        // Parent process
        wait(NULL); // Wait for child process to finish
        printf("Parent process value: %d\n", *shared_memory);
    }

    free(shared_memory);
    return 0;
}

在这个示例中,父进程和子进程在 fork() 后最初共享同一个 shared_memory 指针。当子进程尝试修改 shared_memory 的值时,系统会为子进程创建一个独立的副本,而父进程仍然保持对原始值的访问。

缩容条件

哈希表的负载因子小于0.1。

对字典的哈希表rehash步骤
为ht[1]分配空间:扩展操作,那么ht[1] 的大小为第一个大于等于ht[0] .used*2的2的n次幂;收缩操作,那么ht[1] 的大小为第一个大于等于ht[0].used 的2的n次幂
将ht[0]中的数据转移到ht[1]中,在转移的过程中,重新计算键的哈希值和索引值,然后将键值对放置到ht[1]的指定位置。
当ht[0]的所有键值对都迁移到了ht[1]之后(ht[0]变为空表),将ht[0]释放,然后将ht[1]设置成ht[0],最后为ht[1]分配一个空白哈希表:

标签:扩容,COW,Redis,ht,线程,内存,哈希,进程
From: https://www.cnblogs.com/DCFV/p/18288042

相关文章

  • [Redis]ZSet
    通过value查score在Redis的有序集合(zset)中,通过成员(member)获取其对应的分数(score)的复杂度是O(logN),其中N是有序集合中的元素数量。这是因为Redis使用跳跃表(skiplist)和哈希表(hashtable)的组合来实现有序集合。跳跃表用于按顺序存储元素,以便高效地按分数排序和查找范围,而哈......
  • Redis 高阶应用
    生成全局唯一ID全局唯一ID需要满足以下要求:唯一性:在分布式环境中,要全局唯一高可用:在高并发情况下保证可用性高性能:在高并发情况下生成ID的速度必须要快,不能花费太长时间递增性:要确保整体递增的,以便于数据库创建索引安全性:ID的规律性不能太明显,以免信息泄......
  • Redis主从复制实验
    实验环境系统:Linuxmaster(主库):192.168.1.2slave(从库):192.168.1.6两台服务器均安装好Redis数据库,安装步骤点这里开始搭建主从复制环境之前,确认防火墙是否开启,firewall-cmd--list-all实验步骤在slave下的配置进行配置,进入到/usr/local/bin下,viredis.conf配置redis.conf......
  • Redis的zset的zrem命令可以做到O(1)吗?
    事情是这样的,当我用zrem命令去移除value的时候,我知道他之前会做的几个步骤1、查找这个value对应的score(通过zset中的dict)2、根据这个score查找到跳表中的节点3、删除这个节点我就想了一下为什么dict为什么要保存score呢?如果保存的是跳表中的节点,那么不就可以做到删除O(1)......
  • Redis的数据类型
    1、五种常见的数据类型       String            String类型的三种格式                字符串                int              ......
  • 深入刨析Redis存储技术设计艺术(一)
    一、RedisObject1.1、Redis数据存储1.2、RedisObject的数据结构redis的value都封装在redisObject中redisObject的底层实现:redisObject的数据结构如下:server.htypedefstructredisObject{ unsignedtype:4; unsignedencoding:4; unsignedlru......
  • Redis 清理日志文件的策略
    目录Redis清理日志文件的策略1.Redis日志文件2.日志清理策略定期归档压缩归档文件设置日志文件大小限制注意事项结论Redis清理日志文件的策略在使用Redis时,日志文件可能会不断增长,占用磁盘空间。为了保持良好的系统性能和合理利用磁盘空间,我们需要实施一定的......
  • Redis怎么删除某个目录下的数据
    目录Redis怎么删除某个目录下的数据介绍步骤步骤1:连接到Redis步骤2:列出目录下的所有键步骤3:删除目录下的所有键步骤4:验证数据是否删除成功总结Redis怎么删除某个目录下的数据介绍在使用Redis进行缓存或数据存储时,有时候我们需要删除特定目录下的数据。本......
  • redis7.2 安装部署
    #redis7.2安装部署https://redis.io/download/https://github.com/redis/redis/tree/7.2wgethttps://github.com/redis/redis/archive/7.2.3.tar.gzredis-7.2.3]#yum-yinstallgccgcc-c++systemd-develuseraddredis-s/sbin/nologin-M#编译,生成system......
  • 面试必会之Redis篇
    01-你们项目中哪里用到了Redis?在我们的项目中很多地方都用到了Redis,Redis在我们的项目中主要有三个作用:使用Redis做热点数据缓存/接口数据缓存使用Redis存储一些业务数据,例如:验证码,用户信息,用户行为数据,数据计算结果,排行榜数据等使用Redis实现分布......