文章目录

  • 一、redisObject
  • 二、数据类型(type)与编码(encoding)介绍
  • 字符串
  • 列表
  • 集合
  • 有序集合
  • 哈希表
  • 三、编码方式(encoding)的作用:二进制安全、节省内存
  • 四、类型与编码底层原理
  • 编码转换
  • 数据结构
  • 小结


Redis是一款开源的高性能key-value数据库,广泛应用于各种场景。在Redis中,**数据类型(type)和编码(encoding)**是非常重要的概念。

要查看Redis某个key的内部编码,可以使用Redis命令OBJECT ENCODING key

127.0.0.1:6379> object encoding k1  // 查看某个Redis键值的编码

一、redisObject

在 Redis 中,redisObject 是 Redis 中最基本的数据结构之一。key-value中key的数据结构也是如此

redisObject 的定义如下:

typedef struct redisObject {

    // 数据类型
    unsigned type:4;

    // 编码方式
    unsigned encoding:4;

    // 引用计数
    int refcount;

    // 指向实际值的指针
    void *ptr;

} robj;
  • type:表示 redisObject 的数据类型。
  • encoding:表示 redisObject 的编码方式。
  • refcount:表示当前 redisObject 被引用的次数。
  • ptr: ptr字段则是一个指针,指向实际的 Redis 对象。

Redis源码encoding取值有如下几种:

#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 */

二、数据类型(type)与编码(encoding)介绍

Redis支持五种主要的数据类型:字符串(string)、列表(list)、集合(set)、有序集合(sorted set)和哈希(hash)。每种数据类型都有对应的编码方式。

数据类型与编码方式总览如下:

数据类型(type)

编码方式(encoding)

字符串(string)

int、embstr、raw

哈希表(hash)

ziplist、hashtable

列表(list)

ziplist、linkedlist、quicklist

集合(set)

intset、hashtable

有序集合(zset)

ziplist、skiplist

字符串

字符串是Redis中最基本的数据类型,通常用于存储文本或二进制数据。Redis支持两种编码方式:

  • int:当字符串可以表示为整数时,Redis会将其转换为整数,并采用int编码方式存储。int编码方式的优点是存储空间小,操作效率高。缺点是只能存储整数,不支持字符串操作。
  • embstr(embstr-encoded string):保存长度小于44字节的字符串,当一个字符串比较短,采用此编码方式存储,可以减少内存占用。
  • raw(raw-encoded string):保存长度大于44字节的字符串,当一个字符串比较长时,采用此编码方式存储。

列表

列表是一系列有序的字符串集合,可以添加、修改和删除元素。Redis支持三种编码方式:

  • ziplist:在Redis3.2版本之前,当List列表中每个字符串的长度都小于64字节并且List列表中元素数量小于512个时,List对象使用ziplist编码,其他情况使用linkedlist编码。ziplist是一种紧凑的、压缩的列表结构,可以节省内存。适用于小型列表。
  • linkedlist:linkedlist是一种链表结构,支持任意大小的列表。但其内存占用会随着列表长度的增加而增加。
  • quicklist:Redis 3.2版本引入,quicklist是一种由多个ziplist组成的列表结构,既能保证性能,又能节省内存。适用于大型列表。

集合

集合是一系列无序的字符串集合,支持添加、删除和查询元素。Redis支持两种编码方式:

  • intset:当集合中的元素都是整数时,Redis会采用intset编码方式存储。intset编码方式的优点是存储空间小,操作效率高。
  • hashtable:当集合中的元素包含字符串时,Redis会采用hashtable编码方式存储。hashtable编码方式的优点是可以存储任意类型的元素,支持字符串操作。缺点是存储空间相对较大,操作效率相对较低。

有序集合

有序集合是一系列无序的字符串集合,每个元素关联一个分数,可以根据分数排序。Redis支持两种编码方式:

  • ziplist:保存的元素少于128个并且所有元素大小都小于64字节使用ziplist编码,ziplist是一种紧凑的、压缩的列表结构,适用于小型有序集合。
  • skiplist:skiplist是一种跳跃表结构,支持快速查询和排序。适用于大型有序集合。是一种牺牲空间换时间的数据结构。思想就是在列表中增加高度层,查找数据时从高往下找,就能找到目标数据。是一种类似平衡树的数据结构。(与平衡树的共同点:都是牺牲增删改查速度换查找速度)

哈希表

哈希表是一系列键值对集合,每个键关联一个值。Redis支持两种编码方式:

  • ziplist:哈希对象保存的所有键值的字符串长度小于64字节并且键值对数量小于512个,Redis会采用ziplist编码方式存储。ziplist编码方式的优点是存储空间小,操作效率高。缺点是不支持快速的键查找操作。
  • hashtable:除上述条件之外,Redis会采用hashtable编码方式存储。hashtable编码方式的优点是支持快速的键查找操作。缺点是存储空间相对较大,操作效率相对较低。

三、编码方式(encoding)的作用:二进制安全、节省内存

先看一组命令:

127.0.0.1:6379> set kl hello
OK
127.0.0.1:6379> STRLEN k1
(integer) 5
127.0.0.1:6379> set k2 9
0K
127.0.0.1:6379> 0BJECT encoding k2
"int"
127.0.0.1:6379> STRLEN k2
(integer) 1
127.0.0.1:6379> APPEND k2 999
(integer)4
127.0.0.1:6379> get k2
"9999"
127.0.0.1:6379> 0BJECT encoding k2
"raw"
127.0.0.1:6379> INCR k2
(integer) 10000
127.0.0.1:6379> 0BJECT encoding k2
"int"
127.0.0.1:6379> STRLEN k2
(integer) 5
127.0.0.1:6379> set k3 a
ok
127.0.0.1:6379> get k3
"a"
127.0.0.1:6379> STRLEN k3
1
127.0.0.1:6379> APPEND k3 中
(integer) 4
127.0.0.1:6379> get k3
"a\xe4\xb8\xad"
127.0.0.1:6379> STRLEN k3
(integer) 4

为每个键值对都确定一个编码方式,是为了可以正确的和外界进行交互。

当redis进程与外界进行交互时,redis返回给外界的是字节流,不是字符流。

底层存储的也是字节,不过在get时,会根据编码方式进行转换,呈现出来的就是我们能看懂的。

因为编码方式(encoding)确定,那就可以保证字节流转为字符时是正确的,只要双方客户端的编码方式一致,数据就不会被破坏。

并且我们在执行命令时,比如对一个list类型的key执行了incrby,则立刻返回错误,不需要去到value中校验value是否是int类型。

而且即使set key时,value是一个数值,比如:set k2 9999,它的长度就是4,可以说是一个字符就是一个长度。虽然它有一些数值操作,那是先在内存中转化成数值,再计算,最后再变更,这保证我们在get时,得到的是一个正确类型的数(字符串类型)

对于中文,一个中文是3个字节长度,这个关键在于客户端在和redis通信时,用的是什么编码,utf-8就是三个字符,gbk就是2个字节,再看一些例子

127.0.0.1:6379> FLUSHALL
127.0.0.1:6379> set k1 99999
ok
127.0.0.1:6379> STRLEN k1 
(integer) 5
127.0.0.1:6379> set k2 中
ok
127.0.0.1:6379> STRLEN k2   注意:这是utf-8
(integer) 3
127.0.0.1:6379> set k3 中    注意:切换编码gbk
ok
127.0.0.1:6379> STRLEN k3  
(integer) 2
127.0.0.1:6379> keys *
"k1"
"k2"
"k3"
127.0.0.1:6379> get k2
"xe4\xb8\xad"
127.0.0.1:6379> get k3
"xd6\xd0"
127.0.0.1:6379>exit
[root@basic ~]# redis-cli --raw   注意:这条命令就会将存储的字节转为我们能看懂的,此时是gbk
127.0.0.1:6379> get k3
中
127.0.0.1:6379> get k2
涓
127.0.0.1:6379> STRLEN k3
2
127.0.0.1:6379> STRLEN k2
3
127.0.0.1:6379> set k4 中
OK
127.0.0.1:6379> STRLEN k4
2
127.0.0.1:6379>

所以在用户端使用redis时,一定要沟通好数据的编码和解码方式

另外还有一个好处:节省内存,理论上所有string类型都可以用raw储存,但比较小的string(小于44字节)用embstr储存会更节省内存。

四、类型与编码底层原理

编码转换

Redis中的每个键值对都有一个类型标识,表示该键值对的数据类型。当我们对一个键进行操作时,Redis会根据该键当前的编码方式以及操作所需的编码方式,对键值对进行编码转换。

例如,当我们向一个字符串中追加内容时,如果该字符串当前的编码方式为raw,但是新的内容可以使用embstr编码方式存储,那么Redis会将该字符串的编码方式从raw转换为embstr。

数据结构

除了编码方式外,Redis还使用了许多经典的数据结构来实现各种数据类型。例如,Redis的列表和哈希表都是采用链表结构实现的。而有序集合则采用了跳跃表(Skip List)这种高效的数据结构。

这些数据结构都经过了精心设计和优化,以满足各种场景下的应用需求。例如,链表结构适合频繁地添加和删除元素,而跳跃表结构则适合排序和查找。

小结

Redis的数据类型和编码方式是为了在不同的场景下达到最佳的性能和内存占用。在使用Redis时,需要根据实际情况选择合适的数据类型和编码方式,以达到最佳的效果。同时,需要注意不同数据类型和编码方式的优缺点,以便在实际使用中做出合理的选择。