readis 内部数据结构
- 字符串
- set,get
- 列表
- lpush,rpush,lrange
- 哈希
- hset,hget,hgetall
- hmset,hmget
- 集合
- sadd
- smembers
- 有序集
- zset
- 实现字符串对象(StingObject)
- 在Redis程序内部用作char* 类型的替代品
127.0.0.1:6379> help sadd
SADD key member [member ...]
summary: Add one or more members to a set
since: 1.0.0
group: set 127.0.0.1:6379> sadd nosql "Redis" "MongoDB"
(integer) 2 127.0.0.1:6379> help SMEMBERS SMEMBERS key
summary: Get all the members in a set
since: 1.0.0
group: set 127.0.0.1:6379> smembers nosql
1) "MongoDB"
2) "Redis"
- 客户端传入服务器的协议内容
- aof缓存,返回给客户端的回复
- 等等这些重要的内容都是由sds类型来保存的
- 每次计算字符串长度(strlen)的复杂度为 θ(N)
- 对字符串进行 N 次追加(append),必定需要对字符串进行 N 次内存重分配

.png)
- 通过 len 属性,sdshdr 可以实现复杂度为 θ(1) 的长度计算操作。
- 通过对 buf 分配一些额外的空间,并使用 free 记录未使用空间的大小,
- sdshdr可以让执行追加操作所需的内存重分配次数大大减少。
127.0.0.1:> set msg "hello world"
OK
127.0.0.1:> append msg " again!"
(integer)
127.0.0.1:> get msg
"hello world again!"
struct sdshdr {
len = ;
free = ;
buf = "hello world\0";
}
struct sdshdr {
len = ;
free = ;
// 空白的地方为预分配空间,共 18 + 18 + 1 个字节
buf = "hello world again!\0 ";
}
- 预分配空间足够,无须再进行空间分配
- 如果新字符串的总长度小于 SDS_MAX_PREALLOC
- 那么为字符串分配 2 倍于所需长度的空间
- 否则就分配所需长度加上 SDS_MAX_PREALLOC 数量的空间
- append带来的预分配额外空间,不会被释放,除非字符串对应的键被删除;
- 通常append字符串键数量不多,因此不是什么问题
- 如果append操作的键很多,而字符串的体积又很大的话,就会很浪费内存
- 需要修改Redis服务器,让它定时释放一些字符串预分配空间,从而有效使用内存
- Redis 的字符串表示为 sds ,而不是 C 字符串(以 \0 结尾的 char*)。
- 对比 C 字符串,sds 有以下特性:
- 可以高效地执行长度计算( strlen);
- 可以高效地执行追加操作( append);
- 二进制安全;
- sds 会为追加操作进行优化:加快追加操作的速度,并降低内存分配的次数,
- 代价是多占用了一些内存,而且这些内存不会被主动释放。
- 它既是 Redis 列表结构的底层实现之一
- 还被大量 Redis 模块所使用,用于构建 Redis 的其他功能
redis> RPUSH brands Apple Microsoft Google
(integer)
redis> LPOP brands
"Apple"
redis> LLEN brands
(integer)
redis> LRANGE brands -
) "Microsoft"
) "Google"
- 双端链表
- 压缩列表
- 事务模块使用双端链表来按顺序保存输入的命令;
- 服务器模块使用双端链表来保存多个客户端;
- 订阅/发送模块使用双端链表来保存订阅模式的多个客户端;
- 事件模块使用双端链表来保存时间事件( time event);
.png)

- listNode 带有 prev 和 next 两个指针,因此,对链表的遍历可以在两个方向上进行:从表头到表尾,或者从表尾到表头。
- list 保存了 head 和 tail 两个指针,因此,对链表的表头和表尾进行插入的复杂度都为θ(1)
- 这是高效实现 LPUSH 、 RPOP 、 RPOPLPUSH 等命令的关键。
- list 带有保存节点数量的 len 属性,所以计算链表长度的复杂度仅为 θ(1) ,这也保证了 LLEN 命令不会成为性能瓶颈。
- 沿着节点的 next 指针前进,从表头向表尾迭代
- 沿着节点的 prev 指针前进,从表尾向表头迭代
- Redis 实现了自己的双端链表结构。
- 双端链表主要有两个作用:
- 作为 Redis 列表类型的底层实现之一;
- 作为通用数据结构,被其他功能模块所使用;
- 双端链表及其节点的性能特性如下:
- 节点带有前驱和后继指针,访问前驱节点和后继节点的复杂度为 O(1) ,并且对链表的迭代可以在从表头到表尾和从表尾到表头两个方向进行;
- 链表带有指向表头和表尾的指针,因此对表头和表尾进行处理的复杂度为 O(1)
- 链表带有记录节点数量的属性,所以可以在 O(1) 复杂度内返回链表的节点数量(长度)
- 实现数据库键空间( key space)
- 用作 Hash 类型键的其中一种底层实现
redis> FLUSHDB
OK
##执行 DBSIZE 则返回键空间上现有的键值对:
redis> DBSIZE
(integer)
##还可以用 SET 设置一个字符串键到键空间,并用 GET 从键空间中取出该字符串键的值:
redis> SET number
OK
redis> GET number
""
redis> DBSIZE
(integer)
- 字典;
- 压缩列表
- 最简单的就是使用链表或数组,但是这种方式只适用于元素个数不多的情况下
- 要兼顾高效和简单性,可以使用哈希表
- 如果追求更为稳定的性能特征,并且希望高效地实现排序操作的话,则可以使用更为复
杂的平衡树;
/*
* 字典
**
每个字典使用两个哈希表,用于实现渐进式 rehash
*/
typedef struct dict {
// 特定于类型的处理函数
dictType *type;
// 类型处理函数的私有数据
void *privdata;
// 哈希表( 2 个)
dictht ht[];
// 记录 rehash 进度的标志,值为-1 表示 rehash 未进行
int rehashidx;
// 当前正在运作的安全迭代器数量
int iterators;
} dict;


- sizemask:数组长度掩码,用于计算索引值
- size:数组长度
- used: 节点个数
- used/size :判断是否需要rehash
- 把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,
- 然后就将该数字对数组长度进行取余,取余结果就当作数组的下标(也就是哈希索引),
- 将value存储在以该数字为下标的数组空间里。
- 再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,
- 如此一来,就可以充分利用到数组的定位性能进行数据定位
- MurmurHash2 32 bit 算法:这种算法的分布率和速度都非常好
- 基于djb算法实现的一个大小写无关散列算法
- ht[0]->table 的空间分配将在第一次往字典添加键值对时进行;
- ht[1]->table 的空间分配将在 rehash 开始时进行;
- 如果字典为未初始化(也即是字典的 0 号哈希表的 table 属性为空),那么程序需要对 0号哈希表进行初始化;
- 如果在插入时发生了键碰撞,那么程序需要处理碰撞;
- 如果插入新元素使得字典满足了 rehash 条件,那么需要启动相应的 rehash 程序

.png)
- 比率在1:1时,哈希表性能最好
- 说明没有发生哈希碰撞,每个哈希索引值下面只有一个节点
- 如果节点数量比哈希表的大小要大很多的话,
- 实际上那么哈希表就会退化成多个链表,哈希表本身的性能优势就不在了

.png)
- 自然 rehash : ratio >= 1 ,且变量 dict_can_resize 为真。
- 强 制 rehash : ratio 大 于 变 量 dict_force_resize_ratio
- 目 前 版 本 中,dict_force_resize_ratio的值为5。
- 创建一个比 ht[0]->table 更大的 ht[1]->table ;
- 将ht[0]->table中的所有键值对迁移到ht[1]->table;
- 将原有 ht[0] 的数据清空,并将 ht[1] 替换为新的 ht[0]
- 创建新的ht[1]
- 将字典的 rehashidx 属性设置为 -1 ,标识 rehash 已停止;
.png)

- 查找,删除,修改等操作需要在ht[0],ht[1]两个哈希表中进行
- 增加操作 是在ht[1]上做的。
- 1. 创建一个比 ht[0]->table 小的 ht[1]->table ;
- 2. 将 ht[0]->table 中的所有键值对迁移到 ht[1]->table ;
- 3. 将原有 ht[0] 的数据清空,并将 ht[1] 替换为新的 ht[0] ;
- 字典的扩展操作是自动触发的(不管是自动扩展还是强制扩展);
- 而字典的收缩操作则是由程序手动执行。
- 当字典用于实现哈希键的时候,每次从字典中删除一个键值对,判断是否需要收缩字典
- 如果字典达到了收缩的标准,程序将立即对字典进行收缩;
- 当字典用于实现数据库键空间 ( key space)的时候,
- 收缩的时机由redis.c/tryResizeHashTables 函数决定
- 字典由键值对构成的抽象数据结构。
- Redis 中的数据库和哈希键都基于字典来实现。
- Redis 字典的底层实现为哈希表,每个字典使用两个哈希表,一般情况下只使用 0 号哈希表,只有在 rehash 进行时,才会同时使用 0 号和 1 号哈希表。
- 哈希表使用链地址法来解决键冲突的问题。
- Rehash 可以用于扩展或收缩哈希表。
- 对哈希表的 rehash 是分多次、渐进式地进行的。
127.0.0.1:6379> help zadd
ZADD key score member [score member ...]
summary: Add one or more members to a sorted set, or update its score if it already exists
since: 1.2.0
group: sorted_set 127.0.0.1:6379> help zrange ZRANGE key start stop [WITHSCORES]
summary: Return a range of members in a sorted set, by index
since: 1.2.0
group: sorted_set
127.0.0.1:> zadd s
(integer) 127.0.0.1:> zrange s
) ""
) "" 127.0.0.1:> zrange s
) ""
) "" 127.0.0.1:> zrange s -
) ""
) ""
) ""
) ""
2. 进行对比操作时,不仅要检查 score 值,还要检查 member :当 score 值可以重复时,
1. 只保存着整数元素;
2. 元素的数量不多;
- Intset 用于有序、无重复地保存多个整数值,它会根据元素的值,自动选择该用什么长度的整数类型来保存元素。
- 当一个位长度更长的整数值添加到 intset 时,需要对 intset 进行升级,新 intset 中每个元素的位长度都等于新添加值的位长度,但原有元素的值不变。
- 升级会引起整个 intset 进行内存重分配,并移动集合中的所有元素,这个操作的复杂度为 O(N) 。
- Intset 只支持升级,不支持降级。
- Intset 是有序的,程序使用二分查找算法来实现查找操作,复杂度为 O(lg N) 。
- Ziplist 是由一系列特殊编码的内存块构成的列表,一个 ziplist 可以包含多个节点( entry),
- 每个节点可以保存一个长度受限的字符数组(不以 \0 结尾的 char 数组)或者整数
- 是哈希键、列表键和有序集合键的底层实现之一。
- 添加和删除 ziplist 节点有可能会引起连锁更新,
- 因此,添加和删除操作的最坏复杂度为O(N 2) ,
- 不过,因为连锁更新的出现概率并不高,所以一般可以将添加和删除操作的复杂度视为 O(N)
readis 内部数据结构的更多相关文章
- redis 源码阅读 内部数据结构--字符串
redis的内部数据结构主要有:字符串,双端链表,字典,跳跃表. 这里主要记录redise字符串的设计.相关的源码位于:src/sds.h 和 src/sds.c. 一 字符串 sds的结构体 s ...
- 你真的懂redis的数据结构了吗?redis内部数据结构和外部数据结构揭秘
Redis有哪些数据结构? 字符串String.字典Hash.列表List.集合Set.有序集合SortedSet. 很多人面试时都遇到过这种场景吧? 其实除了上面的几种常见数据结构,还需要加上数据结 ...
- redis底层设计(一)——内部数据结构
redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set ...
- [转]Redis内部数据结构详解-sds
本文是<Redis内部数据结构详解>系列的第二篇,讲述Redis中使用最多的一个基础数据结构:sds. 不管在哪门编程语言当中,字符串都几乎是使用最多的数据结构.sds正是在Redis中被 ...
- redis内部数据结构和外部数据结构揭秘
Redis有哪些数据结构? 字符串String.字典Hash.列表List.集合Set.有序集合SortedSet. 很多人面试时都遇到过这种场景吧? 其实除了上面的几种常见数据结构,还需要加上数据结 ...
- redis内部数据结构
redis内部数据结构,是指redis在自身的构建中,基于这些特定的内部数据结构进行的. 简单动态字符串:Simple Dynamic String 双端链表 字典:Dictonary 跳跃表:ski ...
- Redis学习笔记-Redis内部数据结构
Redis内部数据结构 Redis和其他key-value数据库的很大区别是它支持非字符串类型的value值.它支持的value值的类型如下: sds (simple dynamic string) ...
- 你真的懂了redis的数据结构吗?redis内部数据结构和外部数据结构揭秘
原文链接:https://mp.weixin.qq.com/s/hKpAxPE-9HJgV6GEdV4WoA Redis有哪些数据结构? 字符串String.字典Hash.列表List.集合Set.有 ...
- 探索Redis设计与实现6:Redis内部数据结构详解——skiplist
本文转自互联网 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial ...
随机推荐
- oracle JOB 查询 添加 修改 删除
-------------查询JOB----------------- select job, what, next_date, next_sec, sysdate, failures, broken ...
- Asp.net自定义控件开发任我行(2)-TagPrefix标签
摘要 前面我们已经做了一个最简单的TextBox的马甲,此篇文章,我们来讲讲自定义控件的标签.大家可能看到了上一篇中拖放进来的代码是 <cc1:TextEdit ID="TextEdi ...
- 实时视频h5
http://www.cnblogs.com/dotfun/p/4286878.html
- IOS开发学习笔记008-预处理
预处理 1.宏定义 2.条件编译 3.文件包含 注意: 1.所有预处理都是以#开头,并且结尾不用分号. 2.宏名一般用大写字母,以便与变量名区别开来,但用小写也没有语法错误 3.作用域也是从定义到代码 ...
- Mybatis使用-Error attempting to get column 'type' from result set. / '255' in column '4' is outside valid range for the datatype TINYINT.
一.遇到的问题是这样的: [RemoteTestNG] detected TestNG version 6.9.10log4j: Parsing for [root] with value=[DEBU ...
- Leetcode 523.连续的子数组和
连续的子数组和 给定一个包含非负数的数组和一个目标整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数. 示例 1: ...
- BZOJ 1096: [ZJOI2007]仓库建设(DP+斜率优化)
[ZJOI2007]仓库建设 Description L公司有N个工厂,由高到底分布在一座山上.如图所示,工厂1在山顶,工厂N在山脚.由于这座山处于高原内陆地区(干燥少雨),L公司一般把产品直接堆放在 ...
- 编译静态库tinyxml2
tinyxml的makefile文件默认是编译可执行的二进制文件xmltest. 需要改成静态库. 更改OUTPUT := xmltest 为:OUTPUT := libtinyxml.a 删除SR ...
- python(6)-- 模块
python模块: 定义:Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句. 作用:(a) 模块让你能够有逻辑地组织你 ...
- 【02】【转】Nodejs学习笔记(三)--- 事件模块
目录 简介及资料 事件常用函数及使用 emitter.on(event, listener) emitter.emit(event, [arg1], [arg2], [...]) emitter.on ...