SDS

SDS是Redis中String的底层数据结构,数据结构如下,SDS保留了传统的C字符串表达方式即数组的最后一个元素是'/0'结尾。此外还添加了两个字段len和free,其中len表示字符串长度,free代表空闲空间。

class sds {
int len;
int free;
char[] buf;
}

那么这两个添加的元素有什么作用呢?

  • 常数复杂度获取字符长度。首先第一点就是C数组是不记录长度的,那么为了获取字符串的长度每次就得遍历数组内的全部元素,这无疑会增加时间损耗,有了len就可以实时记录数组长度,获取字符串长度的时间复杂度由O(n)变成了O(1)
  • 杜绝缓冲溢出。我们知道传统的C数组是不记录本身长度的,默认会认为你为要纳入数组的元素分配了足够的内存,如果不对边界进行校验的话可能就会发生缓冲区溢出的情况,举个例子,比如两个字符串数组S1,S2长度都为6,S1 = [ 'H','e', 'l', 'l', 'o', '/0']S2 = [ 'W','o', 'r, 'l', 'd, '/0']两个数组刚好在一段连续的内存中即长度为12的内存中,如果S1数组的index = 6 添加字符'w',由于S1长度不足7,那么可能就会溢出到S2的 index = 0 的内存里。而SDS添加元素的API里通过对free的校验若内存不足则进行扩展,则完全避免了这种缓冲溢出的情况。
  • 减少内存分配的次数。上面提到如果free的空间不满足添加元素长度的需求则需要对SDS的buf进行扩容。此外还有减少元素时回收内存避免内存泄漏。Redis对性能要求是很高的,尤其是内存数据修改更是频繁,频繁的更改和扩容会对性能产生巨大影响,因此Redis使用两种策略来避免这种情况。第一是空间预分配,空间预分配会在buf长度小于1MB时分配和len相等的free长度,大于1MB时固定额外分配1MB的空间。第二是惰性空间释放,buf缩容时free会增加但不会实际回收对应内存,以便后续使用。
  • 二进制安全。C数组中除了字符串的末尾之外是不能记录空字符的,否则会被认为是字符串的结尾。而SDS由于len等有标识字符长度所以是二进制安全的可以用来保存各种数据。
  • 兼容C函数API。由于SDS的本质还是buf这个字符数组,所以本质上可以使用如strcmp等C函数。

LinkedList(链表)

典型的双端链表结构,无环,带有链表计数器,支持多态

// 节点数据结构
class ListNode {
ListNode prev;
ListNode next;
int value;
} class List {
ListNode head;
ListNode tail;
long len;
// 赋值连表保存的值
void dup();
// 比较连表的值和某个输入值
int match();
// 释放连表保存的值
void free();
}

Dict(字典)

数据结构:字典是Redis最基础的结构,实现和Java的HashMap类似。即拉链法解决hash冲突的方式。数据结构如下。

// 键值对数据结构
class DictEntry<K, V> {
K key;
V value;
// 拉链法解决hash冲突,一个key一个bucket,相同buket路由到同一个链表
dictEntry next;
}
// hash表数据结构
class Dicht{
DictEntry[] table;
// hash表大小,即table数组大小
int size;
// 掩码,用于计算索引值,总是等于size减1
int sizeMask = size - 1;
// 键值对数量
long used;
}
// dict字典数据结构
class Dict {
// ht相当于master加replicat,ht[1]只在reshash的时候使用
Dict ht[2];
// -1 不在rehash,>= 0 rehash中
int rehashidx;
}

哈希冲突:当一个元素加入dict时,会先对hash函数计算对象的hash值,然后再和sizeMask作与运算,这里的与运算其实就是对size取余的操作(因为size为2nsizeMask = size - 1,那么sizeMask低位都为1),以便将hash值放在指定范围的数组下标内。如果产生hash冲突会通过拉链法将相同hash值的数据通过链表链接从而放到数组的一个bucket下标内。

扩展与收缩:hash表中有一个loadFactor即负载因子,计算方式:loadFactor= ht[0].used / ht[0].size,其实就是hash表的使用率,为了维持负载因子在合理的范围内,就需要对hash表进行扩展和收缩,触发扩展的条件是:loadFactor > 1 && noBGSAVEOrBGREWRITEAOF(没有BgSave或BgRewriteAof) || loadFactor > 5,触发收缩的条件是loadFactor < 0.1实现扩展收缩的条件是rehash,rehash大致步骤如下

  1. ht[1]分配空间,大小是第一个大于或等于ht[0].used*2的2的次幂。
  2. ht[0]的元素rehash到ht[1],因为此处sizeMask变了,所以对应的与运算的值也会发生改变。
  3. 交换ht[0]ht[1],即释放ht[0]的空间,将ht[0]指向ht[1]

渐进式rehash:如果hash表的元素很多,一次性rehash可能会造成性能问题甚至一段时间不可用。所以rehash一般是渐进式的,渐进式的rehash步骤如下

  1. ht[1]分配空间,置rehashidx = 0
  2. 在元素进行添加、更新、查找、删除时会顺带将在索引为 rehashidx的元素从ht[0]rehash到ht[1]上。每个索引rehash完成后rehashidx++
  3. 最终所有索引的键值对完成rehash时,交换ht[0]ht[1],置rehashidx = -1

注意在渐进式rehash过程中,元素查找涉及两个hash表,会先查找ht[0]再对ht[1]进行查找

SkipList(跳跃表)

跳表是一种类似于树的层级结构,数据结构如下:

class SkipListNode{
// 当前节点在每一层的标识,maxSize = 32
Level[] level;
// 后退指针,即指向上一节点
SkipListNode backword;
// 当前节点值
Object value;
// value的score值,通过这个进行排序,从小到大
int score;
// 每层的数据结构
class Level {
// 当前层级对应的下一个节点
SkipListNode next;
// 当前层级两个节点跨度,通过这个可以快速在任意层级判断当前的元素位置
int span;
}
} class SkipList {
// 头节点
SkipListNode head;
// 尾节点
SkipListNode tail;
// 跳表元素长度
int length;
// 节点中层级最大的值
int level;
}

跳表是一层一层向上选举节点,比如一组链表在Level[0]时是0->1->2->3->4->5->6->7->8],通过随机向上选举那么Level[1]的表示可能为0->3->6->8。同理可以继续晚上选举。通过这种方式从而达到O(logn)的查询时间复杂度。

IntSet(整数集合)

整数集合如其名记录一系列整数,是底层集合键的底层实现之一,数据结构如下

class IntSet{
// int_8,int_32,int_64
Object encoding;
// 保存整数集合的
int[] contents;
// 整数集合的长度
int length;
}

contents中的元素是有序且无重复的,当新加入的元素大于encoding所能支持的长度时,IntSet便会进行升级操作,即修改contents的分配内存策略,并重新索引其中的元素,需要注意的是并不支持降级操作。这种升级策略尽可能保证了数据结构的灵活性的同时节约了内存。

ZipList(压缩链表)

压缩链表是链表键和哈希键的底层实现之一。数据结构如下

// 压缩链表节点
class ZipListNode {
// value的类型及长度
int encoding;
// 实际值
Object contents;
// 前一节点长度,1byte(前一节点长度小于255)或者5byte(前一节点长度大于255)
Object previousEntryLength;
}
// 压缩链表
class ZipList{
// 压缩链表字节数,4bytes
int zlBytes;
// 压缩链表结尾距离起始节点的地址偏移量,4bytes
int zlTail;
// 节点数量 2bytes
short zlLen;
// 压缩链表元素,不定长
ZipListNode[] entries;
// 1byte 固定0Xff即255
byte zllend;
}

压缩链表在链表键或哈希键较短时作为底层实现,可以大大节省内存资源。每一个压缩链表由头部,节点,尾部三部分组成。其中头部由压缩链表字节数(zlBytes)、压缩链表尾部地址(zlTail)、元素数量(zlLen)组成。尾部是固定的数字0xff即标识EOF。

而在头部和尾部中间便是实际存储的元素节点。压缩链表的节点由previous Entry Length(前一节点长度,单位byte)、contents(元素值)、encoding(元素编码组成)。通过前一元素的长度q以及当前元素的起始地址 p 可以很容易前一元素的地址即p-q。因此能够容易的实现从表尾向表头遍历。encoding保存了contents的数据类型及长度。encoding的最高两位如果11且encoding长度为1byte代表记录的是整数值,其他情况保存的是字节数组(字节数组低位标识数组最大长度)。

连锁更新:由于节点的previousEntryLength记录了前一节点的长度,当前一节点长度发生变化时很有可能产生连锁反应,即连锁更新。比如当前元素长度本来是254字节,其中previousEntryLength是1字节。但如果前一元素的长度超过了255,则当前元素的previousEntryLength变成了5,当前元素的字节长度变成了258,这样当前元素的下一个元素也要变化,这样就发生了连锁更新。连锁更新会影响性能,但一定范围内是性能损耗是比较小的。

Redis基础数据结构-基于2.8的更多相关文章

  1. redis 基础数据结构实现

    参考文献 redis数据结构分析 Skip List(跳跃表)原理详解 redis 源码分析之内存布局 Redis 基础数据结构与对象 Redis设计与实现-第7章-压缩列表 在redis中构建了自己 ...

  2. redis基础数据结构及编码方式

    redis基础数据结构和编码方式 一.基础数据结构 1)简单动态字符串 2)双端链表 3)字典 4)跳跃表 5)整数集合 6)压缩列表 二.对象类型与编码 在redis的数据库中创建一个新的键值对时, ...

  3. 1.基础: 万丈高楼平地起——Redis基础数据结构 学习记录

    <Redis深度历险:核心原理和应用实践>1.基础: 万丈高楼平地起——Redis基础数据结构 学习记录http://naotu.baidu.com/file/b874e2624d3f37 ...

  4. Redis——基础数据结构

    Redis提供了5种基础数据结构,分别是String,list,set,hash和zset. 1.String Redis所有的键都是String.Redis的String是动态字符串,内部结构类似J ...

  5. Redis基础数据结构

    Redis数据库中每个键值对都是由对象( c 的结构体对象)组成的. 数据库键总是一个字符串对象(string object) 数据库键的值可以使字符串对象.列表对象(list object).哈希对 ...

  6. Redis 基础数据结构之二 list(列表)

    Redis 有 5 种基础数据结构,分别为:string (字符串).list (列表).set (集合).hash (哈希) 和 zset (有序集合). 今天来说一下list(列表)这种数据结构, ...

  7. Redis 基础数据结构之一:string(字符串)

    Redis 有 5 种基础数据结构,分别为:string (字符串).list (列表).set (集合).hash (哈希) 和 zset (有序集合),Redis存储数据的结构是键值对形式的. 首 ...

  8. 浅析Redis基础数据结构

    Redis是一种内存数据库,所以可以很方便的直接基于内存中的数据结构,对外提供众多的接口,而这些接口实际上就是对不同的数据结构进行操作的算法,首先redis本身是一种key-value的数据库,对于v ...

  9. Redis 基础数据结构与对象

    Redis用到的底层数据结构有:简单动态字符串.双端链表.字典.压缩列表.整数集合.跳跃表等,Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包 ...

随机推荐

  1. mybatis面试题总结

    1.什么是MyBatis? 答:MyBatis是一个可以自定义SQL.存储过程和高级映射的持久层框架. 2.讲下MyBatis的缓存 答:MyBatis的缓存分为一级缓存和二级缓存,一级缓存放在ses ...

  2. Linux centos 安装 mysql 5.6

    一.mysql下载 1.方式一(简单粗暴) 直接在linux 目录下wget https://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.43-li ...

  3. tensorflow models flags 初步使用

    参考官方仓库:https://github.com/tensorflow/models/tree/master/official/utils/flags 测试Demo代码如下: from absl i ...

  4. MyBatis学习总结(三)——MyBatis配置文件配置的优化

    一.连接数据库的配置单独放在一个properties文件中 上文 连接数据库的配置写在 mybatisConf.xml中,本文直接放在 db.properties 中, 在mybatisConf.xm ...

  5. WEB安全性测试之文件上传漏洞

    1.漏洞描述:文件上传漏洞,是指可以利用WEB上传一些特定的文件包含特定代码如(<?php phpnfo;?> 可以用于读取服务器配置信息.上传成功后可以点击) 上传漏洞是指用户上传了一个 ...

  6. openswan协商流程之(四):main_inI2_outR2()

    主模式第四包:main_inI2_outR2 1. 序言 main_inI2_outR2()函数是ISAKMP协商过程中第四包的核心处理函数的入口,同时在此处理流程中已经获取到足够的隧道信息,可以生成 ...

  7. 内核软中断之tasklet机制

    1. 软中断IRQ简介 软中断(SoftIRQ)是内核提供的一种基于中断的延时机制, Linux内核定义的软中断有以下几种: enum { HI_SOFTIRQ=0, /*高优先级的tasklet*/ ...

  8. 编译执行 VS 解释执行

    一般编译程序从对源程序执行途径的角度不同,可分为解释执行和编译执行. 所谓解释执行是借助于解释程序完成,即按源程序语句运行时的动态结构,直接逐句地边分析边翻译并执行.像自然语言翻译中的口译,随时进行翻 ...

  9. undefined和null

    undefined和null undefined的情景: 声明变量为赋值 var name; console.og(name); //undefined 访问对象上不存在的属性 var obj={} ...

  10. 288 day05_异常,线程

    day05 [异常.线程] 主要内容 异常.线程 教学目标 [ ] 能够辨别程序中异常和错误的区别 [ ] 说出异常的分类 [ ] 说出虚拟机处理异常的方式 [ ] 列举出常见的三个运行期异常 [ ] ...