Redis字符串类型内部编码剖析

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 概述 我们平时用 Redis都是处于用户层面,我们可能会不加思索地操作一个 key-value 对来方便地存取数据,感觉方便之至。但你知道这些数据在背后是如何存储以及编码的吗? 了解清楚了这个问题,将对我们更加高效地使用 Redis具有指导意义。

Profile


概述

我们平时用 Redis都是处于用户层面,我们可能会不加思索地操作一个 key-value 对来方便地存取数据,感觉方便之至。但你知道这些数据在背后是如何存储以及编码的吗? 了解清楚了这个问题,将对我们更加高效地使用 Redis具有指导意义。本文开始我们将结合 Redis源码来逐个探讨Redis五大数据类型的内部编码机制。

  • 实验环境:Redis 4.0.10

Redis数据类型内部编码概况

对于 Redis的常用 5 种数据类型(String、Hash、List、Set、sorted set),每种数据类型都提供了 最少两种 内部的编码格式,而且每个数据类型内部编码方式的选择 对用户是完全透明的,Redis会根据数据量自适应地选择较优化的内部编码格式。

如果想查看某个键的内部编码格式,可以使用 OBJECT ENCODING keyname 指令来进行,比如:

127.0.0.1:6379> 
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> 
127.0.0.1:6379> object encoding foo  // 查看某个Redis键值的编码
"embstr"
127.0.0.1:6379> 
127.0.0.1:6379> 

Redis 的每个键值内部都是使用一个名字叫做 redisObject 这个 C语言结构体保存的,其代码如下:

redisObject 结构体

解释如下:

  • type:表示键值的数据类型,包括 String、List、Set、ZSet、Hash
  • encoding:表示键值的内部编码方式,从 Redis源码看目前取值有如下几种:
#define OBJ_ENCODING_RAW 0        /* Raw representation */
#define OBJ_ENCODING_INT 1        /* Encoded as integer */
#define OBJ_ENCODING_HT 2         /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3     /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5    /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6     /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7   /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8     /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9  /* Encoded as linked list of ziplists */
  • refcount:表示该键值被引用的数量,即一个键值可被多个键引用

本文我们就从 Redis最基本的 String类型的内部编码开始探讨!



String类型的内部编码情况

字符串是 Redis最基本的数据类型,Redis 中字符串对象的编码可以是 intraw 或者 embstr 中的某一种,分别介绍如下:

  • int 编码:保存long 型的64位有符号整数
  • embstr 编码:保存长度小于44字节的字符串
  • raw 编码:保存长度大于44字节的字符串

我们不妨来做个实验实际看一下:

String的各种内部编码格式

实际情况就是 Redis 内部会根据用户给的不同键值而使用不同的编码格式,而这一切对用户完全透明!

Redis 是使用 SDS(“简单动态字符串”)这个结构体来存储字符串,代码里定义了 5种 SDS结构体:

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

可以看出,除了结构体字段数据类型的不同,其字段含义相差无几,其中:

  • len:字符串的长度(实际使用的长度)
  • alloc:分配内存的大小
  • flags:标志位,低三位表示类型,其余五位未使用
  • buf:字符数组

了解了这些基本的数据结构以后,我们就来看看上面例子中:

  • set foo 123
  • set foo abc
  • set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx

这三种情形下 Redis 内部到底是怎么存数据的!



INT 编码格式

命令示例: set foo 123

当字符串键值的内容可以用一个 64位有符号整形 来表示时,Redis会将键值转化为 long型来进行存储,此时即对应 OBJ_ENCODING_INT 编码类型。

OBJ_ENCODING_INT 编码类型内部的内存结构可以形象地表示如下:

set foo 123 时键值的内存结构

而且 Redis 启动时会预先建立 10000 个分别存储 0~9999 的 redisObject 变量作为共享对象,这就意味着如果 set字符串的键值在 0~10000 之间的话,则可以 直接指向共享对象 而不需要再建立新对象,此时键值不占空间!

因此,当执行如下指令时:

set key1 100
set key2 100

其实 key1key2 这两个键值都直接引用了一个 Redis 预先已建立好的共享 redisObject 对象,就像下面这样:

共享对象

源码之前,了无秘密,我们再对照下面的源码,来理解一下上述过程

INT编码的源码



EMBSTR编码格式

命令示例: set foo abc

Redis 在保存长度小于 44 字节的字符串时会采用 OBJ_ENCODING_EMBSTR 编码方式,口说无凭,我们来瞅瞅源码:

EMBSTR编码的判断条件

从上述代码中很容易看出,对于长度小于 44的字符串,Redis 对键值采用OBJ_ENCODING_EMBSTR 方式,EMBSTR 顾名思义即:embedded string,表示嵌入式的String。从内存结构上来讲 即字符串 sds结构体与其对应的 redisObject 对象分配在 同一块连续的内存空间,这就仿佛字符串 sds 嵌入在 redisObject 对象之中一样,这一切从下面的代码即可清楚地看到:

embedded string

因此,对于指令 set foo abc 所设置的键值,其内存结构示意图如下:

set foo abc时的键值内存结构



RAW 编码格式

指令示例: set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx

正如指令示例,当字符串的键值为长度大于 44超长字符串 时,Redis 则会将键值的内部编码方式改为 OBJ_ENCODING_RAW 格式,这与上面的 OBJ_ENCODING_EMBSTR 编码方式的不同之处在于 此时动态字符串 sds 的内存与其依赖的 redisObject 的 内存不再连续 了,以 set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx 为例,其键值的内存结构如下所示:

set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx时键值的内存结构

到此就讲完了最基本的String数据类型的内部编码情况,怎么样,还是挺好理解的吧!

后续我们将继续剖析 Redis 中 Hash 数据类型的内部编码格式。



后 记

由于能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!

作者更多的SpringBt实践文章在此:


如果有兴趣,也可以抽点时间看看作者一些关于容器化、微服务化方面的文章:


长按扫描 下面的 小心心 来订阅 CodeSheep,获取更多 务实、能看懂、可复现的 原创文 ↓↓↓

CodeSheep · 程序羊


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2月前
|
负载均衡 NoSQL 算法
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
这篇文章是关于Java面试中Redis相关问题的笔记,包括Redis事务实现、集群方案、主从复制原理、CAP和BASE理论以及负载均衡算法和类型。
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
|
2月前
|
存储 缓存 NoSQL
redis数据结构-字符串
redis数据结构-字符串
31 1
|
2天前
|
存储 缓存 NoSQL
3)深度解密 Redis 的字符串
3)深度解密 Redis 的字符串
8 1
|
8天前
|
JSON 缓存 NoSQL
redis序列化数据时,如何包含clsss类型信息?
通过配置 `com.fasterxml.jackson.databind.ObjectMapper` 的 `enableDefaultTyping` 方法,可以使序列化后的 JSON 包含类信息。
28 2
|
2月前
|
NoSQL 安全 Java
Redis6入门到实战------ 三、常用五大数据类型(字符串 String)
这篇文章深入探讨了Redis中的String数据类型,包括键操作的命令、String类型的命令使用,以及String在Redis中的内部数据结构实现。
Redis6入门到实战------ 三、常用五大数据类型(字符串 String)
|
2月前
|
C# 开发者 UED
WPF开发者必备秘籍:深度解析文件对话框使用技巧,打开与保存文件原来如此简单!
【8月更文挑战第31天】在WPF应用开发中,文件操作是常见需求。本文详细介绍了如何利用`Microsoft.Win32`命名空间下的`OpenFileDialog`和`SaveFileDialog`类来正确实现文件打开与保存功能。通过示例代码展示了如何设置文件过滤器、初始目录等属性,并使用对话框进行文件读写操作。正确使用文件对话框能显著提升用户体验,使应用更友好易用。
51 0
|
3月前
|
存储 缓存 NoSQL
Redis 常用五种数据类型编码
Redis 常用五种数据类型编码
34 9
|
2月前
|
存储 NoSQL Redis
【Redis 探秘】SDS 简单动态字符串:揭秘 Redis 高效字符串处理的秘密武器!
【8月更文挑战第24天】Redis采用的简单动态字符串(SDS)是一种专为优化内存存储和字符串操作而设计的数据结构。相较于C语言的标准字符串,SDS改进了字符串长度计算、内存重分配及字符串比较等问题。其特性包括预分配冗余空间减少未来的内存重分配、显式存储长度以加快获取速度等。这些改进使Redis能更高效地管理字符串数据。例如,在Redis中,SDS被广泛应用于键值对的存储,显著提升了字符串操作的性能。了解SDS不仅对于深入理解Redis的工作原理至关重要,也是开发者技能树中的重要一环。
30 0
|
2月前
|
存储 JSON NoSQL
揭秘Redis字符串String的隐藏技能!从基础到进阶,让你的数据存储操作秒变高大上!
【8月更文挑战第24天】Redis中的字符串类型作为其基石,不仅能够存储从简单文本到复杂格式如JSON的各种数据,还能通过多样化的命令实现包括但不限于自增自减、设置过期时间等高级功能,极大提升了其实用性和灵活性。例如,使用`SET`命令可添加或更新键值对,`GET`获取值,`DEL`删除键;同时,`INCR`和`DECR`支持对整数值的原子性增减操作,非常适合实现计数器等功能;通过`EXPIRE`命令设置过期时间,则适用于需要限时存储的应用场景。尽管名为“字符串”,但实际上还可存储图片、音频文件的Base64编码等形式的数据,为开发者提供了强大而灵活的工具。
37 0
|
2月前
|
NoSQL Java Redis
Redis字符串数据类型之INCR命令,通常用于统计网站访问量,文章访问量,实现分布式锁
这篇文章详细解释了Redis的INCR命令,它用于将键的值增加1,通常用于统计网站访问量、文章访问量,以及实现分布式锁,同时提供了Java代码示例和分布式锁的实现思路。
48 0