一、string类型

  1. SDS(Simple Dynamic Strings, 简单动态字符串)是 Redis 的一种基本数据结构,主要是用于存储字符串和整数。
  2. 老的sds结构:
  3. stringRedisTemplate 存储时间单位 string的存储结构_Redis


  4. 整个sds结构体由三部分组成,其中:buf 表示数据空间,用于存储字符串;len 表示 buf 中已占用的字节数,也即字符串长度;free 表示buf 中剩余可用字节数。

好处:
- 用单独的变量 len 和 free,可以方便地获取字符串长度和剩余空间;
- 内容存储在动态数组 buf 中,SDS 对上层暴露的指针指向 buf,而不是指向结构体 SDS。因此,上层可以像读取 C 字符 串一样读取 SDS 的内容,兼容 C 语言处理字符串的各种函数,同时也能通过 buf 地址的偏移,方便地获取其他变量;
- 读写字符串不依赖于 \0,保证二进制安全。

坏处:
- 对于不同长度的字符串,没有必要使用 len 和 free 这 2 个 4 字节的变量
- 4 字节的 len,可表示的字符串长度为 2^32,而在实际应用中,存放于 Redis 中的字符串往往没有这么长,因此,空间的使用上能否进一步压缩

  1. 新的sds结构
    新sds结构图

    增加了一个 flags 字段来标识类型,用一个字节(8 位)来存储。
    其中:前 3 位表示字符串的类型;剩余 5 位,可以用来存储长度小于 32 的短字符串。
    而对于长度大于 31 的字符串,仅仅靠 flags 的后 5 位来存储长度明显是不够的,需要用另外的变量来存储。sdshdr8、sdshdr16、sdshdr32、sdshdr64 的数据结构定义如下,其中 :
  • len 表示已使用的长度
  • alloc 表示总长度
  • buf 存储实际内容
  • flags 的前 3 位依然存储类型,后 5 位则预留
struct __attribute__ ((__packed__)) sdshdr8 {
  uint8_t len; /* 已使用长度,1字节 */
uint8_t alloc; /* 总长度,1字节 */
unsigned char flags; /* 前3位存储类型,后5位预留 */
char buf[];};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* 已使用长度,2字节 */
uint16_t alloc; /* 总长度,2字节 */
unsigned char flags; /* 前3位存储类型,后5位预留 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* 已使用长度,4字节 */
uint32_t alloc; /* 总长度,4字节 */
unsigned char flags; /* 前3位存储类型,后5位预留 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* 已使用长度,8字节 */
    uint64_t alloc; /* 总长度,8字节 */
    unsigned char flags; /* 前3位存储类型,后5位预留 */
    char buf[];
};

Redis创建字符串流程

sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    // 根据字符串长度计算相应类型
    char type = sdsReqType(initlen);
    // 如果创建的是""字符串,强转为SDS_TYPE_8
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    // 根据类型计算头部所需长度(头部包含 len、alloc、flags)
    int hdrlen = sdsHdrSize(type);
    // 指向flags的指针
    unsigned char *fp;
    // 创建字符串,+1是因为 `\0` 结束符
    sh = s_malloc(hdrlen+initlen+1);
    if (sh == NULL) return NULL;
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        memset(sh, 0, hdrlen+initlen+1);
    // s指向buf
      s = (char*)sh+hdrlen;
      // s减1得到flags
    fp = ((unsigned char*)s)-1;
    ...
    // 在s末尾添加\0结束符
    s[initlen] = '\0';
    // 返回指向buf的指针s
    return s;
      }

创建 SDS 的大致流程是这样的:首先根据字符串长度计算得到 type,根据 type 计算头部所需长度,然后动态分配内存空间。

注意:

  1. 创建空字符串时,SDS_TYPE_5 被强制转换为 SDS_TYPE_8(原因是创建空字符串后,内容可能会频繁更新而引发扩容操作,故直接创建为 sdshdr8)
  2. 长度计算有 +1 操作,因为结束符 \0 会占用一个长度的空间。
  3. 返回的是指向 buf 的指针 s。

二、string数据类型的应用

  1. session共享
    在一个分布式web服务中,使用Redis将用户的Session进行集中管理。这样就只需要保证redis的高可用以及扩展性,每次用户的登录或者查询登录都从Redis中获取Session信息。
  2. 计数器
    string类型的incr、decr命令:将key中存储的数字进行自增、自减
    因此可以用于记录文章的点赞、评论、分享数。商品的收藏数、销售量、评价数等
  3. 限流
    利用key的有效期,可以限制用户对某个接口的访问频率进行限制,如短信验证接口等。