redis底层设计(二)——内存映射数据结构
我们继续接着上一篇博客,今天来看看内存映射数据结构。
上篇我们讲了内部数据结构,虽然内部数据结构非常强大,但是创建一系列完整的数据结构本身也是一件相当耗费时间的工作,当一个对象包含的元素数量并不多,或者元素本身的体积并不大时,使用代价高昂的内部数据结构并不是最好的办法。因此我们会用内存映射数据结构来代替内部数据结构。
内存映射数据结构是一系列经过特殊编码的字节序列,创建他们所消耗的内存通常比作用类似的内部数据结构要少得多,如果使用得当,内存映射数据结构可以为用户节省大量的内存。不过,内存映射数据结构的编码和操作方式要比内部数据结构复杂的多,所以内存映射数据结构所占用的CPU时间会比作用类似的内部结构要多。
2.1整数集合
整数集合(intset)用于有序、无重复地保存多个整数值,他会根据元素的值,自动选择该用什么长度的整数类型来保存元素。
2.1.1 整数集合的应用
intset是集合键的底层实现之一,如果一个集合满足:
* 值保存着整数元素;
* 元素的数量不多;
那么就会使用intset来保存集合元素。
2.1.2 数据结构和主要操作
typedef struct intset {
// 保存元素所使用的类型的长度
uint32_t encoding;
// 元素个数
uint32_t length;
// 保存元素的数组
int8_t contents[];
} intset;
encoding 的值可以是以下三个常量的其中一个(定义位于intset.c ):
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
contents数组是实际保存元素的地方,数组有一下两个特性:
* 没有重复元素;
* 从小到大排序;
contents的 int8_t类型只是作为一个占位符使用,intset不使用int8_t类型保存任何元素。新增元素默认的encoding是int16_t,当添加的新元素不适合于当前intset的编码类型时,intset集合将会进行升级。
2.1.3 小结
* intset用于有序、无重复的保存多个整数值。他会根据元素的值,自动选择该用什么长度的整数类型来保存元素;
* 当一个位长度更长的整数值添加到intset时,需要对intset进行升级,新intset中的每个元素的位长度都等于新添加值的位长度,但原有元素的值不变;
* 升级会引起整个intset进行内存重分配,并移动集合中的所有元素,这个操作的复杂度为O(N);
* intset只支持升级,不支持降级;
* intset是有序的,程序使用二分法查找算法来实现查找操作,复杂度为O(lgN);
2.2 压缩列表
ziplist 是由一系列特殊编码的内存块构成的列表,一个ziplist 可以包含多个节点(entry),每个节点可以保存一个长度受限的字符数组(不以\0结尾的char数组)或者整数,包括:
• 字符数组
– 长度小于等于63 (26 - 1)字节的字符数组
– 长度小于等于16383 (214 - 1)字节的字符数组
– 长度小于等于4294967295 (232 - 1)字节的字符数组
• 整数
– 4 位长,介于0 至12 之间的无符号整数
– 1 字节长,有符号整数
– 3 字节长,有符号整数
– int16_t 类型整数
– int32_t 类型整数
– int64_t 类型整数
因为ziplist节约内存的性质,它被哈希键、列表建和有序集合键作为初始化的底层实现来使用。
2.2.1 ziplist的结构:
因为ziplist header 部分的长度总是固定的(4 字节+ 4 字节+ 2 字节),因此将指针移动到表头节点的复杂度为常数时间;除此之外,因为表尾节点的地址可以通过zltail 计算得出,因此将指针移动到表尾节点的复杂度也为常数时间。
因为ziplist 由连续的内存块构成,在最坏情况下,当ziplistPush 、ziplistDelete 这类对节点进行增加或删除的函数之后,程序需要执行一种称为连锁更新的动作来维持ziplist 结构本身的性质,所以这些函数的最坏复杂度都为O(N2) 。不过,因为这种最坏情况出现的概率并不高,所以大可以放心使用ziplist ,而不必太担心出现最坏情况。
2.2.2 节点的构成:
pre_entry_length:记录了前一个节点的长度,通过这个值,可以进行指针计算,从而跳转到上一个节点。(注:若前一个节点的长度小于254字节,则使用一个字节保存pre_entry_length的值,若大于等于254,则使用5个字节保存,其中第一个字节保存254,后4个字节保存前一个节点的实际长度);
encoding:记录了content的数据类型,长度为2个bit,它的值可以是00、01、10和11(其中00、01和10表示Content中保存着字符数组;11表示content中保存着整数);
length:记录了content的数据长度;
content:保存着节点的内容
2.2.3 小结:
* ziplist是由一系列特殊编码的内存块构成的列表,它可以保存字符数组或整数值,它还是哈希键、列表键和有序集合键的底层实现之一。
* 添加和删除ziplist节点有可能会引起连锁更新,因此,添加和删除操作的最坏复杂度为O(N2),不过,因为连锁更新的出现概率并不高,所以一般可以将添加和删除操作的复杂度视为O(N)。
redis底层设计(二)——内存映射数据结构的更多相关文章
- redisbook笔记——redis内存映射数据结构
虽然内部数据结构非常强大,但是创建一系列完整的数据结构本身也是一件相当耗费内存的工作,当一个对象包含的元素数量并不多,或者元素本身的体积并不大时,使用代价高昂的内部数据结构并不是最好的办法. 为了解决 ...
- redis学习笔记——内存映射数据结构
内存映射数据结构 解决问题:当一个对象包含的元素数量并不多,或者元素本身的体积并不大时,使用代价高昂的内部数据结构并不是最好的办法. 内存映射数据结构是一系列经过特殊编码的字节序列,创建它们所消耗的内 ...
- redis底层设计(一)——内部数据结构
redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set ...
- redis底层实现的几种数据结构
redis底层数据结构 一.简单动态字符串(SDS) 定义: struct sdshdr{ int len; //SDS所保存的字符串长度 int free //记录buf数组中为使用的字节数量 ...
- redis底层设计(三)——redis数据类型
今天我们来看一下redis的数据类型.既然redis的键值对可以保存不同类型的值,那么很自然就需要对键值对的类型进行检查以及多态处理.下面我们将对redis所使用的对象系统进行了解,并分别观察字符串. ...
- redis底层设计(五)——内部运作机制
5.1 数据库 5.1.1 数据库的结构: Redis 中的每个数据库,都由一个redis.h/redisDb 结构表示: typedef struct redisDb { // 保存着数据库以整数表 ...
- redis底层设计(四)——功能的实现
redis中我们会经常用到事务.订阅与发布.Lua脚本以及慢查询日志,接下来我们就一一对他们进行探讨学习. 4.1事务 redis通过MULTI.DISCARD.EXEC和WATCH四个命令来实现事务 ...
- 10w+QPS 的 Redis 真的只是因为单线程和内存?360° 深入底层设计为你揭开 Redis 神秘面纱!
原文链接:10w+QPS 的 Redis 真的只是因为单线程和内存?360° 深入底层设计为你揭开 Redis 神秘面纱! 你以为 Redis 这么快仅仅因为单线程和基于内存? 那么你想得太少了,我个 ...
- Redis底层数据结构详解
上一篇说了Redis有五种数据类型,今天就来聊一下Redis底层的数据结构是什么样的.是这一周看了<redis设计与实现>一书,现来总结一下.(看书总是非常烦躁的!) Redis是由C语言 ...
随机推荐
- 全方位理解Android权限之底层实现概览
0000 这个阶段搞了很多和Android文件权限相关的问题,虽然一知半解,但也算是对Android权限机制有一些自己的理解.遂将这些内容整理出来.因为权限这部分涉及到的内容很多,故将知识分为几块内容 ...
- java基础(五) String性质深入解析
引言 本文将讲解String的几个性质. 一.String的不可变性 对于初学者来说,很容易误认为String对象是可以改变的,特别是+链接时,对象似乎真的改变了.然而,String对象一经创 ...
- 杨学明老师推出全新课程--《敏捷开发&IPD和敏捷开发结合的实践》
课时:13小时(2天) 敏捷开发&IPD和敏捷开发结合的实践 讲 师:杨学明 [课程背景] 集成产品开发(IPD).集成能力成熟度模型(CMMI).敏捷开发(Agile Developmen ...
- Team Services的打包管理
Team Services的打包管理 概述 Package Management (打包管理)是一种扩展,可以更容易地发现.安装和发布包. 它与Team Services中心如构建功能深度集成,这样打 ...
- windows 获取用户的Sid的方法
正常获取: whoami /user 如果要获取其他用户的SID就显得力不从心了,我们可以使用微软提供的系统工具 Sysinternals Suite 下载地址:https://docs.micros ...
- 记一次zookeeper单机伪集群分布
zookeeper的各版本(历史版本)下载地址:http://apache.org/dist/zookeeper/ 环境>:linux 下载的zookeeper解压成3个
- python len()函数的用法
函数:len() 返回字符串.列表.字典.元组等长度. 语法:len(str) str:要计算的字符串.列表.字典.元组等 返回值:字符串.列表.字典.元组等元素的长度. Test: 1:计算字符串的 ...
- 第10章 嵌入式Linux 的调试技术
10.1 打印内核调试信息:printk printk位函数运行在内核空间, printf函数运行在用户空间.也就是说,像Linux 驱动这样的Linux内核程序只能使用printk函数输出调试信息 ...
- puppet 横向扩展(一)
目录 1. 概述 2. 实验环境 3. 实验步骤 3.1. 创建puppetmaster的rack环境 3.2. 配置文件设置 3.3. 补充说明 3.4. 测试配置结果 3.4.1. 默认的负载均衡 ...
- wrk 压力测试 http benchmark POST接口
简单的 http 性能测试工具 wrk.git 一个简单的 http benchmark 工具, 能做很多基本的 http 性能测试. wrk 的一个很好的特性就是能用很少的线程压出很大的并发量. 原 ...