Redis技术交流群481804090

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

Redis中支持的数据结构比Memcached要多的多啦,如基本的字符串、哈希表、列表、集合、可排序集,在这些基本数据结构上也提供了针对该数据结构的各种操作,这也是Redis之所以流行起来的一个重要原因,当然Redis能够流行起来的原因,远远不只这一个,如支持高并发的读写、数据的持久化、高效的内存管理及淘汰机制...

从Redis的git提交历史中,可以查到,2009/10/24在1.050版本,Redis开始支持可排序集,在该版本中,只提供了一条命令zadd,宏定义如下所示:

     {"zadd",zaddCommand,,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},

那么什么是可排序集呢? 从Redis 1.0开始就给我们提供了集合(Set)这种数据结构,集合就跟数学上的集合概念是一个道理【无序性,确定性,互异性】,集合里的元素无法保证元素的顺序,而业务上的需求,可能不止是一个集合,而且还要求能够快速地对集合元素进行排序,于是乎,Redis中提供了可排序集这么一种数据结构,似乎也是合情合理,无非就是在集合的基础上增加了排序功能,也许有人会问,Redis中不是有Sort命令嘛,下面的操作不也是同样可以达到对无序集的排序功能嘛,是的,是可以,但是在这里我们一直强调的是快速这两个字,而Sort命令的时间复杂度为O(N+M*Log(M)),可排序集获取一定范围内元素的时间复杂度为O(log(N) + M)

root@bjpengpeng-VirtualBox:/home/bjpengpeng/redis-3.0./src# ./redis-cli
127.0.0.1:> sort set
) ""
) ""
) ""
) ""
127.0.0.1:> sort set desc
) ""
) ""
) ""
) ""
127.0.0.1:>

在了解可排序集是如何实现之前,需要了解一种数据结构跳表(Skip List),跳表与AVL、红黑树...等相比,数据结构简单,算法易懂,但查询的时间复杂度与平衡二叉树/红黑树相当,跳表的基本结构如下图所示

上图中整个跳表结构存放了4个元素5->10->20->30,图中的红色线表示查找元素30时,走的查找路线,从Head指针数组里最顶层的指针所指的20开始比较,与普通的链表查找相比,跳表的查询可以跳跃元素,上图中查询30,发现30比20大,则查找就是20开始,而普通链表的查询必须一个元素一个元素的比较,时间复杂度为O(n)

有了上图所示的跳表基本结构,再看看如何向跳表中插入元素,向跳表中插入元素,由于元素所在层级的随机性,平均起来也是O(logn),说白了,就是查找元素应该插入在什么位置,然后就是普通的移动指针问题,再想想往有序单链表的插入操作吧,时间复杂度是不是也是O(n),下图所示是往跳表中插入元素28的过程,图中红色线表示查找插入位置的过程,绿色线表示进行指针的移动,将该元素插入

有了跳表的查找及插入那么就看看在跳表中如何删除元素吧,跳表中删除元素的个程,查找要删除的元素,找到后,进行指针的移动,过程如下图所示,删除元素30

有了上面的跳表基本结构图及原理,自已设计及实现跳表吧,这样当看到Redis里面的跳表结构时我们会更加熟悉,更容易理解些,【下面是对Redis中的跳表数据结构及相关代码进行精减后形成的可运行代码】,首先定义跳表的基本数据结构如下所示

#include<stdio.h>
#include<stdlib.h> #define ZSKIPLIST_MAXLEVEL 32
#define ZSKIPLIST_P 0.25
#include <math.h> //跳表节点
typedef struct zskiplistNode {
int key;
int value;
struct zskiplistLevel {
struct zskiplistNode *forward;
} level[1];
} zskiplistNode; //跳表
typedef struct zskiplist {
struct zskiplistNode *header;
int level;
} zskiplist;

在代码中我们定义了跳表结构中保存的数据为Key->Value这种形式的键值对,注意的是skiplistNode里面内含了一个结构体,代表的是层级,并且定义了跳表的最大层级为32级,下面的代码是创建空跳表,以及层级的获取方式

//创建跳表的节点
zskiplistNode *zslCreateNode(int level, int key, int value) {
zskiplistNode *zn = (zskiplistNode *)malloc(sizeof(*zn)+level*sizeof(zn->level));
zn->key = key;
zn->value = value;
return zn;
} //初始化跳表
zskiplist *zslCreate(void) {
int j;
zskiplist *zsl;
zsl = (zskiplist *) malloc(sizeof(*zsl));
zsl->level = 1;//将层级设置为1
zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,NULL,NULL);
for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
zsl->header->level[j].forward = NULL;
}
return zsl;
} //向跳表中插入元素时,随机一个层级,表示插入在哪一层
int zslRandomLevel(void) {
int level = 1;
while ((rand()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
level += 1;
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

在这段代码中,使用了随机函数获取过元素所在的层级,下面就是重点,向跳表中插入元素,插入元素之前先查找插入的位置,代码如下所示,代码中注意update[i]

//向跳表中插入元素
zskiplistNode *zslInsert(zskiplist *zsl, int key, int value) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
int i, level;
x = zsl->header;
//在跳表中寻找合适的位置并插入元素
for (i = zsl->level-1; i >= 0; i--) {
while (x->level[i].forward &&
(x->level[i].forward->key < key ||
(x->level[i].forward->key == key &&
x->level[i].forward->value < value))) {
x = x->level[i].forward;
}
update[i] = x;
}
//获取元素所在的随机层数
level = zslRandomLevel();
if (level > zsl->level) {
for (i = zsl->level; i < level; i++) {
update[i] = zsl->header;
}
zsl->level = level;
}
//为新创建的元素创建数据节点
x = zslCreateNode(level,key,value);
for (i = 0; i < level; i++) {
x->level[i].forward = update[i]->level[i].forward;
update[i]->level[i].forward = x;
}
return x;
}

下面是代码中删除节点的操作,和插入节点类似

//跳表中删除节点的操作
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
int i;
for (i = 0; i < zsl->level; i++) {
if (update[i]->level[i].forward == x) {
update[i]->level[i].forward = x->level[i].forward;
}
}
//如果层数变了,相应的将层数进行减1操作
while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
zsl->level--;
} //从跳表中删除元素
int zslDelete(zskiplist *zsl, int key, int value) {
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
int i;
x = zsl->header;
//寻找待删除元素
for (i = zsl->level-1; i >= 0; i--) {
while (x->level[i].forward &&
(x->level[i].forward->key < key ||
(x->level[i].forward->key == key &&
x->level[i].forward->value < value))) {
x = x->level[i].forward;
}
update[i] = x;
}
x = x->level[0].forward;
if (x && key == x->key && x->value == value) {
zslDeleteNode(zsl, x, update);
//别忘了释放节点所占用的存储空间
free(x);
return 1;
} else {
//未找到相应的元素
return 0;
}
return 0;
}

最后,附上一个不优雅的测试样例

//将链表中的元素打印出来
void printZslList(zskiplist *zsl) {
zskiplistNode *x;
x = zsl->header;
for (int i = zsl->level-1; i >= 0; i--) {
zskiplistNode *p = x->level[i].forward;
while (p) {
printf(" %d|%d ",p->key,p->value);
p = p->level[i].forward;
}
printf("\n");
}
} int main() {
zskiplist *list = zslCreate();
zslInsert(list,1,2);
zslInsert(list,4,5);
zslInsert(list,2,2);
zslInsert(list,7,2);
zslInsert(list,7,3);
zslInsert(list,7,3);
printZslList(list);
//zslDelete(list,7,2);
printZslList(list);
}

有了上面的跳表理论基础,理解Redis中跳表的实现就不是那么难了,先分析到这,下回续写,【代码以Redis 2.9为例】~

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

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

    Redis技术交流群481804090 Redis:https://github.com/zwjlpeng/Redis_Deep_Read 本篇博文紧随上篇Redis有序集内部实现原理分析,在这篇博文 ...

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

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

  3. Redis cluster的核心原理分析

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

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

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

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

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

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

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

  7. 【Redis】内部数据结构自顶向下梳理

    本博客将顺着自顶向下的思路梳理一下Redis的数据结构体系,从数据库到对象体系,再到底层数据结构.我将基于我的一个项目的代码来进行介绍:daredis.该项目中,使用Java实现了Redis中所有的数 ...

  8. 利用多写Redis实现分布式锁原理与实现分析(转)

    利用多写Redis实现分布式锁原理与实现分析   一.关于分布式锁 关于分布式锁,可能绝大部分人都会或多或少涉及到. 我举二个例子:场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能 ...

  9. redis原理分析

    基本全是参考http://blog.csdn.net/a600423444/article/details/8944601     redis的使用大家都很熟悉,可能除了watch 锁,pipelin ...

随机推荐

  1. 由Handle转换为控件

    Control c = Control.FromHandle(this.textBox1.Handle); TextBox f = c as TextBox;

  2. jQuery学习-页面就绪函数

    1.开发工具HBuilder <!DOCTYPE html> <html> <head> <meta charset="utf-8" /& ...

  3. 设置pdsh的默认登录模式

    1.check your pdsh default rcmd rsh pdsh -q -w localhostSee what your pdsh default rcmd is. 2.Modify ...

  4. 网易公开课[一万分钟]《office办公达人养成计划》

    note: Shift表示移动 Ctrl表示复制 Excel: 快速选中单元格: 选中表格左上角Ctrl+A Ctrl+方向键,跳到行或列的尽头 Ctrl+Shift+方向键,选中一行或一列 Shif ...

  5. vue-scroller实现vue单页面的上拉加载和下拉刷新问题

    在vue中如何简单的实现页面的上拉加载和下拉刷新,在这里我推荐使用vue-scrolle插件. vue-scrolle的基本使用方法: 1.下载 npm i vue-scroller -D 2.导包 ...

  6. shell中与运算 cut切分行 if while综合在一起的一个例子

    前言: 公司要统计 treasury库hive表磁盘空间,写了个脚本,如下: 查询hive仓库表占用hdfs文件大小: hadoop fs -du -h  /user/hive/warehouse/t ...

  7. 戴尔win10重新安装win7系统

    戴尔v5468电脑win10重装回win7系统 首先是公司需要用到ie8来执行公司的项目维护,都是很早之前的项目了,因为是对接政府相关的业务,不怎么有把握对项目进行稳定更新,所以我就为这个ie8操碎了 ...

  8. Linux Ubuntu 安装、汉化、常用操作

    一.安装Ubuntu Desktop 安装前准备 现在的PC机都可以满足要求. 软件准备 vmware:http://www.vmware.com/cn ubuntu:18ubuntu Desktop ...

  9. Linux文件句柄数调整

    首先介绍下Linux系统中"一切都是文件". 1. Linux系统文件句柄数概念 文件句柄(Windows) 文件描述符(Unix/Linux):file discriptor,f ...

  10. Tomcat分析

    最近闲来无事,总结了一下tomcat的一些知识,分享出来供大家参考,如有错误,请及时与我联系. 1. 入门示例:虚拟主机提供web服务 该示例通过设置虚拟主机来提供web服务,因为是入门示例,所以设置 ...