资讯专栏INFORMATION COLUMN

【Redis学习笔记】2018-07-03 redis源码学习 quicklist

gxyz / 629人阅读

摘要:顺风车运营研发团队方波概述是一个的双向链表,用在上层数据结构的底层实现。包含多个,由来保存数据。表示两端各有个节点不压缩,中间的节点压缩。

顺风车运营研发团队 方波
quicklist概述:
quicklist是一个ziplist的双向链表,用在上层list数据结构的底层实现。quicklist包含多个quicklistNode,quicklistNode由ziplist来保存数据。

quicklist结构图:

quicklist特点:
双向链表的内存开销很大,每个节点的地址不连续,容易产生内存碎片,利用ziplist减少node数量,但ziplist插入和删除数都很麻烦,复杂度高,为避免长度较长的ziplist修改时带来的内存拷贝开销,通过配置项配置合理的ziplist长度。

Redis的配置文件中,给出了每个ziplist中的元素个数设定,对应quicklist的fill属性,设置根据场景而定:
list-max-ziplist-size -2 // 如果取值为正数表示ziplist节点数量,最大16bit
负数取值:

-1 每个节点的ziplist字节大小不能超过4kb

-2 每个节点的ziplist字节大小不能超过8kb redis默认值

-3 每个节点的ziplist字节大小不能超过16kb

-4 每个节点的ziplist字节大小不能超过32kb

-5 每个节点的ziplist字节大小不能超过64kb

因为链表的特性,一般收尾两端操作较频繁,中部操作相对较少,所以redis提供压缩深度配置:
参数list-compress-depth的取值含义如下:

0: 是个特殊值,表示都不压缩。这是Redis的默认值。

1: 表示quicklist两端各有1个节点不压缩,中间的节点压缩。

2: 表示quicklist两端各有2个节点不压缩,中间的节点压缩。

3: 表示quicklist两端各有3个节点不压缩,中间的节点压缩。

依此类推,最大16bit

quicklist结构体:

typedef struct quicklist {
    quicklistNode *head;
    quicklistNode *tail;
    unsigned long count;        /* total count of all entries in all ziplists */
    unsigned long len;          /* number of quicklistNodes */
    int fill : 16;              /* fill factor for individual nodes */
    unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
} quicklist;
head,tail:指向头尾节点的指针
count: 所有ziplist中entry数量
len: node数量
fill: ziplist中entry能保存的数量,由list-max-ziplist-size配置项控制
compress: 压缩深度,由list-compress-depth配置项控制
 
typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *zl;
    unsigned int sz;             /* ziplist size in bytes */
    unsigned int count : 16;     /* count of items in ziplist */
    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
    unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */
    unsigned int recompress : 1; /* was this node previous compressed? */
    unsigned int attempted_compress : 1; /* node can"t compress; too small */
    unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;
prev,next:前后节点指针
zl: 数据指针。当前节点的数据没有压缩,那么它指向一个ziplist结构;否则,它指向一个quicklistLZF结构。
sz: zl指向的ziplist实际占用内存大小。需要注意的是:如果ziplist被压缩了,那么这个sz的值仍然是压缩前的ziplist大小。
count: ziplist里面包含的数据项个数
encoding: ziplist是否压缩。取值:1--ziplist,2--quicklistLZF
container: 存储类型,目前使用固定值2 表示使用ziplist存储
recompress: 当我们使用类似lindex这样的命令查看了某一项本来压缩的数据时,需要把数据暂时解压,这时就设置recompress=1做一个标记,等有机会再把数据重新压缩
attempted_compress: Redis自动化测试程序有用
extra: 其它扩展字段,目前没有使用
 
typedef struct quicklistLZF {
    unsigned int sz; /* LZF size in bytes*/
    char compressed[];
} quicklistLZF;
sz: 压缩后的ziplist大小
compressed:柔性数组,存放压缩后的ziplist字节数组
lpush操作:
void pushGenericCommand(client *c, int where) {
    int j, 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++) {
        if (!lobj) {
            lobj = createQuicklistObject();
            quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
                                server.list_compress_depth);
            dbAdd(c->db,c->argv[1],lobj);
        }
        listTypePush(lobj,c->argv[j],where);
        pushed++;
    }
    addReplyLongLong(c, (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;
}
如果链表不存在,则创建,并设置fill和compress参数,然后调用listTypePush -> quicklistPush -> quicklistPushHead

int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
    quicklistNode *orig_head = quicklist->head;
    if (likely(_quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
        quicklist->head->zl = ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);
        quicklistNodeUpdateSz(quicklist->head);
    } else {
        quicklistNode *node = quicklistCreateNode();
        node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
        quicklistNodeUpdateSz(node);
        _quicklistInsertNodeBefore(quicklist, quicklist->head, node);
    }
    quicklist->count++;
    quicklist->head->count++;
    return (orig_head != quicklist->head);
}
_quicklistNodeAllowInsert 判断该头部节点是否允许插入,计算头部节点中的大小和fill参数设置的大小相比较,如果超出fill 则创建新的node和ziplist
quicklistNodeUpdateSz 更新头部大小

_quicklistNodeAllowInsert:

REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node, const int fill, const size_t sz) {
    if (unlikely(!node))
        return 0;
    int ziplist_overhead; // 跳过head的字节数
    /* 根据ziplist结构,长度小于254使用一个字节存储长度 */
    if (sz < 254)
        ziplist_overhead = 1;
    else
        ziplist_overhead = 5;
 
    /*  */
    if (sz < 64)
        ziplist_overhead += 1;
    else if (likely(sz < 16384))
        ziplist_overhead += 2;
    else
        ziplist_overhead += 5;
 
    /* new_sz overestimates if "sz" encodes to an integer type */
    unsigned int new_sz = node->sz + sz + ziplist_overhead;
    if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(new_sz, fill)))
        return 1;
    else if (!sizeMeetsSafetyLimit(new_sz))
        return 0;
    else if ((int)node->count < fill)
        return 1;
    else
        return 0;
}
_quicklistNodeSizeMeetsOptimizationRequirement 比较new_sz和fill,如果fill>=0返回0,否则返回-fill对应optimization_level的大小与sz对比

REDIS_STATIC int
_quicklistNodeSizeMeetsOptimizationRequirement(const size_t sz,
                                               const int fill) {
    if (fill >= 0)
        return 0;
    size_t offset = (-fill) - 1;
    if (offset < (sizeof(optimization_level) / sizeof(*optimization_level))) {
        if (sz <= optimization_level[offset]) {
            return 1;
        } else {
            return 0;
        }
    } else {
        return 0;
    }
}
 
static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536}; // 分别是4k 8k 16k 32k 64k 分别对应了上面给出的list-max-ziplist-size负数配置项

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/36718.html

相关文章

  • Redis学习笔记2018-07-06 Redis指令学习2

    摘要:而函数会根据前插还是后插的不同调用和而和都是调用的方法。调用完后,会判断的长度是否为,若不为,则会执行函数,通知阻塞的客户端。 顺风车运营研发团队 谭淼 一、brpoplpushbrpoplpush是rpoplpush的阻塞版本,当给定列表 source 不为空时, brpoplpush的表现和rpoplpush一样。当列表 source 为空时,brpoplpush命令将阻塞连接,...

    z2xy 评论0 收藏0
  • 跟着大彬读源码 - Redis 5 - 对象和数据类型(上)

    摘要:对象源码结构如下对象类型对象编码引用统计指向底层实现数据结构的指针字段对象类型,就是我们常说的。。对象编码对应跳跃表压缩列表集合动态字符串等八种底层数据结构。 相信很多人应该都知道 Redis 有五种数据类型:字符串、列表、哈希、集合和有序集合。但这五种数据类型是什么含义?Redis 的数据又是怎样存储的?今天我们一起来认识下 Redis 这五种数据结构的含义及其底层实现。 首先要明确...

    antz 评论0 收藏0
  • 跟着大彬读源码 - Redis 5 - 对象和数据类型(上)

    摘要:对象源码结构如下对象类型对象编码引用统计指向底层实现数据结构的指针字段对象类型,就是我们常说的。。对象编码对应跳跃表压缩列表集合动态字符串等八种底层数据结构。 相信很多人应该都知道 Redis 有五种数据类型:字符串、列表、哈希、集合和有序集合。但这五种数据类型是什么含义?Redis 的数据又是怎样存储的?今天我们一起来认识下 Redis 这五种数据结构的含义及其底层实现。 首先要明确...

    cnTomato 评论0 收藏0
  • Redis列表类型

    摘要:在前几篇我们介绍了类型中的字符串类型和哈希类型,今天我们了解一下中的列表类型。并且我们通过上图所示知道命令在执行成功后也是会有返回值的,返回的结果就是当前列表中元素的个数。链表当列表类型无法满足条件时,会选择用作为列表的内部实现。 在前几篇我们介绍了Redis类型中的字符串类型和哈希类型,今天我们了解一下Redis中的列表类型。在Redis中列表类型,可以简单的理解为存储多个有序字符串...

    codecraft 评论0 收藏0
  • Redis5源码学习】2019-04-17 压缩列表ziplist

    摘要:记录压缩列表总共占用的字节数,在对压缩列表进行内存重分配时使用个字节。为十六进制值,标记一个压缩列表的末尾具体的数据存放在中。占用或字节表示当前存储数据的类型与数据的长度。在学习的同时,如果没有经过自己的思考,收效甚微。 baiyan全部视频:https://segmentfault.com/a/11... 为什么需要ziplist 乍一看标题,我们可能还不知道ziplist是何许人也...

    church 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<