简单动态字符串(sds)
SDS遵循C字符串以空字符结尾的惯例,保存空字符的1字节不计算在sds的len属性里面,并且为空字符分配额外的1字节空间,以及添加空字符到到字符串末尾等操作,都是有sds函数自动完成的,所以这个空字符对于sds的使用者来说是完全透明的。遵循空字符结尾这一惯例的的好处是,sds可以直接重用一部分C字符串函数库里面的函数。
优点
空间预分配
N次变更最多引起N次内存重分配。修改后sds的len小于1MB,程序分配和len属性同样大小的未使用空间。修改后的长度大于等于1MB,程序分配1MB未使用的空间。对sds修改,只有在需要进行空间扩展时,才会去额外分配未使用空间,这样的策略就可以减少连续执行字符串增长操作所需的内存重分配次数。
惰性空间释放
当sds需要缩短保存的字符串时,程序并不是立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,等待将来使用。
链表
链表提供了高效的节点重排能力,以及顺序型的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。redis实现的是双端链表。
场景
链表广泛应用于redis的很多功能,如列表键,发布与订阅,慢查询,监视器等。当一个列表键包含数量比较多的元素,或者是列表中包含的元素都是比较长的字符串时,redis就使用链表作为列表键的底层实现。
字典
字典,又称为符号表,关联数组,或映射,是一种用于保存键值对的抽象数据结构。redis是使用哈希表作为底层实现,一个哈希表可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。每个字典包含两个哈希表,一个平时用,一个rehash时用(空间换时间)。被用作数据库底层或者哈希键的底层实现时,使用MurmurHash2算法计算哈希值。
哈希键冲突
每个哈希节点都有一个next指针,当哈希值重复时使用单向链表解决键冲突问题,而且总是将新节点添加到链表的表头位置,排在其他已有节点前面。
rehash
哈希表的收缩扩容,为了让哈希表的负载因子维持在一个合理范围之内,当哈希表保存的键值对数量太多或者数量太少时,就需要对哈希表的大小进行扩展或者收缩。扩展和收缩可以通过rehash操作来完成。扩展是设定新哈希表为第一个大于等于当前哈希表used*2 的 2的N次幂;收缩是设定新哈希表为第一个大于等于当前哈希表used的2的N次幂。
当满足以下任一个条件时就会进行扩展:
1.服务器没有在执行BGSAVE或者BGREWRITEAOF命令,且负载因子大于等于1;
2.服务器正在执行BGSAVE或者BGREWRITEAOF命令,且负载因子大于等于5;
#负载因子 = used/size
当负载因子小于0.1时,进行收缩操作。
渐进式rehash
扩展或收缩哈希表需要将当前哈希表的所有键值迁移到另一个预备哈希表中,但是这个rehash并不是一次性,集中式的完成的,而是分多次,渐进式完成的。
1.为预备哈希表分配空间,字典同时持有两个哈希表。
2.字典中维持的索引计数器rehashidx设置为0,标识rehash开始
3.rehash期间,每次对字典执行添加,删除,查找或更新操作时,程序除执行指定的操作外,还顺带把当前hash表在rehashidx索引上的所有键值rehash到预备哈希表中,完成后,rehashidx属性加1.
4.字典操作的不断执行,最终在某个时间点上,当前哈希表中所有键值都被rehash至预备哈希表中,这时将rehashidx的值设定为-1,标识rehash已完成。
rehash时删除,查找,更新操作会在两个哈希表上进行,但添加只会添加到预备哈希表中。
跳跃表
跳跃表是一种有序的数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。节点查找复杂度平均O(logN),最坏O(N)。 具体实现.
效率
在大部分情况下,跳跃表的效率可以和平衡树媲美,实现方式又比平衡树更简单,有不少程序使用跳跃表来代替平衡树。
场景
redis只在两个地方用到了跳跃表,一个是有序集合键;一个是集群节点中用作内部数据结构。跳跃表是有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者是有序集合中元素的成员是比较长的字符串时,就使用跳跃表来作为有序集合键的底层实现。
整数集合
整数集合(intset)底层实现为数组,是redis用于保存整数值的集合抽象数据结构,可以保存的类型为int16_t,int32_t,int64_t的整数值,并且保证集合中有序且不会出现重复元素。
场景
当一个集合只包含整数值元素,并且这个集合的元素数量不多时,redis就使用整数集合作为集合键的底层实现。
升级
当一个整数集合只有int16_t的整数时,每个整数使用16位保存,新加入一个int32_t或者是int64_t的整数时,整体数组会进行整体升级,重新分配空间,每个整数改为用32或者64位保存。
优点:提升灵活性(可同时存放多种类型数据);节约内存(在必要时才升级)
整数集合只支持升级不支持降级
压缩列表
压缩列表是redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。添加新节点或者删除节点都可能引发连锁更新操作,但连锁更新出现的几率并不高,而且即使出现造成性能问题的几率也很低。
场景
压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么是小整数值,要么长度比较短的字符串,redis就会使用压缩列表来做列表键的底层实现。