该系列基于redis-2.8.18,主要记录自己的理解或者想法。redis以自己支持存储的数据结构丰富吸引了大批人,把memcached比了下去。本文就从简单基本的数据结构入手。

双向链表-adlist

typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode; typedef struct listIter {
listNode *next;
int direction;
} listIter; typedef struct list {
listNode *head;
listNode *tail;
void *(*dup)(void *ptr);//使用函数指针便于扩展,不同的业务指定自己的实现
void (*free)(void *ptr);
int (*match)(void *ptr, void *key);
unsigned long len;
} list;

压缩链表-ziplist

ziplist是双向链表的一种实现,采用一些编码技巧高效的使用一块连续的内存,每个节点可以保存字符串或者整数。ziplist常用做其他数据结构的一种底层实现方式。
内存结构如下图:
zlbytes:ziplist占用的字节数(不包括zlbytes自己,最小是7),unsigned int类型。可以算出一个ziplist最多2**32-1byte,大概4G。
zltail:从zltail到最后一个entry的距离(byte)-zlbytes结尾到last entry的开头,zltail最少是6,len(zltail)+len(zllen)。这样可以O(1)找到尾部,不用遍历整个list。
zllen:ziplist的节点数,大小是2byte,最多代表2**16-2=65534个(为啥是减2?因为减1是2byte表示的最大数,被用于表示超过zllen范围了,zllen无效了),超过的就得遍历list获得有效节点数。
zlend:各位全是1(255),表示ziplist的结尾。zltail可以找到last entry,那不用zlend行不行?那在遍历链表时需要记录遍历的个数,等于zltail时才能停止。
entry:一个具体的节点,其内部结构如下。
pre-entry-len:上一个节点的长度(byte),便于回退。
encoding:表示该节点存储的数据类型,string或整型。
entry-len:该节点的value大小,entry-len占用的bit数与存储类型有关,如下。
字符串类型
encoding=00,strlen<=63,entry-len占6bit
encoding=01, strlen <= 16383,entry-len占14bit
encoding=10, strlen>=16384, entry-len占38bit
整型类型
encoding+entry-len共占1byte,encoding=11,后面6bit用于表示整数类型(int16_t/int32_t/int64_t等)。
11 000000代表int16_t,
11 010000代表int32_t,
11 100000代表int64_t,
11 110000代表int24
11 111110代表int8
11 11xxxx:0001 <= xxxx <= 1101(从上面看0000/11110不能用),xxxx就是存储的整数值,虽然实际存储的是1到13,解释成0到12。
补:
ziplist是一块连续的内存,所以每次新增都重新分配内存,删除会压缩内存(后面的往前移,void * memmove ( void * destination, const void * source, size_t num ))
值和bit的变换-需要用到void * memcpy ( void * destination, const void * source, size_t num ),整型还会用到强制类型转换和移位。

散列表-dict

散列表,使用拉链法解决hash冲突。比较有意思的是使用渐进式重hash的方法来扩大散列表大小。
何时resize?新增元素时,检查ht[0]中used/size > ratio(默认为5),如果true,会新生成ht[1],大小大于等于2*used,且为2的指数次方。used是保存的元素个数,size是散列表的桶个数。开始渐进式rehash,将ht[0]中的元素逐渐rehash到ht[1]中。
何为渐进式?每当新增、删除、查找元素时,如果散列表处于rehash(rehashidx!=-1)时,就rehash一个桶(有相同hash值的链表)到ht[1],直到rehash结束。
过程中的注意点?rehash中新增元素都放到ht[1]中,查找要遍历2个ht,以ht[1]中的为准。如果有safe iterator就要暂停rehash,以保证基于iterator的操作安全。
优缺点?resize过程中不中断散列表的使用,缺点是新旧列表并存,resize过程中长期占用多余的空间。
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht; typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];//ht[0]为默认使用,resize时将ht[0]中的元素逐一rehash到ht[1],最后ht[1]复制给后台[0]
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
int iterators; /* number of iterators currently running,记录safe iterator个数,如果非0,就暂停rehash*/
} dict;
/* If safe is set to 1 this is a safe iterator, that means, you can call
* dictAdd, dictFind, and other functions against the dictionary even while
* iterating. Otherwise it is a non safe iterator, and only dictNext()
* should be called while iterating. */
typedef struct dictIterator {
dict *d;
long index;
int table, safe;//0非安全,1安全
dictEntry *entry, *nextEntry;
/* unsafe iterator fingerprint for misuse detection. */
long long fingerprint;//是散列表各属性的异或
} dictIterator;
//dictType,散列表实现操作,如key比较/hash函数
typedef struct dictType {
unsigned int (*hashFunction)(const void *key);//hash function
void *(*keyDup)(void *privdata, const void *key);//duplicate key
void *(*valDup)(void *privdata, const void *obj);//duplicate value
int (*keyCompare)(void *privdata, const void *key1, const void *key2);//key compare
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
} dictType;

函数指针挺好的,与面向对象编程中的继承有异曲同工的效果,实现了多态。下图是散列表结构:

redis源码系列-数据结构(adlist/ziplist/dict)的更多相关文章

  1. Redis 源码简洁剖析 05 - ziplist 压缩列表

    ziplist 是什么 Redis 哪些数据结构使用了 ziplist? ziplist 特点 优点 缺点 ziplist 数据结构 ziplist 节点 pre_entry_length encod ...

  2. Redis源码分析(adlist)

    源码版本:redis-4.0.1 源码位置: adlist.h : listNode.list数据结构定义. adlist.c:函数功能实现. 一.adlist简介 Redis中的链表叫adlist( ...

  3. redis源码之压缩列表ziplist

    压缩列表ziplist1.简介连续,无序的数据结构.压缩列表是 Redis 为了节约内存而开发的, 由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构. 2.组成 属性 类型 长 ...

  4. Redis 源码简洁剖析 07 - main 函数启动

    前言 问题 阶段 1:基本初始化 阶段 2:检查哨兵模式,执行 RDB 或 AOF 检测 阶段 3:运行参数解析 阶段 4:初始化 server 资源管理 初始化数据库 创建事件驱动框架 阶段 5:执 ...

  5. 曹工说Redis源码(2)-- redis server 启动过程解析及简单c语言基础知识补充

    文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...

  6. 曹工说Redis源码(3)-- redis server 启动过程完整解析(中)

    文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...

  7. Redis 源码简洁剖析 06 - quicklist 和 listpack

    quicklist 为什么要设计 quicklist 特点 数据结构 quicklistCreate quicklistDelIndex quicklistDelEntry quicklistInse ...

  8. 曹工说Redis源码(8)--面试时,redis 内存淘汰总被问,但是总答不好

    文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...

  9. 曹工说Redis源码(4)-- 通过redis server源码来理解 listen 函数中的 backlog 参数

    文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis,而怎么才能懂,光看是不够的,建议跟着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者跟着我这边一起阅读.由于 ...

随机推荐

  1. 结构化查询语言(SQL)数据类型

    简要描述一下结构化查询语言中的五种数据类型:字符型,文本型,数值型,逻辑型和日期型. 字符型 VARCHARVS CHAR VARCHAR型和CHAR型数据的这个差别是细微的,但是非常重要.他们都是用 ...

  2. C# 动态加载程序集dll (实现接口)

    一.程序集(接口程序集):LyhInterface.Dll namespace LyhInterface { public interface ILyhInterface { void Run(); ...

  3. ubuntu安装goldendict以及添加本地词典和在线词典

    软件在ubuntu软件中心安装就可以了, 关于网络词典, 推荐有道在线词典,网址http://dict.youdao.com/search?q=%GDWORD%&ue=utf8 添加方法:编辑 ...

  4. 在虚拟机上安装Linux6.5

    下定决心开始学习Linux了,这个博客将记录我的成长点滴,方便日后温故而知新!并希望有小伙伴能给出意见和建议! 我用的是VMware Workstation 10,当然是破解版,毕竟只是自己做练习使用 ...

  5. bootstrap中table的colspan不起作用

    bootstrap中table的colspan不起作用,即在不指定宽度的条件下,各个td宽度不符合colspan指定的宽度. 添加table0layout:fixed后显示正常. table{ tab ...

  6. 可维护的javascript

    理论上我只能把序看完....... 第一章:基本的格式化 1.1:JSLint,JSHint查找代码中潜在的错误. 1.2:缩进:空格(2,4,8没有兼容性)和tab(不同的编辑器展现不一样),在编辑 ...

  7. Spring 学习笔记 2. 尚硅谷_佟刚_Spring_IOC&DI概述

    1,远古时代 这里讲述的IOC的演变历史,举一个例子,假如需要生成HTML和PDF格式的报表,以前的开发方式就是有个报表服务类需要使用报表生成器 它需要和其他三个都关联,它既需要知道接口类型,也需要知 ...

  8. HTML与CSS基础知识补遗(一)

    开始从零基础系统地学习前端知识了,虽说html和css多少了解一些,但是学着还是能发现很多新大陆.... 一. HTML中head标签 1. <meta>标签: meta标签里是一些基础的 ...

  9. c++中的指针之指针在数组

    使用一维指针数组输出一维数组中的数 int array[]={1,2,3,4,5,6};        int *p; p=array;        for(int i=0;i<6;i++){ ...

  10. hdu 5104 素数打表水题

    http://acm.hdu.edu.cn/showproblem.php?pid=5104 找元组数量,满足p1<=p2<=p3且p1+p2+p3=n且都是素数 不用素数打表都能过,数据 ...