暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

聊聊 Redis 基本数据类型 String

writeline 2021-04-19
1531

今天尝试使用了新的文章排版工具,看看效果怎么样。今天想来聊一聊 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";

我们从下面的表格来对比下双方的优缺点

CSDS
使用前要先分配内存,不够会有溢出风险自动扩缩容机制
长度变化引起内存再分配空间预分配机制,惰性空间释放
计算长度O(n)计算长度O(1)
\0 为结束标记不以 \0 为结束标记,使用 len 属性

可以发现,没有理由不使用SDS

4.使用场景

  • 缓存
  • 分布式 session
  • 分布式锁
  • 限流
  • 全局 ID
  • 计数器
  • 其他

本篇分享先到这里,欢迎关注。

如有收获,点个在看,诚挚感谢
文章转载自writeline,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论