❝今天尝试使用了新的文章排版工具,看看效果怎么样。今天想来聊一聊 Redis 里的基本数据类型,主要想聊下
❞String
及其应用场景。
Redis 现在应用非常广泛,而基于String
类型的应用来说,在我们日常开发中运用得是最多的。接下来我们就一起来学习下这个普通又不一样的String
。
1.String 可以存储哪些类型数据
int float String
首先呢,Redis 中的String
可以存储这么些个类型的数据。我们从它的 API 中也可以看到。API 使用很简单,我就不做太多的介绍了。从官网的介绍中我们能看到,String
是二进制安全,那为什么要特意提到二进制安全呢?接下来我们一起来看下,Redis 内部使用了什么样的数据结构来保证它是二进制安全的呢?
2.探究 String 数据结构
Redis 是个 KV 数据库,使用了 hashtable 实现,KV 的存储使用了dictEntry
这个结构
typeof struct dictEntry{
void *key;
union {
void *val;
unit64_t u64;
int64_t s64;
double d;
} v
struct dictEntry *next;
} dictEntry;
比如我们执行 Redis 命令
set hello world
(integer) 1
我们设置了一个 key hello,value world。结合上面的dictEntry
可以知道,key 的存储结构为String
,那 value 呢?并不是直接存储为String
,而是又进一步进行了抽象,统一包装成了redisObject
typeof struct redisObject{
unsigned type:4;
unsigned encoding:4;
....
void *ptr --指向实际的存储类型
} robj;
之所以要这么做,也是为了后续增加其他类型时比较好扩展。
我画了个简易的数据结构图。这样更能清楚的看到内部的存储结构。而redisObject
结构里有 type,encoding,通过官网的文档我们可以知道,Redis 存储String
会有三种不同的编码。分别为
raw embstr int
那我们刚加的 hello 是什么编码呢?通过下面的命令可以查到。
object encoding hello
"embstr"
它是一个embstr
编码。我在尝试追加一些内容
append hello !!!
(integer) 2
现在的 value 是hello world !!!
。在查看一下编码
object encoding hello
"raw"
可以发现,已经变成了raw
这是为什么呢?这其实是因为用embstr
编码的String
是只读的,所以当内容发生变化后会直接变更编码。那我再来试试int
编码。
set test1 1
(integer) 1
object encoding test1
"int"
append test1 a
(integer) 2
object encoding test1
"raw"
通过上面的一系列命令操作后,可以发现编码由int
编码变为raw
。这是因为int
编码存储8个字节的长整型,所以当内容不是整型时会变更编码。
那我们可以总结下三种编码
int 存储8个字节的长整型(long 2^63-1),超过最大长度或是追加的内容不为 int 编码会转为 raw embstr 小于44个字节,且是只读的,追加内容会直接转为 raw raw 大于44个字节
44个字节每个 Redis 版本不同不太一样。而区分不同编码,也是为了更加有效的节省内存开销。而我们文章中一直在说的String
,String
到底是什么玩意呢?
3. String 是啥
Redis 使用了一种名为SDS(Simple dynamic string)
的数据结构。那文章开头也有说到 Redis String
是二进制安全,是不是就是使用它来保证二进制安全的呢?我们先来看下SDS
的结构
struct __attribute__ ((__packed__)) sdshdr5{
unsigned char flags;
char buff[];
};
struct __attribute__ ((__packed__)) sdshdr8{
uint8_t len;
uint8_t alloc;
unsigned char flags;
char buff[];
};
struct __attribute__ ((__packed__)) sdshdr16{
uint16_t len;
uint16_t alloc;
unsigned char flags;
char buff[];
};
struct __attribute__ ((__packed__)) sdshdr32{
uint32_t len;
uint32_t alloc;
unsigned char flags;
char buff[];
};
struct __attribute__ ((__packed__)) sdshdr64{
uint64_t len;
uint64_t alloc;
unsigned char flags;
char buff[];
};
可以看到 Redis 内部定义了很多种类型,而这里的 hdr5,hdr8,hdr16 这些指的是可存储的字节大小,比如 hdr5 的意思是2的5次方,hdr8,hdr16 依次类推。但我们知道,字符串最大可存储 512MB 的内容大小。所以这个也可以看成是为未来预留的扩展。看下最终的存储结构图
这样就非常清晰了。可为什么 Redis 要自己实现SDS
呢?这里就要说下 C 语言里并没有String
的概念,比如我要声明一个字符串,只能这么干。
char a[] = "hello";
我们从下面的表格来对比下双方的优缺点
C | SDS |
---|---|
使用前要先分配内存,不够会有溢出风险 | 自动扩缩容机制 |
长度变化引起内存再分配 | 空间预分配机制,惰性空间释放 |
计算长度O(n) | 计算长度O(1) |
\0 为结束标记 | 不以 \0 为结束标记,使用 len 属性 |
可以发现,没有理由不使用SDS
。
4.使用场景
缓存 分布式 session 分布式锁 限流 全局 ID 计数器 其他
❝本篇分享先到这里,欢迎关注。
❞