iis服务器助手广告广告
返回顶部
首页 > 资讯 > 数据库 >Redis之quicklist源码分析
  • 670
分享到

Redis之quicklist源码分析

Redis之quicklist源码分析 2019-01-23 04:01:34 670人浏览 绘本
摘要

一、quicklist简介 Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。 一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个

Redis之quicklist源码分析

一、quicklist简介

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

其底层实现所依赖的内部数据结构就是quicklist,主要特点有:

list是一个双向链表

在list的两端追加和删除数据极为方便,时间复杂度为O(1)。

list也支持在任意中间位置的存取操作,时间复杂度为O(N)。

 

在看源码之前(版本3.2.2),我们先看一下quicklist中的几个主要数据结构:

一个quicklist由多个quicklistnode组成,每个quicklistNode指向一个ziplist,一个ziplist包含多个entry元素,每个entry元素就是一个list的元素,示意图如下:

 

 

                        图1:quicklist

 二、quicklist数据结构源码

下面分别看下quicklist、quicklistNode的源码(代码文件是Quicklist.h,ziplist后面文章再分析):

quicklist:


typedef struct quicklist {
    quicklistNode *head;
    quicklistNode *tail;
    unsigned long count;        
    unsigned int len;           
    int fill : 16;              
    unsigned int compress : 16; 
} quicklist;

quicklistNode:


typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *zl;
    unsigned int sz;             
    unsigned int count : 16;     
    unsigned int encoding : 2;   
    unsigned int container : 2;  
    unsigned int recompress : 1; 
    unsigned int attempted_compress : 1; 
    unsigned int extra : 10; 
} quicklistNode;

 

 

三、quicklist的增删改查

1. 创建quicklist

在执行push命令时(例如lpush),发现无此key时,会创建并初始化quicklist,如下:

 

void pushGenericCommand(client *c, int where) {
    int j, waiting = 0, pushed = 0;
    robj *lobj = lookupKeyWrite(c->db,c->argv[1]);

    if (lobj && lobj->type != OBJ_LIST) {
        addReply(c,shared.wrongtypeerr);
        return;
    }

    for (j = 2; j < c->arGC; j++) {
        c->argv[j] = tryObjectEncoding(c->argv[j]);
        if (!lobj) {  // key不存在,则首先创建key对象并加入db中
            lobj = createQuicklistObject(); // 初始化quicklist对象
            quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
                                server.list_compress_depth); // 使用redis server的配置项做初始化
            dbAdd(c->db,c->argv[1],lobj); // 把quicklist添加到redis db中
        }
        // 把新元素加入list中
        listTypePush(lobj,c->argv[j],where);
        pushed++;
    }
    addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0));
    if (pushed) {
        char *event = (where == LIST_HEAD) ? "lpush" : "rpush";

        signalModifiedKey(c->db,c->argv[1]);
        notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
    }
    server.dirty += pushed;
}

 

再看下createQuicklistObject:


quicklist *quicklistCreate(void) {
    struct quicklist *quicklist;

    quicklist = zmalloc(sizeof(*quicklist));
    quicklist->head = quicklist->tail = NULL;
    quicklist->len = 0;
    quicklist->count = 0;
    quicklist->compress = 0;
    quicklist->fill = -2;
    return quicklist;
}

 

2. 添加元素

继续看上面源码中的listTypePush方法:

void listTypePush(robj *subject, robj *value, int where) {
    if (subject->encoding == OBJ_ENCODING_QUICKLIST) {
        int pos = (where == LIST_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL;
        value = getDecodedObject(value);
        size_t len = sdslen(value->ptr);// 计算新元素长度
        quicklistPush(subject->ptr, value->ptr, len, pos); // 加入到quicklist
        decrRefCount(value); 
    } else {
        serverPanic("Unknown list encoding");
    }
}

继续看quicklistPush:


void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
                   int where) {
    if (where == QUICKLIST_HEAD) {  // 添加到list头部
        quicklistPushHead(quicklist, value, sz);
    } else if (where == QUICKLIST_TAIL) {  // 添加到list尾部
        quicklistPushTail(quicklist, value, sz);
    }
}


int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
    quicklistNode *orig_head = quicklist->head;
    // 如果head不为空,且空间大小满足新元素的存储要求,则新元素添加到head中,否则新加一个quicklistNode
    if (likely(
            _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
        quicklist->head->zl =
            ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);
        quicklistNodeUpdateSz(quicklist->head);
    } else {
        // 创建新的quicklistNode
        quicklistNode *node = quicklistCreateNode();
        // 把新元素添加到新建的ziplist中
        node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
        // 更新ziplist的长度到quicklistNode的sz字段,再把新node添加到quicklist中,即添加到原head前面
        quicklistNodeUpdateSz(node);
        _quicklistInsertNodeBefore(quicklist, quicklist->head, node);
    }
    quicklist->count++;
    quicklist->head->count++;
    return (orig_head != quicklist->head);
}

ziplistpush会把新元素添加到ziplist中,这部分代码后面文章再分析。

3. 获取元素

获取元素方法是quicklistPop方法(quicklist.c),如下:


int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
                 unsigned int *sz, long long *slong) {
    unsigned char *vstr;
    unsigned int vlen;
    long long vlong;
    if (quicklist->count == 0)
        return 0;
    // pop一个元素
    int ret = quicklistPopCustom(quicklist, where, &vstr, &vlen, &vlong,
                                 _quicklistSaver);
    if (data)
        *data = vstr;
    if (slong)
        *slong = vlong;
    if (sz)
        *sz = vlen;
    return ret;
}

具体实现在quicklistPopCustom:


int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
                       unsigned int *sz, long long *sval,
                       void *(*saver)(unsigned char *data, unsigned int sz)) {
    unsigned char *p;
    unsigned char *vstr;
    unsigned int vlen;
    long long vlong;
    int pos = (where == QUICKLIST_HEAD) ? 0 : -1;

    if (quicklist->count == 0)
        return 0;

    if (data)
        *data = NULL;
    if (sz)
        *sz = 0;
    if (sval)
        *sval = -123456789;

    quicklistNode *node;
    if (where == QUICKLIST_HEAD && quicklist->head) {
        node = quicklist->head;
    } else if (where == QUICKLIST_TAIL && quicklist->tail) {
        node = quicklist->tail;
    } else {
        return 0;
    }
    // p: 0 取ziplist的第一个元素; -1 取ziplist的最后一个元素;
    p = ziplistIndex(node->zl, pos);
    if (ziplistGet(p, &vstr, &vlen, &vlong)) {
        if (vstr) {
            if (data)
                *data = saver(vstr, vlen);
            if (sz)
                *sz = vlen;
        } else {
            if (data)
                *data = NULL;
            if (sval)
                *sval = vlong;
        }
        // 从quicklist中删除该元素
        quicklistDelIndex(quicklist, node, &p);
        return 1;
    }
    return 0;
}

再看下quicklistDelIndex:


REDIS_STATIC int quicklistDelIndex(quicklist *quicklist, quicklistNode *node,
                                   unsigned char **p) {
    int Gone = 0;

    node->zl = ziplistDelete(node->zl, p);
    node->count--;
    if (node->count == 0) {
        gone = 1;
        __quicklistDelNode(quicklist, node);
    } else {
        quicklistNodeUpdateSz(node);
    }
    quicklist->count--;
    
    return gone ? 1 : 0;
}

 

至此,quicklist的主体结构代码,和主要的两个方法push和pop的代码就分析结束了,下一篇分析quicklist底层存储ziplist的源代码。

本篇内容参考了钱文品的《Redis深度历险:核心原理与应用实践》,特此感谢!

您可能感兴趣的文档:

--结束END--

本文标题: Redis之quicklist源码分析

本文链接: https://www.lsjlt.com/news/5583.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Redis源码分析之set 和 sorted set 使用
    目录set 和 sorted set前言set常见命令set 的使用场景看下源码实现sorted set常见的命令使用场景分析下源码实现总结参考set 和 sorted set 前言...
    99+
    2024-04-02
  • Redis事件处理源码分析
    今天小编给大家分享一下Redis事件处理源码分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下...
    99+
    2024-04-02
  • redis源码分析教程之压缩链表ziplist详解
    前言 压缩列表(ziplist)是由一系列特殊编码的内存块构成的列表,它对于Redis的数据存储优化有着非常重要的作用。这篇文章总结一下redis中使用非常多的一个数据结构压缩链表ziplist。该数据结构...
    99+
    2024-04-02
  • Golang源码分析之golang/sync之singleflight
    目录1.背景1.1. 项目介绍1.2.使用方法2.源码分析2.1.项目结构2.2.数据结构2.3.API代码流程3.总结1.背景 1.1. 项目介绍 golang/sync库拓展了官...
    99+
    2022-11-13
    golang/sync golang源码分析 golang singleflight
  • BlueStore源码分析之Stupid分配器
    前言前面介绍了BlueStore的BitMap分配器,我们知道新版本的Bitmap分配器的优势在于使用连续的内存空间从而尽可能更多的命中CPU Cache以提高分配器性能。在这里我们了解一下基于区间树的Stupid分配器(类似于Linux ...
    99+
    2023-06-05
  • Java源码分析:Guava之不可变集合ImmutableMap的源码分析
    目录一、案例场景二、ImmutableMap源码分析总结一、案例场景 遇到过这样的场景,在定义一个static修饰的Map时,使用了大量的put()方法赋值,就类似这样—— pu...
    99+
    2024-04-02
  • jvm原理之SystemGC源码分析
    概述 JVM的GC一般情况下是JVM本身根据一定的条件触发的,不过我们还是可以做一些人为的触发,比如通过jvmti做强制GC,通过System.gc触发,还可以通过jmap来触发等,...
    99+
    2024-04-02
  • DolphinScheduler容错源码分析之Worker
    目录引言Worker容错源码分析worker启动注册Master监听worker在zk节点的状态处理容错event事件总结引言 上一篇文章介绍了DolphinScheduler中M...
    99+
    2023-02-06
    DolphinScheduler容错Worker DolphinScheduler Worker
  • Go并发之RWMutex源码分析
    这篇文章主要介绍“Go并发之RWMutex源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Go并发之RWMutex源码分析”文章能帮助大家解决问题。RWMutex是一个支持并行读串行写的读写锁...
    99+
    2023-07-05
  • Redis 4.0源码安装的示例分析
    这篇文章主要介绍了Redis 4.0源码安装的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。 去官网下...
    99+
    2024-04-02
  • Java源码解析之ConcurrentHashMap的示例分析
    小编给大家分享一下Java源码解析之ConcurrentHashMap的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!早期 ConcurrentHashMap,其实现是基于:分离锁,也就是将内部进行分段(Segme...
    99+
    2023-06-15
  • Vue源码分析之虚拟DOM的示例分析
    小编给大家分享一下Vue源码分析之虚拟DOM的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!为什么需要虚拟dom?虚拟DOM就是为了解决浏览器性能问题而被...
    99+
    2023-06-15
  • python源码剖析之PyObject的示例分析
    这篇文章主要介绍python源码剖析之PyObject的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、Python中的对象Python中一切皆是对象。————Guido van Rossum(1989)这...
    99+
    2023-06-15
  • Mybatis源码分析之插件模块
    Mybatis插件模块 插件这个东西一般用的比较少,就算用的多的插件也算是PageHelper分页插件; PageHelper官网:https://github.com/pagehe...
    99+
    2024-04-02
  • Redis底层数据结构之dict、ziplist、quicklist详解
    目录1 Redis dict1.1 扩缩容的条件1.2 渐进式rehash操作2 Redis ziplist2.1 ziplist结构 2.2 entry结构3 Redis...
    99+
    2024-04-02
  • JAVA核心知识之ConcurrentHashMap源码分析
    1 前言 ConcurrentHashMap是基于Hash表的Map接口实现,键与值均不允许为NULL,他是一个线程安全的Map。同时他也是一个无序的Map,不同时间进行遍历可能会得...
    99+
    2024-04-02
  • Vue源码分析之虚拟DOM详解
    为什么需要虚拟dom? 虚拟DOM就是为了解决浏览器性能问题而被设计出来的。例如,若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内...
    99+
    2024-04-02
  • Flutter——ListView源码分析之Viewport的作用
    在Flutter中,ListView是一个高性能的滚动容器,用于展示一个列表。它可以根据内容的大小自动进行滚动,并且支持上下滑动、左...
    99+
    2023-09-21
    Flutter
  • Redisson分布式锁之加解锁源码分析
    这篇文章主要介绍“Redisson分布式锁之加解锁源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Redisson分布式锁之加解锁源码分析”文章能帮助大家解决问题。锁的可重入性我们都知道,Ja...
    99+
    2023-07-05
  • CesiumJS源码分析
    这篇文章主要介绍“CesiumJS源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“CesiumJS源码分析”文章能帮助大家解决问题。1. 有什么光CesiumJS 支持的光的类型比较少,默认场...
    99+
    2023-07-06
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作