Redis源码学习(6),t

Redis源码学习(6),t,第1张

前言

  大体学习完t_string.c的代码,我们正式进入下一个文件的学习,这一次我们学习的是t_list.c文件,从文件名我们可以知道这是一个关于列表的相关命令的源代码。
  在学习列表命令之前,这里要介绍一下,在Redis中列表的底层数据结构由压缩表和链表实现,所以在今天的学习中会发现大量的底层数据结构判断,根据不同的数据结构使用不同的方法。不过这里要注意的是,这里是基于3.0版本的源代码,3.2版本之后发生了变化,3.2版本之后使用了快速表来实现。

1 pushGenericCommand 1.1 方法说明

  这是一个lpush、rpush命令的基础方法,两个命令都会调用这个方法。

1.2 方法源代码
void pushGenericCommand(redisClient *c, int where) {
    int j, waiting = 0, pushed = 0;
	
	//获取列表对象
    robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
	
	//如果对象的类型不为列表,报类型错误
    if (lobj && lobj->type != REDIS_LIST) {
        addReply(c,shared.wrongtypeerr);
        return;
    }
	
	//遍历推入的元素
    for (j = 2; j < c->argc; j++) {
        c->argv[j] = tryObjectEncoding(c->argv[j]);
		
		//如果列表不存在,则创建一个压缩表对象
        if (!lobj) {
            lobj = createZiplistObject();
            dbAdd(c->db,c->argv[1],lobj);
        }
		//调用列表推入元素方法
        listTypePush(lobj,c->argv[j],where);
        pushed++;
    }

	//返回列表的元素总个数
    addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0));
    
    //如果推入了元素
    //标记键被修改,并发送通知事件
    if (pushed) {
    	//判断事件类型
        char *event = (where == REDIS_HEAD) ? "lpush" : "rpush";
		
		//标记键被修改	
        signalModifiedKey(c->db,c->argv[1]);

		//通知事件
        notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
    }
	
	//改变状态值增加
    server.dirty += pushed;
}
1.3 代码理解

  可以看到pushGenericCommand总共做了以下几件事情

  1. 获取键对象
  2. 判断键对象的类型是否为列表类型
  3. 遍历推入的元素,一个个的推入列表中
  4. 如果列表不存在,则创建一个压缩表对象,可以看到列表默认会使用压缩表作为底层数据结构。
  5. 调用listTypePush将元素推入列表中。
  6. 返回列表的总数量
  7. 如果推入了元素,则标记键已被修改,并且触发通知事件。
  8. 记录更改状态值。

  这里个人有一点不太明白的是,不知道为什么这里判断列表对象不存在的逻辑要写在循环里,而不是写在外面,可能作者有其他的考量。

2 listTypePush 2.1 方法说明

  这个方法可以说是实现push命令的核心方法了,根据列表的底层数据结构调用不同的方法来推入元素。

2.2 方法源代码
void listTypePush(robj *subject, robj *value, int where) {

    /* Check if we need to convert the ziplist */
	//检查该值是否会引起数据结构变化
    listTypeTryConversion(subject,value);

	//检查列表的长度是否超过等于压缩表最大值
	//如果是的话,需要转变数据结构为链表
    if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
        ziplistLen(subject->ptr) >= server.list_max_ziplist_entries)
            listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);

	//如果数据结构是压缩表
	//则调用压缩表相关方法推入元素
    if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
        int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
        value = getDecodedObject(value);
        subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos);
        decrRefCount(value);
    }
    
    //如果数据结构是链表 
    //则调用链表相关方法推入元素
    else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
        if (where == REDIS_HEAD) {
            listAddNodeHead(subject->ptr,value);
        } else {
            listAddNodeTail(subject->ptr,value);
        }
        incrRefCount(value);
    } else {
        redisPanic("Unknown list encoding");
    }
}
2.3 代码理解

  这个方法做了以下几件事情

  1. 判断当前值的类型,是否会引起底层数据结构变化。
  2. 判断当前压缩列表的元素个数是否超过了配置,如果超过了配置则要转变底层数据结构为链表。
  3. 如果当前数据结构为压缩表,则调用压缩表相关方法推入元素。
  4. 如果当前数据结构为链表,则调用链表相关方法推入元素。
3 lpushCommand 3.1 命令说明

  向列表的头部插入一个元素,成功返回列表元素的个数

3.2 命令实践

3.3 命令源代码
void lpushCommand(redisClient *c) {
    pushGenericCommand(c,REDIS_HEAD);
}
3.4 代码理解

  可以看到lpushCommand没有过多其他的代码,直接调用了pushGenericCommand这个方法,并传入了一个参数REDIS_HEAD表示是从头部推入元素,这个参数在redis.h文件里有定义。

#define REDIS_HEAD 0
#define REDIS_TAIL 1
4 rpushCommand 4.1 命令说明

  向列表的尾部插入一个元素,成功返回列表元素的个数

4.2 命令实践

4.3 命令源代码
void rpushCommand(redisClient *c) {
    pushGenericCommand(c,REDIS_TAIL);
}
4.4 代码理解

  可以看到rpushCommand也是直接调用了pushGenericCommand这个方法,并传入了一个参数REDIS_TAIL表示是从尾部推入元素。

5 总结

1、列表的底层数据结构由两种实现方式,一种是压缩表,另一种是链表
2、列表的底层数据结构默认使用压缩表,达到某种条件会触发转换动作。
3、触发数据结构转换的条件有:如果值类型不符合则会转换,如果元素的个数超过配置也会转换。
4、意味着每次推入一个元素都会检查是否要转换数据结构。
5、lpush和rpush命令都是调用了pushGenericCommand这个方法,只不过通过位置参数控制推入的位置。
6、推入元素会标记列表键被修改,并且触发通知事件。
7、server.dirty 增加的是推入元素的数量。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/717310.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存