Redis技术交流群481804090

Redis:https://github.com/zwjlpeng/Redis_Deep_Read

本篇博文紧随上篇Redis有序集内部实现原理分析,在这篇博文里凡出现源码的地方均以下述src/version.h中定义的Redis版本为主

#define REDIS_VERSION "2.9.11"

在上篇博文Redis有序集内部实现原理分析中,我分析了Redis从什么时候开始支持有序集、跳表的原理、跳表的结构、跳表的查找/插入/删除的实现,理解了跳表的基本结构,理解Redis中有序集的实现就不难了,因为Redis有序集的实现也是以跳表作为基础的底层数据结构,选择这种数据结构,不仅仅是因为简单,更多的是因为性能。

Redis中跳表的基本数据结构定义如下,与基本跳表数据结构相比,在Redis中实现的跳表其特点是不仅有前向指针,也存在后向指针,而且在前向指针的结构中存在span跨度字段,这个跨度字段的出现有助于快速计算元素在整个集合中的排名

//定义跳表的基本数据节点
typedef struct zskiplistNode {
robj *obj; // zset value
double score;// zset score
struct zskiplistNode *backward;//后向指针
struct zskiplistLevel {//前向指针
struct zskiplistNode *forward;
unsigned int span;
} level[];
} zskiplistNode; typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist; //有序集数据结构
typedef struct zset {
dict *dict;//字典存放value,以value为key
zskiplist *zsl;
} zset;

将如上数据结构转化成更形式化的图形表示,如下图所示

在上图中,可以看到header指针指向的是一个具有固定层级(32层)的表头节点,为什么定义成,是因为定义成层理论上对于2^32-1个元素的查询最优,而2^32=4294967296个元素,对于绝大多数的应用来说,已经足够了,所以就定义成了层,到于为什么查询最优,你可以将其想像成一个层的完全二叉排序树,算算这个树中节点的数量

Redis中有序集另一个值得注意的地方就是当Score相同的时候,是如何存储的,当集合中两个值的Score相同,这时在跳表中存储会比较这两个值,对这两个值按字典排序存储在跳表结构中

有了上述的数据结构相关的基础知识,来看看Redis对zskiplist/zskiplistNode的相关操作,源码如下所示(源码均出自t_zset.c)

创建跳表结构的源码

//#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */
zskiplist *zslCreate(void) {
int j;
zskiplist *zsl;
//分配内存
zsl = zmalloc(sizeof(*zsl));
zsl->level = ;//默认层级为1
zsl->length = ;//跳表长度设置为0
zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,,NULL);
for (j = ; j < ZSKIPLIST_MAXLEVEL; j++) {
//因为没有任何元素,将表头节点的前向指针均设置为0
zsl->header->level[j].forward = NULL;
//将表头节点前向指针结构中的跨度字段均设为0
zsl->header->level[j].span = ;
}
//表头后向指针设置成0
zsl->header->backward = NULL;
//表尾节点设置成NULL
zsl->tail = NULL;
return zsl;
}

在上述代码中调用了zslCreateNode这个函数,函数的源码如下所示=

zskiplistNode *zslCreateNode(int level, double score, robj *obj) {
zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
zn->score = score;
zn->obj = obj;
return zn;
}

执行完上述代码之后会创建如下图所示的跳表结构

创建了跳表的基本结构,下面就是插入操作了,Redis中源码如下所示

zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; //update[32]
unsigned int rank[ZSKIPLIST_MAXLEVEL];//rank[32]
int i, level;
redisAssert(!isnan(score));
x = zsl->header;
//寻找元素插入的位置
for (i = zsl->level-; i >= ; i--) {
/* store rank that is crossed to reach the insert position */
rank[i] = i == (zsl->level-) ? : rank[i+];
while (x->level[i].forward &&
(x->level[i].forward->score < score || //以下是得分相同的情况下,比较value的字典排序
(x->level[i].forward->score == score &&compareStringObjects(x->level[i].forward->obj,obj) < ))) {
rank[i] += x->level[i].span;
x = x->level[i].forward;
}
update[i] = x;
}
//产生随机层数
level = zslRandomLevel();
if (level > zsl->level) {
for (i = zsl->level; i < level; i++) {
rank[i] = ;
update[i] = zsl->header;
update[i]->level[i].span = zsl->length;
}
//记录最大层数
zsl->level = level;
}
//产生跳表节点
x = zslCreateNode(level,score,obj);
for (i = ; i < level; i++) {
x->level[i].forward = update[i]->level[i].forward;
update[i]->level[i].forward = x;
//更新跨度
x->level[i].span = update[i]->level[i].span - (rank[] - rank[i]);
update[i]->level[i].span = (rank[] - rank[i]) + ;
}
//此种情况只会出现在随机出来的层数小于最大层数时
for (i = level; i < zsl->level; i++) {
update[i]->level[i].span++;
}
x->backward = (update[] == zsl->header) ? NULL : update[];
if (x->level[].forward)
x->level[].forward->backward = x;
else
zsl->tail = x;
zsl->length++;
return x;
}

上述源码中,有一个产生随机层数的函数,源代码如下所示:

int zslRandomLevel(void) {
int level = ;
//#define ZSKIPLIST_P 0.25
while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
level += ;
//#ZSKIPLIST_MAXLEVEL 32
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

图形化的形式描述如下图所示:

理解了插入操作,其他查询,删除,求范围操作基本上类似,此处忽略...

Redis有序集内部实现原理分析(二)的更多相关文章

  1. Redis有序集内部实现原理分析

    Redis技术交流群481804090 Redis:https://github.com/zwjlpeng/Redis_Deep_Read Redis中支持的数据结构比Memcached要多的多啦,如 ...

  2. Redis cluster的核心原理分析

    一.节点间的内部通信机制 1.基础通信原理 (1)redis cluster节点间采取gossip协议进行通信 跟集中式不同,不是将集群元数据(节点信息,故障,等等)集中存储在某个节点上,而是互相之间 ...

  3. Redis 发布/订阅机制原理分析

    Redis 通过 PUBLISH. SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能.   这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播.实时 ...

  4. (转载)Java NIO:NIO原理分析(二)

          NIO中的两个核心对象:缓冲区和通道,在谈到缓冲区时,我们说缓冲区对象本质上是一个数组,但它其实是一个特殊的数组,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况,如果我们使用 ...

  5. Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(二)__原理分析

    前置文章: <Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(一)__使用具体解释> 转载请务必注明出处:http://b ...

  6. 【Redis】跳跃表原理分析与基本代码实现(java)

    最近开始看Redis设计原理,碰到一个从未遇见的数据结构:跳跃表(skiplist).于是花时间学习了跳表的原理,并用java对其实现. 主要参考以下两本书: <Redis设计与实现>跳表 ...

  7. redis 发布与订阅原理分析

    前言:用了redis也有一段时间了,但是发布与订阅的使用频率也不高,趁着这次空闲,深究下redis的发布与订阅模式. 一.订阅频道和信息发布 功能说明:Redis 的 SUBSCRIBE 命令可以让客 ...

  8. ConcurrentHashMap原理分析(二)-扩容

    概述 在上一篇文章中介绍了ConcurrentHashMap的存储结构,以及put和get方法,那本篇文章就介绍一下其扩容原理.其实说到扩容,无非就是新建一个数组,然后把旧的数组中的数据拷贝到新的数组 ...

  9. Redlock(redis分布式锁)原理分析

    Redlock:全名叫做 Redis Distributed Lock;即使用redis实现的分布式锁: 使用场景:多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击) ...

随机推荐

  1. 针对Windows 64位系统中Matlab没有LED Control Activex控件的解决方法

    Win 10 64bits系统中Matlab 64位软件没有LED Control Activex控件,LED ActiveX Control控件位于Gauges Blockset模块中,而Gauge ...

  2. mfc 带参数的构造函数

    知识点 默认的构造函数 带参数的构造函数 重载构造函数 一.默认的构造函数 二.带参数的构造函数 三.重载构造函数 class Tdate { public: int year;//年 int mon ...

  3. 【转载】COM小结

    原文:http://blog.csdn.net/byxdaz/article/details/6595210 一.Com概念 所谓COM(Componet Object Model,组件对象模型),是 ...

  4. 【转载】完成C++不能做到的事 - Visitor模式

    原文: 完成C++不能做到的事 - Visitor模式 拿着刚磨好的热咖啡,我坐在了显示器前.“美好的一天又开始了”,我想. 昨晚做完了一个非常困难的任务并送给美国同事Review,因此今天只需要根据 ...

  5. Mac下 通过 VMware Fusion 配置 windows 虚拟机的详细步骤

    内容中包含 base64string 图片造成字符过多,拒绝显示

  6. python之shutil模块详解

    shutil模块 -- --High-level file operations  高级的文件操作模块. os模块提供了对目录或者文件的新建/删除/查看文件属性,还提供了对文件以及目录的路径操作.比如 ...

  7. Lookup 转换组件

    查找转换(Lookup)组件用于实现两个数据源的连接,实现的方式是嵌套循环.查找转换通常在内存中缓存查找数据集,然后在输入管道中,把输入数据的每一行都和缓存中的查找数据集进行比较,并输出匹配成功和失败 ...

  8. css绘制常见的几何图形

    前言:终于我的大一生活结束了,迎来了愉快的暑假,大家都开始了各自的忙碌.一直忙着一些项目的事情,终于决定今天要更新一篇博客了,对上一阶段的学习做简单的总结. 这次我主要总结一下用Css绘制各种形状的技 ...

  9. Linux中的mysql指令

    如何启动/停止/重启MySQL一.启动方式1.使用 service 启动:service mysqld start2.使用 mysqld 脚本启动:/etc/inint.d/mysqld start3 ...

  10. MySQL基础架构之查询语句执行流程

    这篇笔记主要记录mysql的基础架构,一条查询语句是如何执行的. 比如,在我们从student表中查询一个id=2的信息 select * from student where id=2; 在解释这条 ...