Redis小于等于44个字节的字符串是embstr编码、大于44个字节是raw编码

1、字符串编码类型

字符串类型的内部编码有三种:
1、int:存储 8 个字节的长整型(long,2^63-1)。
2、embstr:代表 embstr 格式的 SDS(Simple Dynamic String 简单动态字符串),
存储小于 44 个字节的字符串,只分配一次内存空间(因为 Redis Object 和 SDS 是连续的)。
3、raw:存储大于 44 个字节的字符串(3.2 版本之前是 39 个字节),需要分配两次内存空间(分别为 Redis Object 和 SDS 分配空间)。

2、embstr结构

今天主要是来研究一下,embstr编码的问题,先来看一下embstr的内存结构:

在这里插入图片描述
embstr分配的是连续的一块内存,包含redisObjecsds,redisObject占用了16个字节,先来看一下redisObject的存储结构:

typedef struct redisObject {
    /*对象的类型*/
    unsigned type:4;
    /*具体的数据结构,embstr、sds、*/
    unsigned encoding:4;
    /* 24位,对象最后一次被命令程序访问的时间,与内存回收有关*/
    /* LRU time (relative to global lru_clock) or
     * LFU data (least significant 8 bits frequency
     * and most significant 16 bits access time). */
    unsigned lru:LRU_BITS;
    /*引用计数。当refcount为0的时候,表示该对象已经不被任何对象引用,则可以进行垃圾回收了*/
    int refcount;
    /*指向对象实际的数据结构*/
    void *ptr;
} robj;

下面再来看一下sds的结构:

2.1 Reids3.2之前版本

因为在之前很多书上都会说存储小于39个字节,编码为embstr,超过39个字节,编码为raw。这是在3.2版本之前。下面我们就先来看一下之前的版本:

/*
 * 保存字符串对象的结构
 */
struct sdshdr {
    // buf 中已占用空间的长度
    unsigned int len;
    // buf 中剩余可用空间的长度
    unsigned int free;
    // 数据空间
    char buf[];
};

先来剖析一下为什么之前embstr在存储小于39个字符都是embstr的,大于39个之后就是raw的了。

 sdshdr = unsigned int * 2 = 4 * 2 = 8

embstr : redisObject(16)+sds{char(39个) + 8 + 1} =64

此时可以看出如果是39个字符就是64个字节,那么也就是说只要是小于39个字符的,分配的空间都是64个字节。超过64个字节就为raw。

2.2 Reids3.2之后版本

本文选用的源代码是5.0.8版本,在 3.2 以后的版本中,SDS 又有多种结构(sds.h文件中):sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64,用于存储不同的长度的字符串,分别代表 25=32byte,28=256byte,216=65536byte=64KB,232byte=4GB。先来查看一下sds的结构:

/* object.c */
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44 //制定长度
struct __attribute__ ((__packed__)) sdshdr8 {
    //当前字符数组的长度
    uint8_t len; /* used */
//    当前字符数组总共分配的内存大小
    uint8_t alloc; /* excluding the header and null terminator */
//    当前字符数组的属性、用来标识到底是 sdshdr8 还是 sdshdr16 等
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
//    字符串真正的值
    char buf[];
};

我们来看一下5.0的中的sds的结构,经过分析可以看到sds使用了uint8_t代替int,占用的内存空间更小了,下面计算一下sds的头部信息占用的空间大小:sdsdr8 = uint8_t (1个字节)* 2 + char(1个字节) = 3个字节

由于对sds的头部信息结构做了调整,所以在原来39的基础上又可以多存储了5个字节,为44个字节。

从2.4版本开始,redis开始使用jemalloc内存分配器。这个比glibc的malloc要好不少,还省内存。在这里可以简单理解,jemalloc会分配8,16,32,64等字节的内存。

所以embstr分配的最新内存为:

redisObject(16)+sds(uint8_t (1个字节)* 2 + char(1个字节)+8(最小分配内存) +1(\0结束符))=28个字节;

3、引出问题

写到这里突然发现一个问题,因为redis中提供了sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64,五种用于存储不同的长度的sds字符串。现在最小的大小为28个字节,会不会使用sdshdr5来进行存储呢,那如果是这样的话,我们之前最小字节数又得重新计算,因为sdshdr5()的结构跟其他几个有所不同,如下

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};

不过我们仔细看下上面的注释:sdshdr5 is never used, we just access the flags byte directly.这个方法居然从未没使用,感觉有点奇怪,只能继续扒代码,然后有一个重大发现:

//在sds.c文件中
sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    char type = sdsReqType(initlen);
    /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    
    。。。。。。。。。。。。。。。。。。。。。。。。。。。
    switch(type) {
        case SDS_TYPE_5: {
            *fp = type | (initlen << SDS_TYPE_BITS);
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
      。。。。。。。。。。。。。。。。。。。。。。。
    }
    if (initlen && init)
        memcpy(s, init, initlen);
    s[initlen] = '\0';
    return s;
}

上面可以看到:

  /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;

initlen=0的时候,并未使用SDS_TYPE_5,而是直接使用的是SDS_TYPE_8,所以头字节信息还是占用的3个字节,验证我们上面的推断是没有问题的。

4、结论

embstr存储形式是这样一种存储形式,它将 RedisObject 对象头和 SDS 对象连续存在一起,使用 malloc 方法一次分配。embstr的最小占用空间为19(16+3),而64-19-1(结尾的\0)=44,所以empstr只能容纳44字节。

当字节数小于44时,分配的大小一直都是64个字节。一旦超过44个字节,整体的大小超过64个字节,在Redis中将认为是一个大的字符串,不再使用 emdstr 形式存储,存储结构将变为raw。

  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
在 Java 中,如果要将一个对象序列化为字符串后再存储到 Redis 中,可以使用一些常见的序列化工具,比如 JSON、Jackson、Protobuf 等,将对象转换为字符串后再进行存储。 以下是将一个 Java 对象使用 Jackson 序列化为 JSON 字符串,并使用 Jedis 客户端进行存储的示例: ```java import com.fasterxml.jackson.databind.ObjectMapper; import redis.clients.jedis.Jedis; public class RedisExample { public static void main(String[] args) throws Exception { // 创建 Redis 客户端连接 Jedis jedis = new Jedis("localhost"); // 定义一个 Java 对象 User user = new User("Alice", 25, "alice@example.com"); // 使用 Jackson 序列化为 JSON 字符串 ObjectMapper mapper = new ObjectMapper(); String userJson = mapper.writeValueAsString(user); // 将序列化后的字符串保存到 Redis 中 jedis.set("user:1", userJson); // 关闭 Redis 客户端连接 jedis.close(); } } class User { private String name; private int age; private String email; public User(String name, int age, String email) { this.name = name; this.age = age; this.email = email; } // getter 和 setter 方法省略 } ``` 在这个示例中,我们使用 Jackson 序列化工具将一个 Java 对象序列化为 JSON 字符串,并使用 Jedis 客户端的 `set` 方法将其保存到 Redis 中。在读取数据时,需要将序列化后的字符串反序列化为 Java 对象。以下是从 Redis 中读取并反序列化为 Java 对象的示例: ```java import com.fasterxml.jackson.databind.ObjectMapper; import redis.clients.jedis.Jedis; public class RedisExample { public static void main(String[] args) throws Exception { // 创建 Redis 客户端连接 Jedis jedis = new Jedis("localhost"); // 从 Redis 中读取序列化后的字符串 String userJson = jedis.get("user:1"); // 使用 Jackson 反序列化为 Java 对象 ObjectMapper mapper = new ObjectMapper(); User user = mapper.readValue(userJson, User.class); // 输出用户信息 System.out.println(user.getName()); // Alice System.out.println(user.getAge()); // 25 System.out.println(user.getEmail()); // alice@example.com // 关闭 Redis 客户端连接 jedis.close(); } } class User { private String name; private int age; private String email; public User() { } public User(String name, int age, String email) { this.name = name; this.age = age; this.email = email; } // getter 和 setter 方法省略 } ``` 在这个示例中,我们使用 Jedis 客户端的 `get` 方法从 Redis 中读取序列化后的字符串,然后使用 Jackson 反序列化工具将其反序列化为 Java 对象,并输出用户信息。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值