zoukankan      html  css  js  c++  java
  • redis源码之SDS

    1:SDS介绍

    我们在redis中执行命令

    set key name
    

    的时候,key和name都是字符串类型,而且字符串(string)在redis中是会经常用到的类型,那redis是如何保存字符串的呢?我们接下来往下看
    众所周知,redis是c写的,在c中使用char来保存字符串,并且用作为字符串的结尾,但是redis不是这样保存的,redis是使用一种叫SDS的结构来保存字符串的。结构如下(redis3.2以前)

     struct sdshdr{
       int len;
       int free;
       char buf[];
    }
    

    那么问题来了,redis为什么 会用SDS的结构,而不直接用c语言的字符串,我们来看看他们的区别

    1:计算字符串长度的区别

    对于c来说,计算字符串的长度的方式就是遍历,遇到就停止,所以复杂对是O(n),而SDS直接保存了字符串的长度,复杂度是O(1)

    2:保证二进制的安全

    因为SDS并不是以为结尾的标志,自然就保证了二进制的安全

    3:内存管理策略(预分配内存和惰性空间释放策略)

    redis是一个高速的缓存数据库,需要频繁的对字符串进行操作,如果内存分配错误,会导致很严重的后果,就算内存分配没问题,频繁的内存分配也是非常耗费时间的,所以这些都是应该去避免的

    惰性空间释放策略

    在SDS中首先用到了惰性空间释放策略,惰性空间释放用于优化SDS的字符串缩短操作。
    当要缩短SDS保存的字符串时,程序并不立即使用内存充分配来回收缩短后多出来的字节,而是使用表头的free成员将这些字节记录起来,并等待将来使用。
    源码如下

    void sdsclear(sds s) {  //重置sds的buf空间,懒惰释放
        struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
        sh->free += sh->len;    //表头free成员+已使用空间的长度len = 新的free
        sh->len = 0;            //已使用空间变为0
        sh->buf[0] = '';         //字符串置空
    }
    
    预分配内存

    扩容策略是字符串在长度小于 1M 之前,扩容空间采用加倍策略,也就是保留 100% 的冗余空间。当长度超过1M 之后,为了避免加倍后的冗余空间过大而导致浪费,每次扩容只会多分配 1M大小的冗余空间。

    4:兼容c语言函数库 (字符串后面会自动加上)

    3.2版本以后的SDS结构

    前面的len和free以及char这种结构看起来很好,但是是存在一定的问题的

     struct sdshdr{
       int len;
       int free;
       char buf[];
    }
    

    len和free都是int类型,都是4byte也就是32bit,能表示42亿左右的范围,大大的造成了空间的浪费,所以在3.2以后对SDS有一定的更改,更改如下

    typedef char *sds;
    
    /* Note: sdshdr5 is never used, we just access the flags byte directly.
     * However is here to document the layout of type 5 SDS strings. */
    struct __attribute__ ((__packed__)) sdshdr5 {
        unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
        char buf[];
    };
    struct __attribute__ ((__packed__)) sdshdr8 {
        uint8_t len; /* used */
        uint8_t alloc; /* excluding the header and null terminator */
        unsigned char flags; /* 3 lsb of type, 5 unused bits */
        char buf[];
    };
    struct __attribute__ ((__packed__)) sdshdr16 {
        uint16_t len; /* used */
        uint16_t alloc; /* excluding the header and null terminator */
        unsigned char flags; /* 3 lsb of type, 5 unused bits */
        char buf[];
    };
    .........
    

    sdshdr5表示的是用5个bit位来表示数据的长度,sdshdr8就是表示用8个bit位来表示数据的长度,以此类推
    sdshdr5的内存分配如图
    sdshdr5.png
    当需要存储的数据长度超过31,就需要用sdshdr8来表示
    sdshdr8的内存分配如图
    sdshdr8.png

    其余的sdshdr16以上的都是以此类推,判断方式源码如下

    static inline char sdsReqType(size_t string_size) {
        if (string_size < 1<<5) //2^5-1
            return SDS_TYPE_5;
        if (string_size < 1<<8) //2^8-1
            return SDS_TYPE_8;
        if (string_size < 1<<16) //2^16-1
            return SDS_TYPE_16;
    #if (LONG_MAX == LLONG_MAX)
        if (string_size < 1ll<<32) //2^32-1
            return SDS_TYPE_32;
        return SDS_TYPE_64;
    #else
        return SDS_TYPE_32;
    #endif
    }
    

    关注我的技术公众号,每周都有优质技术文章推送。
    微信扫一扫下方二维码即可关注:
    file

  • 相关阅读:
    Python中的类(上)
    Django REST Framework API Guide 07
    Django REST Framework API Guide 06
    Django REST Framework API Guide 05
    Django REST Framework API Guide 04
    Django REST Framework API Guide 03
    Django REST Framework API Guide 02
    Django REST Framework API Guide 01
    Django 详解 信号Signal
    Django 详解 中间件Middleware
  • 原文地址:https://www.cnblogs.com/xuehao/p/13816651.html
Copyright © 2011-2022 走看看