Redis-字典

场景

最近在读《redis设计与实现》,记录下第4章字典这个常用数据结构。

定义

字典又叫关联数组或者映射,是一种用于保存键值对的数据结构。

用途

Redis的数据库就是使用字典作为底层实现,例如执行set msg "hello world",在数据库中会保存为一个key为’msg‘,value为’hello world‘键值对。

实现

Redis字典底层采用哈希表实现,一个hash表有多个hash节点,每个hash节点就保存了字典中的一个键值对。

typedef struct dict{

	// 类型特定函数
	dictType *type;
	// 私有数据
	void *privdata;

	// 哈希表
	// 包含2个hashTable的数组,ht[0]平常使用,ht[1]rehash时会用到
	dictht ht[2];

	// rehash索引,也标志着rehash的进度
	// rehash不进行时为-1
	int rehashidx;
} dict;

随着字典的键值对的使用,Redis为了让哈希表的负载因子维持在一个合理范围内,会对字典进行rehash操作(包括扩展和收缩)

rehash时机

什么时候进行rehash操作呢?
当满足以下任意一个条件时,程序自动进行rehash(扩展)操作:

  1. 如果Redis服务器没有在执行BGSAVE命令(RDB)或者BGREWRITEAOF(AOF)命令且哈希表负载因子大于等于1
  2. 如果Redis服务器正在执行BGSAVE命令(RDB)或者BGREWRITEAOF(AOF)命令且哈希表负载因子大于等于5

为什么当Redis在进行RDB或者AOF持久化时,需要负载因子大于1呢?最终目标是为了节省内存,Redis在进行持久化时会创建一个子进程执行任务,而大多数操作系统会采用写时复制(copy on write)来提高子进程使用效率,负载因子提高会避免在子进程存在期间进行rehash操作,避免不必要的内存写入操作。

rehash(收缩):hash表负载因子小于等于0.1。

rehash流程:

当满足rehash时机时,程序会进行rehash操作,因为Redis是单线程(epoll+事件提高效率),采用一次性把所有的键值对重写计算和放置的话,势必会有stop the world,Redis采用渐进式rehash解决了长时间的阻塞问题。
具体流程:

  1. 为ht[1]分配空间size,其大小取决于是扩展还是收缩以及ht[0]实际使用数量 used
    扩展:size = 第一个大于等于used * 2的2的n次幂
    收缩:size = 第一个大于等于used的2的n次幂
    当n为2的次幂时 (n - 1) & size = n % size,即可以用与运算替换取模运算,为什么要替换,因为速度快啊,为什么快啊,可以通过查看汇编指令观察二者的CPU周期总数,前者比后者少的多,所以更快
  2. rehash所有ht[0]键值对,把元素放到ht[1]上
  3. 释放ht[0],将ht[1]设置为ht[0],在ht[1]新建一个空白hash表,为下一次rehash做准备

在渐进式rehash期间,rehashidx = 0 标志着rehash正式开始,在对字典执行删除、查找或者更新时,程序除了执行指定的操作外,会顺带将ht[0]在rehashidx索引上的所有键值对rehash至ht[1],当rehash完成后rehashidx加一,当所有ht[0]都被移至ht[1]后rehashidx置为-1。执行添加时,程序只在ht[1]添加,使得ht[0]的哈希表是逐渐减少的。

在此期间删除、查找、更新操作需要同时对ht[0]和ht[1]进行操作,以免命令不能被正确执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值