浅析Redis中String数据类型及其底层编码

 更新时间:2023年05月25日 09:18:15   作者:WARRIOR  
这篇文章主要介绍 Redis 中 String 数据类型及其底层编码,文中有详细的代码示例,对大家的工作及学习有一定的帮助,需要的朋友可以参考下

从 RedisObject 说起

在 Redis 中,任意数据类型的键和值都会被封装为一个 RedisObject ,也叫做Redis对象,源码如下

/*server.h*/
typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
    int refcount;
    void *ptr;
} robj;

我们来看一下这个结构体中的成员变量分别代表什么:

  • unsigned type:4 :对象类型,分别是 string hash list set zset ,占 4 个 bit 位,如下所示

#define OBJ_STRING 0    /* String object. */
#define OBJ_LIST 1      /* List object. */
#define OBJ_SET 2       /* Set object. */
#define OBJ_ZSET 3      /* Sorted set object. */
#define OBJ_HASH 4      /* Hash object. */
  • unsigned encoding:4: 底层编码方式,共有 11 种,4 个 bit 位

  • unsigned lru:LRU_BITS :该对象最后一次被访问的时间,占 24 个 bit ,在 Redis 内存回收中起到关键作用

  • int refcount :对象引用计数器,计数器为 0 则说明对象无人引用,可以被回收

  • void *ptr:指针,指向存放实际数据的空间

我们注意到,在 Redis 中有 5 中数据结构(用户使用的),但在底层却有 11 种编码方式,Redis 会根据存储的数据类型、存储数据的大小,选择不同的编码方式,以获得最优的性能。一种数据结构会对应多种数据结构,如下表所示。

数据类型编码方式
OBJ_STRINGint、embstr、raw
OBJ_LISTLinkedList和ZipList(3.2以前)、QuickList(3.2以后)
OBJ_SETintset、HT
OBJ_ZSETZipList、HT、SkipList
OBJ_HASHZipList、HT

下面,我们现在介绍以下 String 数据类型,及其底层的编码方式。

Redis 数据结构 -- String

String 类型的基本介绍和命令

String 类型,也就是字符串类型,是Redis中最简单的存储类型。它可以存储字符串、整数或浮点数。下面是一些 String 类型常用的命令

  • SET key value:设置指定 key 的值为指定的字符串或数字。

  • GET key:获取指定 key 的值。

本地虚拟机redis:0>set key01 value01
"OK"
本地虚拟机redis:0>get key01
"value01"

  • INCR key:将指定 key 的值加 1,如果该 key 不存在,则先将其设置为 0,再进行加 1 操作。

  • DECR key:将指定 key 的值减 1,如果该 key 不存在,则先将其设置为 0,再进行减 1 操作。

  • INCRBY key increment:将指定 key 的值增加指定的增量。

  • DECRBY key decrement:将指定 key 的值减少指定的减量。

  • APPEND key value:将指定的值追加到指定 key 的值的末尾。

  • STRLEN key:返回指定 key 的值的长度。

  • GETRANGE key start end:返回指定 key 的值的子字符串,根据起始位置和结束位置指定。

  • SETRANGE key offset value:将指定 key 的值从指定偏移位置开始,替换为指定的字符串。

  • MSET key1 value1 [key2 value2 ...]:同时设置多个 key 的值。(”[ ]” 中括号内表示可选)

  • MGET key1 [key2 ...]:获取多个 key 的值。

💡 这里仅给出 SET、GET 命令,其他的请自行测试。这些命令只是 Redis String 类型命令的一小部分,Redis 还提供了其他更多的命令来处理 String 类型的数据。你可以参考 Redis 官方文档以获取完整的命令列表和详细的命令说明。

String 类型的底层实现

在 Redis 中,String 类型的数据结构并不是采用 C 语言中自带的字符串类型,C 语言中的数据结构存在很多问题,比如:

  • 获取字符串长度的需要通过运算
  • 非二进制安全
  • 不可修改

因此,String 在 Redis 中有其他三种编码方式: int、embstr、raw 。其中, raw 和 embstr 类型,都是基于动态字符串(SDS)实现的,下面我们先来看看动态字符串的结构是怎样的。

动态字符串(SDS)

动态字符串的结构体如下

struct __attribute__ ((__packed__)) hisdshdr8 {
    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[];
};

这里解释一下结构体中各个成员变量的作用:

  • len:已经保存的字符串字节数,不包含结束标示
  • alloc:申请的总的字节数,不包含结束标示
  • flags:不同的 SDS 的头类型,用来控制 SDS 的头大小
  • buf[]:真正存储数据

我们先来聊一下 flags 这个成员变量。在 redis 中其实定义了 5 个 SDS结构体(其中 hisdshdr5 已经弃用)如图所示。他们之间的主要区别在于 len 和 alloc 的长度不同。

在 redis 中,为了尽可能地节省内存空间,当字符串长度在不同的区间时,会选择不同的结构体,例如:

  • 当字符串长度在 0~255 个字节之间时,会选择 hisdshdr8 ,这样一来,用于表示字符串字节数和申请的总字节数的空间就会被大大节省,以此类推。

例如,一个包含字符串“name”的 sds 结构如下:

SDS之所以叫做动态字符串,是因为它具备动态扩容的能力,例如一个内容为 “hello” 的 SDS,假如我们要给这个 SDS 追加一段字符串 ”world” ,这里首先会申请新内存空间:

  • 如果新字符串小于1M,则新空间为扩展后字符串长度的两倍+1
  • 如果新字符串大于1M,则新空间为扩展后字符串长度+1M+1。

这种机制称为内存预分配。内存预分配可以减少进行内存重新分配的开销,减少内存碎片,使得 redis 的性能得到提高,空间利用率也得到提高。

String 的三种编码方式

RAW

  • raw 是 string 的基本编码方式,基于简单动态字符串(SDS)实现,存储上限为512mb。当一个字符串采用 raw 的编码方式的时候,它的结构如图所示。

EMBSTR

  • 如果存储在 SDS 中的数据小于等于 44 字节,则会采用 EMBSTR 编码,此时 **RedisObject 与 SDS 是一段连续空间。而不是像 RAW 的编码方式一样,由 ptr 指向另外一片空间,**申请内存时只需要调用一次内存分配函数,效率更高。结构如下,

💡 为什么是 44 字节?Redis 默认的内存分配器 jemalloc 分配内存大小的单位是 $2^n$ ,因此,如果分配的空间大小为 2、4 、8 … 字节等 $2^n$ 字节,就不会产生内存碎片。

redisObjecthisdshdr8len alloc flags三个成员变量加起来刚刚好是 16 + 4 = 20 字节,如果 char[] (数据大小)的大小为 44 字节时,加起来刚刚好是 64 字节,也即 262^626 不会产生内存碎片。

  • RAW 和 EMBSTR 的编码演示
/* 44个v,采用embstr编码*/
本地虚拟机redis:0>set key vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
"OK"
本地虚拟机redis:0>object encoding key
"embstr"
/* 45个v,采用embstr编码*/
本地虚拟机redis:0>set key vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
"OK"
本地虚拟机redis:0>object encoding key
"raw"

INT

  • 如果存储的字符串是整数值,并且大小在 LONG MAX 范围内,则会采用 INT 编码
  • 直接将数据保存在 RedisObject 的 ptr 指针位置(刚好8字节),不再需要SDS了。

  • INT 编码演示
本地虚拟机redis:0>set key 12
"OK"
本地虚拟机redis:0>object encoding key
"int"

写在最后:在使用 string 类型时,尽可能让其长度小于 44 字节,或者使用整数表示,使其使用 EMBSTR 和 INT 编码

以上就是浅析Redis中String数据类型及其底层编码的详细内容,更多关于Redis Redis数据类型及编码的资料请关注脚本之家其它相关文章!

相关文章

  • Redis数据导入导出以及数据迁移的4种方法详解

    Redis数据导入导出以及数据迁移的4种方法详解

    这篇文章主要介绍了Redis数据导入导出以及数据迁移的4种方法详解,需要的朋友可以参考下
    2020-02-02
  • mac安装redis全过程

    mac安装redis全过程

    文章内容主要介绍了如何从官网下载指定版本的Redis,以及如何在自定义目录下安装和启动Redis,还提到了如何修改Redis的密码和配置文件,以及使用RedisInsight进行可视化管理,最后,总结了一些常用的Redis命令
    2025-01-01
  • 使用Spring Boot实现Redis键过期回调功能示例详解

    使用Spring Boot实现Redis键过期回调功能示例详解

    这篇文章主要介绍了使用Spring Boot实现Redis键过期回调功能,就是一个实现Redis键过期回调功能的Spring Boot应用的示例,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07
  • Redis数据库的使用场景介绍(避免误用Redis)

    Redis数据库的使用场景介绍(避免误用Redis)

    这篇文章主要介绍了Redis数据库的使用场景介绍(避免误用Redis),本文用简要的语言总结了Redis数据库的适应场合,人而避免错误的使用它而产生昂贵的维护代价,需要的朋友可以参考下
    2015-03-03
  • Redis报错:Could not create server TCP listening socket 127.0.0.1:6379: bind:解决方法

    Redis报错:Could not create server TCP 

    这篇文章主要介绍了Redis报错:Could not create server TCP listening socket 127.0.0.1:6379: bind:解决方法,是安装与启动Redis过程中比较常见的问题,需要的朋友可以参考下
    2023-06-06
  • Redisson 主从一致性问题详解

    Redisson 主从一致性问题详解

    这篇文章主要为大家介绍了Redisson 主从一致性问题详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Redis消息队列实现秒杀教程

    Redis消息队列实现秒杀教程

    这篇文章主要介绍了Redis消息队列实现秒杀教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04
  • Redis 的内存淘汰策略和过期删除策略的区别

    Redis 的内存淘汰策略和过期删除策略的区别

    这篇文章主要介绍了Redis 的内存淘汰策略和过期删除策略的区别,Redis 是可以对 key 设置过期时间的,因此需要有相应的机制将已过期的键值对删除,而做这个工作的就是过期键值删除策略
    2022-07-07
  • 浅析Redis如何保证数据不丢失

    浅析Redis如何保证数据不丢失

    Redis是一种Nosql类型的数据存储,全称Remote Dictionary Server,也就是远程字典服务器,本文主要来和大家讨论一下Redis如何保证数据不丢失,需要的可以参考下
    2024-02-02
  • 在Centos 8.0中安装Redis服务器的教程详解

    在Centos 8.0中安装Redis服务器的教程详解

    由于考虑到linux服务器的性能,所以经常需要把一些中间件安装在linux服务上,今天通过本文给大家介绍下在Centos 8.0中安装Redis服务器的详细过程,感兴趣的朋友一起看看吧
    2022-03-03

最新评论