SDS(Simple Dynamic String)

即简单动态字符串,是redis自定义的数据结构。

首先介绍一下几个基本概念:

柔性数组:在结构体中最后一个成员允许是未知大小的数组,这就叫做柔性数组成员

二进制安全:C语言中用"\0"表示字符串的结束,如果字符串中本身就有"\0"这个字符,字符串就会被截断,这是非二进制安全。反过来,如果通过某种机制保证读写字符串是不会使其内容被截断或损坏,就称为二进制安全。

特性:

  1. 柔性数组成员前面必须至少有一个其他成员。
  2. sizeof返回的结构体内存大小不包括柔性数组成员的大小
  3. 包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

3.2版本之前的SDS:


redis6存储结构体 redis的存储结构有哪些_字符串

redis为了方便获取字符串的相关信息,SDS中除了存放数据之外,还增加了两个字段。

  • len:记录字符串的长度
  • free:柔性数组成员中剩余可用字节
  • buf:柔性数组成员

这样设计有以下优点:

  1. 设计了单独的统计数据,可以很容易的就得到字符串的长度和柔性数组成员的剩余空间大小
  2. 内容存放在柔性数组中,SDS对上层暴露的指针不是指向结构体SDS的指针,而是直接指向柔性数组buf的指针,上层可以像读取字符串一样读取SDS的内容。
  3. 读写字符串的时候,可以根据len字段记录的长度来判断字符串的结束位置,不用依赖于“/0”字符串终止字符,实现了二进制安全。

但是也存在一些问题:对于不同长度的字符串,使用固定长度的头部(len、free字段)明显有些浪费,存储效率不高,于是在3.2版本之后,对SDS结构进行了改进.

3.2版本之后的SDS结构


redis6存储结构体 redis的存储结构有哪些_数据库_02

  • len:依然表示buf已经占用的字节数
  • alloc:柔性数组buf已经分配的字节数
  • flags:表示柔性数组的类型
#define SDS_TYPE_5  0  
    #define SDS_TYPE_8  1
    #define SDS_TYPE_16 2
    #define SDS_TYPE_32 3
    #define SDS_TYPE_64 4
  • buf:柔性数组

改进后的SDS结构的最大区别在于记录SDS的头部信息(主要是len和alloc的长度不同),可以根据flags来判断当前SDS的类型,从而得到当前SDS头部信息的长度,进而正确读取其中的内容

不同类型的SDS的头部长度如下:

Flags

len长度(字节)

alloc长度(字节)

ssdshdr5

0

0

ssdshdr8

1

1

ssdshdr16

2

2

ssdshdr32

4

4

ssdshdr64

8

8

对于长度小于32位的字符串,redis设计的数据结构中为了节省存储空间,并没有设置len和alloc字段.


redis6存储结构体 redis的存储结构有哪些_redis6存储结构体_03

而是通过划分flags字段来存储长度信息。flags字段的划分如下所示:


redis6存储结构体 redis的存储结构有哪些_数据库_04

由于一共有5种类型的结构,所以至少要3位来区分(2^ 3=8>5),而剩下的5位(2^5=32)用来表示字符串的长度。

对于其他类型的SDS,flags采用了同样的划分方式:使用前3位来表示结构体类型,但是由于后5位已经不足以表示字符串的长度,故使用了额外的字段来存放柔性数组的长度和分配情况,flags字段的后5位就暂时保留了下来。

String的基本操作

给key的value追加字符串append key value

127.0.0.1:6379> set key1 v1
OK
127.0.0.1:6379> append
(error) ERR wrong number of arguments for 'append' command
127.0.0.1:6379> append key1 "hello"
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379>

查看当前key的字符串长度:strlen

127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> strlen key1
(integer) 12

整数自增:incr

127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views
(integer) 1

整数自减:decr

127.0.0.1:6379> set views 1
OK
127.0.0.1:6379> decr views
(integer) 0

以步长k自增:incrby key k

127.0.0.1:6379> set views -1
OK
127.0.0.1:6379> incrby views 10
(integer) 9

以步长k自减:decrby key k

127.0.0.1:6379> decrby views 5
(integer) 4

获取字符串某一区间的值 getrange ley start end

对应于java中的substring函数

127.0.0.1:6379> set string "123456789"
OK
127.0.0.1:6379> get string
"123456789"
127.0.0.1:6379> getrange string 1 5
"23456"
//获取整个字符串
127.0.0.1:6379> getrange string 0 -1
"123456789"

替换字符串某个区间的值:setrange key offset value

127.0.0.1:6379> get string
"123456789"
127.0.0.1:6379> setrange string 2 kkkkkk
(integer) 9
127.0.0.1:6379> get string
"12kkkkkk9"

设置过期时间:setex key time value

即set with expire

127.0.0.1:6379> setex key3 30 hello
OK
127.0.0.1:6379> ttl key3
(integer) 18
127.0.0.1:6379> ttl key3
(integer) 15

不存在再设置:setnx key value

即set if not exists,在分布式锁中会常常使用(乐观锁)

127.0.0.1:6379> set name lingg
OK
127.0.0.1:6379> get name 
"lingg"
127.0.0.1:6379> setnx name linmm
(integer) 0
127.0.0.1:6379> get name
"lingg"
127.0.0.1:6379>

先获取再设置:getset

127.0.0.1:6379> get ff
(nil)
127.0.0.1:6379> getset ff 123
(nil)
127.0.0.1:6379> get ff
"123"

批量操作:原子性操作,一个失败,都失败

批量设置键值对:mset k1 v1 k2 v2 k3 v3

127.0.0.1:6379> mset k1 v1 k2 v1 k3 v1
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "k3"

如果不存在则批量设置键值对: msetnx k1 v1 k2 v2 k3 v3

127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> msetnx k4 sss k1 sss  k2 sss  k3 ssss k4 sss
(integer) 0
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "k3"

对象:

set user:1{name:zhangsan,age:3} #设置一个对象
#这里利用到key的一个巧妙设计
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 18
OK
127.0.0.1:6379> mget user:1
1) (nil)
127.0.0.1:6379> mget user:1:username user:1:age
1) (nil)
2) "18"
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "18"

key的基本操作

查看当前数据库中所有key

127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set name lingg
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"

查看key是否存在:exists

127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists name1
(integer) 0

删除key:move key value

127.0.0.1:6379> move name 1
(integer) 1
127.0.0.1:6379> keys *
1) "age"

设置key的过期时间:expire

127.0.0.1:6379> set name lingg
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> get name
"lingg"
//设置key的过期时间
127.0.0.1:6379> expire name 10
(integer) 1

查看当前key的过期时间:ttl

//查看key的生命周期(倒计时)
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)

查看当前key对应的值的类型:type

127.0.0.1:6379> keys *
1) "age"
//查看key的类型
127.0.0.1:6379> type age
string

参考文献:Redis5:设计与源码分析 -陈雷等