redis数据结构(二) - 字符串

基于redis5.0的版本。

字符串编码:字符串对象的编码可以是int,raw或者embstr。

1.raw

raw就是redisObject+sds,即redisObjectptr指针指向一个sds对象。
78419626.png

// object.c
define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

robj *createRawStringObject(const char *ptr, size_t len) {
    return createObject(OBJ_STRING, sdsnewlen(ptr,len));
}

/* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is
 * an object where the sds string is actually an unmodifiable string
 * allocated in the same chunk as the object itself. */
robj *createEmbeddedStringObject(const char *ptr, size_t len) {
    robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1); // 申请连续的空间
    struct sdshdr8 *sh = (void*)(o+1);

    o->type = OBJ_STRING;
    o->encoding = OBJ_ENCODING_EMBSTR;
    o->ptr = sh+1;
    o->refcount = 1;
    if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
        o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
    } else {
        o->lru = LRU_CLOCK();
    }

    sh->len = len;
    sh->alloc = len;
    sh->flags = SDS_TYPE_8;
    if (ptr == SDS_NOINIT)
        sh->buf[len] = '\0';
    else if (ptr) {
        memcpy(sh->buf,ptr,len);
        sh->buf[len] = '\0';
    } else {
        memset(sh->buf,0,len+1);
    }
    return o;
}

2. embstr

如果字符串对象保存的是一个字符串值,并且这个字符粗值的长度小于等于44字节(44这个值并不会一直保持不变,例如redis3.2版本之前是39),则使用embstr编码,embstr即embedded string,“嵌入式的字符串,将SDS结构体嵌入RedisObject对象中”,是专门用于保存短字符串的一种编码方式,与raw的差别在于,raw会调用两次内存分配函数来创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间内一次包含了redisObject和sdshdr两个结构。
embstr有以下好处:

  • embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次,内存释放函数也是从两次降低为一次。
  • 因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面,所以这些编码的字符串对象比起raw编码的对象字符串,能够更好地利用缓存(CPU缓存/缓存行)带来的优势。

embstr的缺点:

  • 如果字符串的长度增加需要重新分配内存时,sds需要重新分配空间,所以embstr编码的字符串对象实际上是只读的,redis没有为embstr编码的字符串对象编写任何相应的修改程序。当我们对embstr编码的字符串对象执行任何修改命令(例如append)时,程序会先将对象的编码从embstr转换成raw,然后再执行修改命令。
redis> SET msg hello
OK
redis> OBJECT ENCODING msg
embstr
redis> DEBUG OBJECT msg
Value at:0x7fd74ecac8a0 refcount:1 encoding:embstr serializedlength:6 lru:815344 lru_seconds_idle:14
redis> APPEND msg world
10
redis> OBJECT ENCODING msg
raw
redis> DEBUG OBJECT msg
Value at:0x7fd76445d0b0 refcount:1 encoding:raw serializedlength:11 lru:815482 lru_seconds_idle:26

为什么是“44”?

  1. redisObject = 16byte = type 4bit + encoding 4bit + lru 24bit + refcount 4byte + ptr 8byte。
  2. sdshdr=len 1byte + alloc 1byte + flag 1byte + '0' 1byte + buf长度。(3.2之前的版本)

    • 本身就是针对短字符串的embstr自然会使用最小的sdshdr8。
  3. 从2.4版本开始,redis开始使用jemalloc内存分配器。可以简单理解,jemalloc不是一个一个字节来申请和分配的,会分配8,16,32,64等字节的内存(如需要12个字节,就会分配16个字节)。embstr最小为16+8+1=25,所以最小分配64字节。当字符数小于39时,都会分配64字节。这个默认39就是这样来的。

    • jemalloc作为Redis的默认内存分配器,在减小内存碎片方面做的相对比较好。jemalloc在64位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当Redis存储数据时,会选择大小最合适的内存块进行存储。
    • 例如:如果需要存储大小为130字节的对象,jemalloc会将其放入160字节的内存单元中。
    • 下图图片来源:Redis容量评估模型
      62832053.png
  4. 3.2版本之前是39,3.2开始是44,

    • 3.2版本将原来的sdshdr改成了sdshdr8,sdshdr16,sdshdr32,sdshdr64,里面的unsigned int 变成了uint8_t,uint16_t...(还加了一个char flags)这样更加优化小sds的内存使用。
    • 本身就是针对短字符串的embstr自然会使用最小的sdshdr8,而sdshdr8与之前的sdshdr相比正好减少了5个字节(sdsdr8 = uint8_t 2 + char = 12+1 = 3, sdshdr = unsigned int 2 = 4 2 = 8),所以其能容纳的字符串长度增加了5个字节变成了44。
    • 本次变动的commit地址
  5. embstr结构图:

78516328.png

3. int

如果一个字符串对象保存的是整数,并且这个整数值可以用long类型来标识(不超过long的范围),这时候字符串对象redisobject的指针将直接保存long数值(将void *转换成long)。

  • 没有sdshdr对象。
  • Long刚好跟指针的字节数一样(例如64位服务器下都是占8byte,“9223372036854775807”是8位字节可表示的最大整数,它的16进制形式是:0x7fffffffffffffffL,所以数值不能超过9223372036854775807)。
  • 取数据时将指针地址转为long值:(long)o->ptr。
// server.h
define OBJ_SHARED_INTEGERS 10000
// object.c
robj *createStringObjectFromLongLongWithOptions(long long value, int valueobj) {
    robj *o;

    if (server.maxmemory == 0 ||
        !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS))
    {
        /* If the maxmemory policy permits, we can still return shared integers
         * even if valueobj is true. */
        valueobj = 0;
    }

    if (value >= 0 && value < OBJ_SHARED_INTEGERS && valueobj == 0) { // 10000以内直接用共享的对象
        incrRefCount(shared.integers[value]);
        o = shared.integers[value];
    } else {
        if (value >= LONG_MIN && value <= LONG_MAX) {
            o = createObject(OBJ_STRING, NULL);
            o->encoding = OBJ_ENCODING_INT;
            o->ptr = (void*)((long)value); // 直接将long值转为指针地址存储
        } else {
            o = createObject(OBJ_STRING,sdsfromlonglong(value));
        }
    }
    return o;
}
Image placeholder
MZTB0511
未设置
  63人点赞

没有讨论,发表一下自己的看法吧

推荐文章
Bash技巧:使用参数扩展获取变量的子字符串和字符串长度

在bash中,通常使用${parameter}表达式来获取parameter变量的值,这是一种参数扩展(parameterexpansion)。Bash还提供了其他形式的参数扩展,可以对变量值做一些处

我们为什么应该关注SaaS数据备份?

使用基于云的应用程序,也称为软件即服务(SaaS),如Office365和Salesforce,现在几乎已成为大多数组织运营的主流部分。依赖这些服务承载您业务的关键部分意味着它们的可用性对确保您的业务

Pandas数据分析——超好用的Groupby详解

微信公众号:「Python读财」如有问题或建议,请公众号留言在日常的数据分析中,经常需要将数据根据某个(多个)字段划分为不同的群体(group)进行分析,如电商领域将全国的总销售额根据省份进行划分,分

Pandas数据处理三板斧——map、apply、applymap详解

微信公众号:「Python读财」如有问题或建议,请公众号留言在日常的数据处理中,经常会对一个DataFrame进行逐行、逐列和逐元素的操作,对应这些操作,Pandas中的map、apply和apply

jquery如何判断字符串是否包含指定字符?

方法一:使用indexOf()和lastIndexOf()方法案例:varCts="bblText"; if(Cts.indexOf("Text")>=0){ alert('Cts中包含Text字符串

Redis的数据结构和内部编码

redis是单线程,一次只执行一条命令,那为什么可以这么快: 纯内存 非阻塞IO 避免线程切换和竞态消耗 在使用过程中要注意: 一次只运行一条命令 避免长(慢)命令,例如keys、flushall、f

【数据结构】2_数据的艺术

程序设计的挑战 利用计算机解决现实生活中的问题 生活中的不同个体之间存在联系 用计算机程序描述生活中个体间的联系 问题:如何描述生活中的个体?数据的概念程序的操作对象,用于描述客观事物数据的特点 可以

python set (集合)数据结构

set(集合)是一个非常有用的数据结构。它与列表(list)的行为类似,区别在于set不能包含重复的值。这在很多情况下非常有用。例如你可能想检查列表中是否包含重复的元素,你有两个选择,第一个需要使用f

Python入门教程_5. 数据结构

这个章节将更详细地描述一些你已经了解的内容,并且添加了一些新的内容。 5.1.深入列表对象 List数据类型包含更多的方法,下面是List对象包含的所有方法: list.append(*x*) 将一个

数据结构与算法分析——开篇以及复杂度分析

开篇 你也许已经发现了,工作了几年,原以为已经是一只老鸟。但看到刚参加工作的同事,你发现,原来自己一直在原地踏步。跟新人相比,你的唯一优势就是对业务更熟悉而已,别的就没有什么优势了。 怎样才能够让自己

数据结构与算法分析——开篇以及复杂度分析

开篇你也许已经发现了,工作了几年,原以为已经是一只老鸟。但看到刚参加工作的同事,你发现,原来自己一直在原地踏步。跟新人相比,你的唯一优势就是对业务更熟悉而已,别的就没有什么优势了。怎样才能够让自己更上

JavaScript 的数据结构和算法

现在有个还不是好的项目,未来会成为好的项目的项目想介绍给大家。传送门https://github.com/MasterShu/JavaScript-Da...这个是本人在维护的一个项目。主要是使用Ja

数据结构与算法分析——链表

链表链表是一种常见的数据结构,是一组有序的数据,每个链表中的数据项称为元素。它跟数组很像,二者对比学习会更容易理解和记忆。数组是内存中连续的一块,不会间断。链表在内存中不一定是连续的一块。如果内存只剩

数据结构与算法分析——队列

定义队列,和栈类似,也是一种操作受限的线性表数据结构。与栈结构不同的是,可以对队列2端操作,要求数据从一端进(入队),从另一端出(出队)。队列遵循"先进先出"的原则,即最先进队列的数据元素,同样要最先

数据结构与算法分析——栈

定义栈是一种操作受限的线性表,只支持在一端进行插入和删除操作(入栈和出栈)。后进先出、先进后出是它最大的特点。当某个数据集合只在一端插入和删除数据,并满足先进后出的特性时,就可以选择栈这种数据结构。实

【数据结构】3_程序设计的灵魂

学员间的对话 木暮:我发现三井真是牛,只用一行就实现了strlen 宫城:那么强!他是怎么做的呢? 木暮:不知道,我看了一下,没看懂。。。 宫城:牛人就是牛人啊! 问题:程序是否越短越好?是否别人看不

【数据结构】1_进阶高手的大门

理解程序的本质问题:为什么会有各种各样的程序存在?程序的本质是什么?程序是为了解决实际问题而存在的,从本质而言,程序是解决问题的步骤描述。一小步的进阶:理解实际问题 确认问题类型 如:数值计算,求最

java与数据结构

数组与链表数组 数组是数据结构中的基本模块之一 数组是一种基本的数据结构,用于按顺序存储元素的集合。但是元素可以随机存取,因为数组中的每个元素都可以通过数组索引来识别。 数组可以有一个或多个维度

【数据结构】 10_C++异常简介

C++内置了异常处理的语法元素try...catch... try语句处理正常代码逻辑 catch语句处理异常代码逻辑 try语句中的异常由对应的catch语句处理 try { doubler=

第一章:数据结构绪论

[[数据结构-第1章]绪论目录 [1数据结构有什么用?] [2基本概念和术语] [3逻辑结构与存储结构] [3.1逻辑结构] [3.2存储结构] [4抽象数据类型] [4.1数据类型] [

【数据结构】11_异常类构建

异常的类型可以是自定义类类型 对于类类型异常的匹配依旧是至上而下严格匹配 赋值兼容性原则在异常匹配中依然适用 一般而言 匹配子类异常的catch放在上部 匹配父类异常的catch放下下部 现代

数据类型详解-字符串笔记

数据类型详解-字符串 回顾字符串的定义方式 了解转义字符 字符串相关的操作 字符串格式化的方法 字符串相关函数 字符串的定义方式 单引号定义字符串‘’ 双引号定义字符串“” 三引号定义字符串‘’

GoWeb教程_07.6. 字符串处理

字符串在我们平常的Web开发中经常用到,包括用户的输入,数据库读取的数据等,我们经常需要对字符串进行分割、连接、转换等操作,本小节将通过Go标准库中的strings和strconv两个包中的函数来讲解

Java 8 API 示例:字符串、数值、算术和文件

Java8API示例:字符串、数值、算术和文件 大量的教程和文章都涉及到Java8中最重要的改变,例如lambda表达式和函数式数据流。但是此外许多现存的类在JDK8API中也有所改进,带有一些实用的

分享 6 个 Go 处理字符串的技巧

如果你从Ruby或者Python转型到Go,将会有很多语言差异需要学习,其中很多问题都是围绕处理string类型。下面是一些字符串的技巧,这些技巧解决了我在使用Golang的最初几周中遇到的问题。