Jadual Kandungan
sdsnewlen:

Redis源码解析

Mar 22, 2018 am 09:56 AM
redis Kod sumber menghuraikan

Redis系统当中,针对字符串进行的更加完善的封装,建立了一个动态字符串,并构建了大量的实用api。相关的实现代码为sds.h及sds.c,以下为我的源码阅读笔记。内容较多,逐步更新

  1. typedef char *sds;  
      
    struct __attribute__ ((__packed__)) sdshdr5 {  
        usigned char flags;  
        char buf[];  
    };  
    struct __attribute__ ((__packed__)) sdshdr8 {  
        uint8_t len;  
        uint8_t alloc;  
        unsigned char flags;  
        char buf[];  
    };  
    struct __attribute__ ((__packed__)) sdshdr16 {  
        uint16_t len;  
        uint16_t alloc;  
        unsigned char flags;  
        char buf[];  
    };  
    struct __attribute__ ((__packed__)) sdshdr32 {  
        uint32_t len;  
        uint32_t alloc;  
        unsigned char flags;  
        char buf[];  
    };  
    struct __attribute__ ((__packed__)) sdshdr64 {  
        uint64_t len;  
        uint64_t alloc;  
        unsigned char flags;  
        char buf[];  
    };  
      
    #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  
    #define SDS_TYPE_MASK 7  
    #define SDS_TYPE_BITS 3  
    #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));  
    #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))  
    #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
    Salin selepas log masuk

以上是动态字符串的结构体声明及define声明的函数。动态字符串一共有5种类型,分别为不同长度的字符串所实用。我在这里称之为动态字符串的头部。

sdshdr5:长度为小于32的字符串

sdshdr8:长度为小于256的字符串

sdshdr16:长度为小于2^16的字符串

sdshdr32:长度为小于2^32的字符串。这里有一点注意,若是机器的LONG_MAXbu不等于LLONG_MAX,则返回sdshdr64类型。

sdshdr64:其他所有长度都实用此类。

sdshdr5这个类型不同于其他类型,它缺少len成员与alloc成员,它的判断与处理都比较特别。但是官方在代码有过一段注释,如下:

/* Note: sdshdr5 is never used, we just access the flags byte directly.

* However is here to document the layout of type 5 SDS strings. */

注释中说明,这个类型从未被使用,所以我们在这里姑且不考虑它,而实际上,它的处理操作本质上与其他类型并没有什么区别。方便起见,我们以通用的类型进行研究。

结构体当中,有四个类,分别为len、alloc、flags与buf。

len:字符串的长度。

alloc:字符串内存总大小,注意,alloc不同于len。len是实际字符串的长度,而alloc,则是实际分配的内存大小(不包含sds头与结尾的'\0'的大小)。为了减少字符串内容增加时反复的重新申请内存,redis当中会申请更多内存以备使用。当字符串大小小于1MB的时候,申请两倍大小的内存使用,当字符串大小大于等于1MB的时候,多申请1MB的内存以备使用。详细的设定可以看之后的sdsMakeRoomFor函数的解析。

flags:作为区分不同类型的标记使用,暂时只使用了低3位来标记,高5位暂未使用,也供以后增加新功能时使用。上面代码中define声明的SDS_TYPE_*类型为相应的标记内容,用于区分不同的字符串类型。比如flags等于SDS_TYPE_8时,则可以从字符串开始字节在向前17字节,或者字符串头部开始获取8字节数据获取当前字符串的实际长度。等于SDS_TYPE_16时,从字符串开始字节向前33字节,或者字符串头部开始获取16自己获取当前字符串的实际长度。flags与头部信息的配合使用,将在之后的函数解析里面大量出现。

buf:实际存储字符串内容的数组,同传统数组一样,结尾需要'\0'字符。

在sdshdr的声明当中,我们可以看到 __attribute__ ((__packed__)) 关键字,这将使这个结构体在内存中不再遵守字符串对齐规则,而是以内存紧凑的方式排列。所以可以从字符串位置开始向前一个字节便可以获取flags信息了,比如buf[-1]。具体__attribute__ ((__packed__))与字符串对齐的内容请查看另一篇博客。

SDS_HDR_VAR函数则通过结构体类型与字符串开始字节,获取到动态字符串头部的开始位置,并赋值给sh指针。SDS_HDR函数则通过类型与字符串开始字节,返回动态字符串头部的指针。使用方式为可在之后的代码当中看到,具体define声明中的双'#'号的使用方式与意义,请看草另一篇博客。

sds比起传统的字符串,有着自己优势跟便利之处。

1、内存预分配:sds通过预先分配了更多的内存量,来避免频繁的申请、分配内存,无端的浪费了性能

2、惰性释放:sds作为普通字符串使用之时,可以通过在不同字节打上'\0'字符,来代表字符串的截断及减少长度,而不是需要清空多余的字节并释放它们,那些内存再之后的操作当中可以当做空闲内存继续使用。

3、二进制安全:作为非字符串使用存储数据之时,通过善用头部的len属性,可以存储一些包含'\0'字符的数据。当然,一定要善用len属性,api当中,如长度更新的函数,同样通过'\0'字符来判断结尾!

接下来开始介绍sds相关的api函数,第一批是声明、定义在sds.h文件内的静态函数,这些函数都是针对动态字符串头部的属性的获取与修改,简单易懂

  1. //获取动态字符串长度  
    static  inline  size_t  sdslen(const sds s) {  
        unsigned char flags = s[-1];//获取头部信息中的flags属性,因内存紧密相连,可以直接通过这种方式获取  
        switch(flags&SDS_TASK_MASK) {//获取类型,SDS_TASK_MASK为7,所以flags&SDS_TASK_MASK等于flags  
            case SDS_TYPE_5:  
                return SDS_TYPE_5_LEN(flags);//SDS_TYPE_5类型的长度获取稍微不同,它的长度被定义在flags的高5位当中,具体可查看之后的sdsnewlen函数,或者下面的sdssetlen函数  
            case SDS_TYPE_8:  
                return SDS_HDR(8,s)->len;//SDS_HDR函数通过类型与字符串开始字节获取头部,以此获取字符串的长度  
            case SDS_TYPE_16:  
                return SDS_HDR(16,s)->len;  
            case SDS_TYPE_32:  
                return SDS_HDR(32,s)->len;  
            case SDS_TYPE_64:  
                RETURN SDS_HDR(64,S)->len;  
        }  
        return 0;  
    }  
      
    //获取动态字符串的剩余内存  
    static inline size_t sdsavail(const sds s) {  
        unsigned char flags = s[-1];//获取flags  
        switch(flags&SDS_TYPE_MASK) {  
            case SDS_TYPE_5://SDS_TYPE_5直接返回0,  
                return 0;  
            case SDS_TYPE_8: {  
                SDS_HDR_VAR(8,s);//通过SDS_HDR_VAR函数,将头部指针放置在sh变量  
                return sh->alloc - sh->len;//总内存大小 - 字符串长度,获取可用内存大小  
            }  
            case SDS_TYPE_16: {  
                SDS_HDR_VAR(16,s);  
                return sh->alloc - sh->len;  
            }  
            case SDS_TYPE_32: {  
                SDS_HDR_VAR(32,s);  
                return sh->alloc - sh->len;  
            }  
            case SDS_TYPE_64: {  
                SDS_HDR_VAR(64,s);  
                return sh->alloc - sh-len;  
            }  
        }  
        return 0;  
    }  
      
    //重置字符串长度  
    static inline void sdssetlen(sds s, size_t newlen) {  
        unsigned char flags = s[-1];//获取flags  
        switch(flags&SDS_TASK_MASK) {  
            //SDS_TYPE_5的长度设置较为特殊,长度信息写在flags的高5位  
            case SDS_TYPE_5:  
                {  
                    unsigned char *fp = ((unsigned char*)s)-1;  
                    *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);  
                }  
                break;  
            //其他类型则是统一修改len属性的值  
            case SDS_TYPE_8:  
                SDS_HDR(8,s)->len = newlen;  
                break;  
            case SDS_TYPE_16:  
                SDS_HDR(16,s)->len = newlen;  
                break;  
            case SDS_TYPE_32:  
                SDS_HDR(32,s)->len = newlen;  
                break;  
            case SDS_TYPE_64:  
                SDS_HDR(64,s)->len = newlen;  
                break;  
        }  
    }  
      
    //按照指定数值,增加字符串长度  
    static inline void sdsinclen(sds s, size_t inc) {  
        unsigned char flags = s[-1];//获取flags  
        switch(flags&SDS_TYPE_MASK) {  
            //SDS_TYPE_5类型使用上面的函数,获取长度、更新、设置  
            case SDS_TYPE_5:  
                {  
                    unsigned char *fp = ((unsigned char*)s)-1;  
                    unsigned char newlen = SDS_TYPE_LEN(flags)+inc;  
                    *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);  
                }  
                break;  
            //其他类型则直接通过SDS_HDR函数,更新len值  
            case SDS_TYPE_8:  
                SDS_HDR(8,s)->len += inc;  
                break;  
            case SDS_TYPE_16:  
                SDS_HDR(16,s)->len += inc;  
                break;  
            case SDS_TYPE_32:  
                SDS_HDR(32,s)->len += inc;  
                break;  
            case SDS_TYPE_64:  
                SDS_HDR(64,s)->len += inc;  
                break;  
        }  
    }  
      
    //获取动态字符串的总内存  
    static inline size_t sdsalloc(const sds s) {  
        unsigned char flags = s[-1];//获取flags  
        switch(flags&SDS_TASK_MASK) {  
            //SDS_TYPE_5直接通过SDS_TYPE_5_LEN函数返回  
            case SDS_TYPE_5:  
                return SDS_TYPE_5_LEN(flags);  
            //其他类型则返回头部信息中的alloc属性  
            case SDS_TYPE_8:  
                return SDS_HDR(8,s)->alloc;  
            case SDS_TYPE_16:  
                return SDS_HDR(16,s)->alloc;  
            case SDS_TYPE_32:  
                return SDS_HDR(32,s)->alloc;  
            case SDS_TYPE_64:  
                return SDS_HDR(64,s)->alloc;  
        }  
        return 0;  
    }  
      
    //重置字符串内存大小  
    static inline size_t sdssetalloc(sds s, size_t newlen) {  
        unsigned cahr flags = s[-1];//获取flags  
        switch(flags&SDS_TASK_MASK) {  
            case SDS_TYPE_5:  
                //官方注释,SDS_TYPE_5不做任何操作  
                /*Nothing to do, this type has no total allocation info. */  
                break;  
            //其他类型直接修改头部信息中的alloc属性  
            case SDS_TYPE_8:  
                SDS_HDR(8,s)->alloc = newlen;  
                break;  
            case SDS_TYPE_16:  
                SDS_HDR(16,s)->alloc = newlen;  
                break;  
            case SDS_TYPE_32:  
                SDS_HDR(32,s)->alloc = newlen;  
                break;  
            case SDS_TYPE_64:  
                SDS_HDR(64,s)->alloc = newlen;  
                break;  
        }  
    }
    Salin selepas log masuk

上述的几个函数,sdslen,sdsavail,sdssetlen,sdsinclen,sdsalloc,sdssetalloc函数,都是基本的头部属性操作函数。代码的难度也不大,可以直观的阅读、理解。

接下来的sds的相关api,数量有点多,之后的dict、zskiplist也是有大量api,挑部分代码较多,需要逐行理解的函数来记录、分析。

sdsnewlen:

  1. //创建一个新的sds对象  
    sds sdsnewlen(const void *init, size_t initlen) {  
        void *sh;  
        sds s;  
        char type = sdsReqType(initlen);//根据初始化长度获取对应结构体类型  
        if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;//若长度为0的则初始化为SDS_TYPE_8类型  
        int hdrlen = sdsHdrSize(type);  
        unsigned char *fp;  
          
        sh = s_malloc(hdrlen+initlen+1);//申请足够的内存,头部大小+初始化大小+结尾符  
        if (!init)  
            memset(sh, 0, hdrlen+initlen+1);  
        if (sh == NULL) return NULL;  
        s = (char*)sh+hdrlen;//获取字符串起始字节  
        fp = ((unsigned char*)s)-1;//获取flags字节  
        switch(type) {  
            //SDS_TYPE_5又是特立独行了,有自己的初始化方案,我都懒得说明了。。。  
            case SDS_TYPE_5: {  
                *fp = type | (initlen << SDS_TYPE_BITS);  
                break;  
            }  
            //其他类型通过SDS_HDR_VAR函数获取头部信息,并逐步初始化  
            case SDS_TYPE_8: {  
                SDS_HDR_VAR(8,s);  
                sh->len = initlen;  
                sh->alloc = initlen;  
                *fp = type;  
                break;  
            }  
            case SDS_TYPE_16: {  
                SDS_HDR_VAR(16,s);  
                sh->len = initlen;  
                sh->alloc = initlen;  
                *fp = type;  
                break;  
            }  
            case SDS_TYPE_32: {  
                SDS_HDR_VAR(32,s);  
                sh->len = initlen;  
                sh->alloc = initlen;  
                *fp = type;  
                break;  
            }  
            case SDS_TYPE_64: {  
                SDS_HDR_VAR(64,s);  
                sh->len = initlen;  
                sh->alloc = initlen;  
                *fp = type;  
                break;  
            }  
        }  
        //根据init与initlen,将内容复制给字符串  
        if (initlen && init)  
            memcpy(s, init, initlen);  
        //打上结尾符  
        s[initlen] = &#39;\0&#39;;  
        return s;  
    }
    Salin selepas log masuk

sdsnewlen根据参数给予的init字符串与initlen初始长度,生成并返回一个动态字符串。代码都打上了注释,阅读已经没什么难度了。

相关推荐:

Redis源码解析1

Redis源码解析2

Redis源码解析3

Atas ialah kandungan terperinci Redis源码解析. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

AI Hentai Generator

AI Hentai Generator

Menjana ai hentai secara percuma.

Artikel Panas

R.E.P.O. Kristal tenaga dijelaskan dan apa yang mereka lakukan (kristal kuning)
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Tetapan grafik terbaik
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Cara Memperbaiki Audio Jika anda tidak dapat mendengar sesiapa
3 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: Cara Membuka Segala -galanya Di Myrise
4 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Cara Membina Mod Kluster Redis Cara Membina Mod Kluster Redis Apr 10, 2025 pm 10:15 PM

Mod Redis cluster menyebarkan contoh Redis ke pelbagai pelayan melalui sharding, meningkatkan skalabilitas dan ketersediaan. Langkah -langkah pembinaan adalah seperti berikut: Buat contoh Redis ganjil dengan pelabuhan yang berbeza; Buat 3 contoh sentinel, memantau contoh redis dan failover; Konfigurasi fail konfigurasi sentinel, tambahkan pemantauan maklumat contoh dan tetapan failover; Konfigurasi fail konfigurasi contoh Redis, aktifkan mod kluster dan tentukan laluan fail maklumat kluster; Buat fail nodes.conf, yang mengandungi maklumat setiap contoh Redis; Mulakan kluster, laksanakan perintah Buat untuk membuat kluster dan tentukan bilangan replika; Log masuk ke kluster untuk melaksanakan perintah maklumat kluster untuk mengesahkan status kluster; buat

Cara melaksanakan redis yang mendasari Cara melaksanakan redis yang mendasari Apr 10, 2025 pm 07:21 PM

Redis menggunakan jadual hash untuk menyimpan data dan menyokong struktur data seperti rentetan, senarai, jadual hash, koleksi dan koleksi yang diperintahkan. Redis berterusan data melalui snapshots (RDB) dan menambah mekanisme tulis sahaja (AOF). Redis menggunakan replikasi master-hamba untuk meningkatkan ketersediaan data. Redis menggunakan gelung acara tunggal untuk mengendalikan sambungan dan arahan untuk memastikan atom dan konsistensi data. Redis menetapkan masa tamat tempoh untuk kunci dan menggunakan mekanisme memadam malas untuk memadamkan kunci tamat tempoh.

Apa yang perlu dilakukan sekiranya pelayan redis tidak dapat dijumpai Apa yang perlu dilakukan sekiranya pelayan redis tidak dapat dijumpai Apr 10, 2025 pm 06:54 PM

Langkah-langkah untuk menyelesaikan masalah yang tidak dapat dijumpai oleh Redis-Server: periksa pemasangan untuk memastikan Redis dipasang dengan betul; Tetapkan pembolehubah persekitaran redis_host dan redis_port; Mulakan Redis Server Redis-server; Semak sama ada pelayan sedang menjalankan ping redis-cli.

Cara Melihat Semua Kekunci di Redis Cara Melihat Semua Kekunci di Redis Apr 10, 2025 pm 07:15 PM

Untuk melihat semua kunci di Redis, terdapat tiga cara: Gunakan perintah kunci untuk mengembalikan semua kunci yang sepadan dengan corak yang ditentukan; Gunakan perintah imbasan untuk melangkah ke atas kunci dan kembalikan satu set kunci; Gunakan arahan maklumat untuk mendapatkan jumlah kunci.

Cara membaca kod sumber redis Cara membaca kod sumber redis Apr 10, 2025 pm 08:27 PM

Cara terbaik untuk memahami kod sumber REDIS adalah dengan langkah demi langkah: Dapatkan akrab dengan asas -asas Redis. Pilih modul atau fungsi tertentu sebagai titik permulaan. Mulakan dengan titik masuk modul atau fungsi dan lihat baris kod mengikut baris. Lihat kod melalui rantaian panggilan fungsi. Berhati -hati dengan struktur data asas yang digunakan oleh REDIS. Kenal pasti algoritma yang digunakan oleh Redis.

Cara menggunakan perintah redis Cara menggunakan perintah redis Apr 10, 2025 pm 08:45 PM

Menggunakan Arahan Redis memerlukan langkah -langkah berikut: Buka klien Redis. Masukkan arahan (nilai kunci kata kerja). Menyediakan parameter yang diperlukan (berbeza dari arahan ke arahan). Tekan Enter untuk melaksanakan arahan. Redis mengembalikan tindak balas yang menunjukkan hasil operasi (biasanya OK atau -r).

Cara Membaca Gilir Redis Cara Membaca Gilir Redis Apr 10, 2025 pm 10:12 PM

Untuk membaca giliran dari Redis, anda perlu mendapatkan nama giliran, membaca unsur -unsur menggunakan arahan LPOP, dan memproses barisan kosong. Langkah-langkah khusus adalah seperti berikut: Dapatkan nama giliran: Namakannya dengan awalan "giliran:" seperti "giliran: my-queue". Gunakan arahan LPOP: Keluarkan elemen dari kepala barisan dan kembalikan nilainya, seperti LPOP Queue: My-Queue. Memproses Baris kosong: Jika barisan kosong, LPOP mengembalikan nihil, dan anda boleh menyemak sama ada barisan wujud sebelum membaca elemen.

Cara menggunakan kunci redis Cara menggunakan kunci redis Apr 10, 2025 pm 08:39 PM

Menggunakan REDIS untuk mengunci operasi memerlukan mendapatkan kunci melalui arahan SETNX, dan kemudian menggunakan perintah luput untuk menetapkan masa tamat tempoh. Langkah-langkah khusus adalah: (1) Gunakan arahan SETNX untuk cuba menetapkan pasangan nilai utama; (2) Gunakan perintah luput untuk menetapkan masa tamat tempoh untuk kunci; (3) Gunakan perintah DEL untuk memadam kunci apabila kunci tidak lagi diperlukan.

See all articles