redis源码浅析-ziplist实现

这篇具有很好参考价值的文章主要介绍了redis源码浅析-ziplist实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

redis中的list是有多种实现的,其中一种是ziplist,其介绍如下

ziplist 是一个经过特殊编码的双向链表,旨在提高内存效率。 它存储字符串和整数值,其中整数被编码为实际整数而不是一系列字符。 它允许在 O(1) 时间内在列表的任一侧进行推送和弹出操作。 但是,由于每个操作都需要重新分配 ziplist 使用的内存,因此实际复杂性与 ziplist 使用的内存量有关。

ziplist是一个双向链表结构,是一整块紧凑的内存块,当大小不足需要重新扩展,底层使用je_realloc进行扩展。

typedef struct zlentry {
    unsigned int prevrawlensize; // prevrawlen字段的字节数大小,前一节点的大小的类型,1字节或者5字节
    unsigned int prevrawlen;     // 前一个节点的的长度
    unsigned int lensize;       // len字段的字节数大小
    unsigned int len;          
    unsigned int headersize;     /* prevrawlensize + lensize. */
    unsigned char encoding;      /* Set to ZIP_STR_* or ZIP_INT_* depending on
                                    the entry encoding. However for 4 bits
                                    immediate integers this can assume a range
                                    of values and must be range-checked. */
    unsigned char *p;            /* Pointer to the very start of the entry, that
                                    is, this points to prev-entry-len field. */
} zlentry;

zipList中在实际计算的时候,使用zlentry来表示每个节点,其中的prevrawlen是前一个节点的大小,len表示的是当前节点的大小,由于整个ziplist是一整块的内存块,通过prevrawlenlen字段,我们就能够向前或向后便利整个链表。
redis源码浅析-ziplist实现
需要注意的是,ziplist中的节点并不是一个zlentry,而是在计算的时候使用zlentry来模拟,实际ziplist中的每个节点,大概是如下结构``: ![在这里插入图片描述](https://img-blog.csdnimg.cn/cb20b5e505d841e1b12fb0aa9015e587.png) 通过zipEntry函数,将ziplist中一个entry节点信息,填充到一个zlentry中,这二者只是共享了同一个实际数据p`的指针。

static inline void zipEntry(unsigned char *p, zlentry *e) {
    ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen);
    ZIP_ENTRY_ENCODING(p + e->prevrawlensize, e->encoding);
    ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len);
    assert(e->lensize != 0); /* check that encoding was valid. */
    e->headersize = e->prevrawlensize + e->lensize;
    e->p = p;
}

整个ziplist的结构如上:zlbytes表示的是整个ziplist所占用的空间大小,zltail是尾节点的位置,zllen表示的是当前有多少个节点,zlend是一个标志位,表示的是链表的结尾。一个byte,为255.

在创建ziplist的时候,初始的时候只创建了一个空的ziplist表头:

/* Create a new empty ziplist. */
//创建一个空的ziplist
unsigned char *ziplistNew(void) {
    // 这里只分配了表头,即 zlbytes,zltail,zllen三个字段记录list的相关信息
    // zlbytes记录整个list的占用空间,zltail记录整个list中最后一个entry的偏移量,zllen记录entry的数量
    unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE;
    unsigned char *zl = zmalloc(bytes);
    ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);
    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);
    ZIPLIST_LENGTH(zl) = 0;
    zl[bytes-1] = ZIP_END;
    return zl;
}

初始分配了一个空的ziplist,只分配了zlbytes、zltai、zllen、,zlend这四个字段。当我们要插入一个节点时,通过ziplistPush方法插入

unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) {
    unsigned char *p;
    p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);
    return __ziplistInsert(zl,p,s,slen);
}
unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, newlen;
    unsigned int prevlensize, prevlen = 0;
    size_t offset;
    int nextdiff = 0;
    unsigned char encoding = 0;
    long long value = 123456789; /* initialized to avoid warning. Using a value
                                    that is easy to see if for some reason
                                    we use it uninitialized. */
    zlentry tail;

    /* Find out prevlen for the entry that is inserted. */
    if (p[0] != ZIP_END) {
        ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
    } else {
        unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
        if (ptail[0] != ZIP_END) {
            prevlen = zipRawEntryLengthSafe(zl, curlen, ptail);
        }
    }

    /* See if the entry can be encoded */
    if (zipTryEncoding(s,slen,&value,&encoding)) {
        /* 'encoding' is set to the appropriate integer encoding */
        reqlen = zipIntSize(encoding);
    } else {
        /* 'encoding' is untouched, however zipStoreEntryEncoding will use the
         * string length to figure out how to encode it. */
        reqlen = slen;
    }
    /* We need space for both the length of the previous entry and
     * the length of the payload. */
    reqlen += zipStorePrevEntryLength(NULL,prevlen);
    reqlen += zipStoreEntryEncoding(NULL,encoding,slen);

    /* When the insert position is not equal to the tail, we need to
     * make sure that the next entry can hold this entry's length in
     * its prevlen field. */
    int forcelarge = 0;
    nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
    if (nextdiff == -4 && reqlen < 4) {
        nextdiff = 0;
        forcelarge = 1;
    }

    /* Store offset because a realloc may change the address of zl. */
    offset = p-zl;
    newlen = curlen+reqlen+nextdiff;
    zl = ziplistResize(zl,newlen);
    p = zl+offset;

    /* Apply memory move when necessary and update tail offset. */
    if (p[0] != ZIP_END) {
        /* Subtract one because of the ZIP_END bytes */
        memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);

        /* Encode this entry's raw length in the next entry. */
        if (forcelarge)
            zipStorePrevEntryLengthLarge(p+reqlen,reqlen);
        else
            zipStorePrevEntryLength(p+reqlen,reqlen);

        /* Update offset for tail */
        ZIPLIST_TAIL_OFFSET(zl) =
            intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);

        /* When the tail contains more than one entry, we need to take
         * "nextdiff" in account as well. Otherwise, a change in the
         * size of prevlen doesn't have an effect on the *tail* offset. */
        assert(zipEntrySafe(zl, newlen, p+reqlen, &tail, 1));
        if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
        }
    } else {
        /* This element will be the new tail. */
        ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);
    }

    /* When nextdiff != 0, the raw length of the next entry has changed, so
     * we need to cascade the update throughout the ziplist */
    if (nextdiff != 0) {
        offset = p-zl;
        zl = __ziplistCascadeUpdate(zl,p+reqlen);
        p = zl+offset;
    }

    /* Write the entry */
    p += zipStorePrevEntryLength(p,prevlen);
    p += zipStoreEntryEncoding(p,encoding,slen);
    if (ZIP_IS_STR(encoding)) {
        memcpy(p,s,slen);
    } else {
        zipSaveInteger(p,value,encoding);
    }
    ZIPLIST_INCR_LENGTH(zl,1);
    return zl;
}

我们来分析下这个过程:

  1. 判断当前插入是在头结点插入还是尾结点插入,如果是头结点插入,那么当前指针偏移如下位置#define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t)) 结合上面的结构图,这个应该很明显。如果是尾结点,则偏移#define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1),这里的-1 是插入放在zlend前面。这样就拿到了待插入的节点在链表中的具体位置
  2. 拿到插入的位置后,通过__ziplistInsert进行实际的插入操作。在这里,首先会判断待插入的数据是否全部是数字还是是字符串。如果是数字的话,则会将字符串转换为数字保存,这样能够减少内存空间(并且redis在这里还会判断数字是在哪个范围,继而使用uint_8、uint_16、uint_32、uint_64),如果不是数字的话,则按照实际字符串的长度申请大小。
  3. 这里是节点本身内容的大小,还有节点需要额外记录前驱节点信息,prevlenencoding信息
  4. 对整个链表扩容,并移动数据,移动数据主要是ZIP_END节点,
  5. 写入preLen和encoding信息
  6. 复制数据内容到当前节点

这样我们就在当前list上插入了一个节点。

接下来我们看看,通过一个index获取list中的一个节点ziplistIndex

unsigned char *ziplistIndex(unsigned char *zl, int index) {
    unsigned char *p;
    unsigned int prevlensize, prevlen = 0;
    size_t zlbytes = intrev32ifbe(ZIPLIST_BYTES(zl));

        p = ZIPLIST_ENTRY_HEAD(zl);
        while (index--) {
            /* Use the "safe" length: When we go forward, we need to be careful
             * not to decode an entry header if it's past the ziplist allocation. */
            p += zipRawEntryLengthSafe(zl, zlbytes, p);
            if (p[0] == ZIP_END)
                break;
        }
    
    if (p[0] == ZIP_END || index > 0)
        return NULL;
    zipAssertValidEntry(zl, zlbytes, p);
    return p;
}

可以看到,这里就是通过不断移动指针,来到达指定节点指针,比如我们获取index = 6位置的节点,那么进行5次指针偏移计算,从而到达第6个节点指针。文章来源地址https://www.toymoban.com/news/detail-455767.html

到了这里,关于redis源码浅析-ziplist实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 浅析Redis(1)

    一.Redis的含义 Redis可以用来作数据库,缓存,流引擎,消息队列。redis只有在分布式系统中才能充分的发挥作用,如果是单机程序,直接通过变量来存储数据是更优的选择。那我们知道进程之间是有隔离性的,那么redis中的数据是如何共享的呢?reids是基于网络,把自己内存中

    2024年02月11日
    浏览(21)
  • 浅析Redis大Key | 京东云技术团队

    在京东到家购物车系统中,用户基于门店能够对商品进行加车操作。用户与门店商品使用Redis的Hash类型存储,如下代码块所示。不知细心的你有没有发现,如果单门店加车商品过多,或者门店过多时,此Key就会越来越大,从而影响线上业务。 2.1、BigKey的界定 BigKey称为大Key,

    2024年02月06日
    浏览(27)
  • 浅析Redis集群数据倾斜问题及解决方法

    在服务端系统服务开发中,缓存是一种常用的技术,它可以提高系统对请求的处理效率,而redis又是缓存技术栈中的一个佼佼者,广泛的应用于各种服务系统中。在大型互联网服务中,每天需要处理的请求和存储的缓存数据都是海量的,在这些大型系统中,使用单实例的redi

    2024年02月07日
    浏览(28)
  • OkHttp 源码浅析二

    OkHttp 配置参数: dispatcher 用于线程调度 connectionPool 连接池  64 个or 5 host 可以提升复用性 方便管理和提升性能 interceptors  networkInterceptors eventListenerFactory 事件监听器 连接建立 发送head body 等 retryOnConnectionFailure 连接 / 请求 失败是否重置 authenticator 自动认证修正 比如

    2024年02月12日
    浏览(37)
  • Android OkHttp 源码浅析一

    演进之路:原生Android框架不好用 ---- HttpUrlConnect   和 Apache HTTPClient   第一版  底层使用HTTPURLConnect   第二版 Square构建 从Android4.4开始 基本使用: 同步方法,Deque 双向队列 executableCalls 添加到calls 然后取出遍历 执行 executeOn runningAsyncCalls 正在执行的Call    for (i in 0 until e

    2024年02月11日
    浏览(31)
  • Android OkHttp 源码浅析二

    OkHttp 配置参数: dispatcher 用于线程调度 connectionPool 连接池  64 个or 5 host 可以提升复用性 方便管理和提升性能 interceptors  networkInterceptors eventListenerFactory 事件监听器 连接建立 发送head body 等 retryOnConnectionFailure 连接 / 请求 失败是否重置 authenticator 自动认证修正 比如

    2024年02月11日
    浏览(26)
  • Android Okhttp 源码浅析三

    添加网络事件拦截器 Interceptor val chain = RealInterceptorChain(         call = this,         interceptors = interceptors,         index = 0,         exchange = null,         request = originalRequest,         connectTimeoutMillis = client.connectTimeoutMillis,         readTimeoutMillis = client.readTimeoutMillis,      

    2024年02月11日
    浏览(28)
  • Tomcat请求处理流程与源码浅析

    系列文章目录和关于我 在tomcat中,Connector负责开启socket并且监听客户端请求,返回响应数据。 其中: Endpoint:tomcat中没有这个接口,只有AbstractEndpoint,它负责启动线程来监听服务器端口,并且在接受到数据后交给Processor处理 Processor:Processor读取到客户端请求后按照请求地址

    2024年02月06日
    浏览(31)
  • 11_Redis经典五大类型源码及底层实现

    SDS 动态字符串 双向链表 压缩列表 zpilist 哈希表 hashtable 调表 skiplist 整数集合 intset 快速列表 quicklist 紧凑列表 listpack Github:https://github.com/redis/redis Redis设计与实现 Redis5设计与源码分析 4.1 源码分析思路 怎么看 外面考什么,看什么 分类 4.2 Redis基本的数据结构(骨架) 简单动

    2024年02月11日
    浏览(27)
  • 浅析js中的闭包

    闭包 指那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。 闭包形成的原理 : 作用域链 。只要是代码都一个作用域中,写在函数内部的局部作用域,未写在任何函数内部即在全局作用域中;如果函数中还有函数,那么在这个作用域中就又可以诞生一

    2024年01月23日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包