Redis Source Code Read Log( 15 zset & pubsub & pipe))

Part 1 zset

Redis zset 是一个 sorted set 有序集合,其中的每一个元素都会关联一个 double 类型的 score,形成一个key-value pair。既然是集合,那个 key 是唯一的,但是score是可以重复的,但是score的类型必须是double,而key值的类型,必须是string

命令

ZADD zset_name  score member [score2 member2 ...]

这个与一般 k-v pair 存储结构的命令相比,略有不同,其中是 value 前而 key 值在后。如果member存在,该命令则会更新该memberscore

ZREM zset_name member [member2 ...]

删除元素

zset 作为一个集合,其亦支持集合的相关操作,如差交并等。

其他的一些命令,略。

zset 存储结构

ziplist ,前文在 set 中已经介绍了该种数据结构,此处略。

说明:

zset中的ziplist是有序的,按照元素 score 升序排列,对于插入操作,是会遍历的,复杂度是O(N)

其他按照分数的相关操作,也并没有进行比如二分查找的处理,因为 ziplist 从逻辑上而言,是一个 linked list,难以二分查找,只能顺序查找,所以复杂度也都是O(N)级别(算法操作上,可以采用双游标往中间游,可以一定程度上的加快速度,仍旧是O(N)级别,zset中并没有这么干)

分数相等的情况下,采用的是元素的key值按照字典序进行排列。

但是,如果是按照元素 key 值的相关操作,那就直接是O(N)级别,比如 ZREM 某个 member

修改元素的 scorezset的内部实现,可想而知,为了保持 score 有序,那必须进行 delete + re-insert的操作。这样的操作的复杂的实际也是O(N)级别的。

SKIPLIST

zset 内部,对于一些比较简单的zset数据,一般默认采用 ziplist作为底层存储结构,但是对于复杂的,比如key值过长,数量过多,(参见redis配置),便会发送convert,zset内部的编码格式由ziplist转变为skiplist。如下图所示(图片截自Redisbook.com):

首先:左下角就是 zset的skiplist的数据结构:

struct zskiplistLevel
{
	struct zskiplistNode *forward;
	unsigned long span;
};

typedef struct zskiplistNode
{
	sds ele;
	double score;
	struct zskiplistNode *backward;
	struct zskiplistLevel level[];
} zskiplistNode;

typedef struct zskiplist
{
	struct zskiplistNode *header, *tail;
	unsigned long length;
	int level;
} zskiplist;

level 表示所有节点中,level最高的那个节点的level值。

header: 其实是一个空节点,主要内容是level数组中各个节点指针。如上图所示的level为5的skiplist,header中的L5总是指向第一个level为5的那个元素,从最底层level 1层来看,skiplist其实就是一个单链表,redis中加入了backward指针,就变成了双链表。

各个节点:1. 分数升序,2. 分数相等的,采用ele字典序进行排列。

每个节点的 span 表示的是,跳跃的间隔节点个数。当前这个节点的level数组的该level元素,到forward中间间隔了几个元素,上图中曲线箭头上的数字就是span。

每一个节点,并没有一个单独的成员就记录level值,所以 skiplistLevel 数组,都是最大值的方式申请来的(默认64层,64个元素的数组)。遍历都是从 zskiplist 中的level开始由最高往最低遍历,所以,有些节点层数不高的时候,高层次的位置,指向的forward就是NULL。

另外,每一个节点的level都是随机来的,假设一个插入场景:分数1.5,如果是生成的level是4

那么这个 1.5 节点的 L4 会直接指向3.0的 L4,因为2.0节点的level只有L2,也可说,新增1.5节点的L4从前一个节点继承过来。L4类似,L2,L1同理。然后,前面1.0的节点的所有层的forward都指向了新增的1.5节点。

如果1.5节点层数是3层,那么前面一个1.0节点的L4中的forward的不会更新。

如果1.5节点层数是5层,那么前四层与前面类似,但是L5层,就会继承到1.0前面的有L5层的节点,就是header节点,所以新增的1.5的L5层就会直接指向3.0节点。原先header中的L5层的foward就会断开,指向新增的1.5节点的L5.

Part 2 pubsub & PIPE

Redis的发布订阅其实非常简单,如果熟悉 linux 中的socket tcp + nonblock + epoll,就可以了。

订阅者,需要注意的是,必须保持TCP的连接,redis中记录这个连接(封装为client然后添加CLIENT_PUBSUB flag)。在处理publish 命令的时候,就会遍历检查对应的client,然后逐一去回复。这个内部逻辑与 BLPOP之类的命令比较类似。无非就是缓存连接,有数据可写的时候,检查socket的可写状态,不可写,就注册写事件,挂callback,可写,就直接回复了。完全的nonblock+异步I/O。

PIPE也是类似的,redis的客户端命令主要分为两种类型,一种就是内部称之为multibuk的命令,一个称之为 INLINE的命令,multibulk的命令,用的其实就是epoll 的回射模型。而PIPE命令,如果是自实现客户端,推荐采用异步操作,采用epoll监听,一堆命令发送过去之后,有部分数据,那就回复部分数据,客户端这边进入可读状态,客户端读取我们需要的结果即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值