自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Redis 中的 “SOS”,不對,是SDS

存儲 存儲軟件 Redis
身為一名有追求的程序員,阿粉我的理解是光會吃老本是不行的,所以我一直也在學(xué)習(xí),今天大家就跟我一起來了解一下 Redis 的 SDS 吧(不是 SOS 哦~)。

[[312201]]

大家好,我是鴨血粉絲(大家會親切的喊我 「阿粉」),是一位喜歡吃鴨血粉絲的程序員,之前給大家總結(jié)了線上 OOM 的情況,相信大家也能從中學(xué)到一些東西,身為一名有追求的程序員,阿粉我的理解是光會吃老本是不行的,所以我一直也在學(xué)習(xí),今天大家就跟我一起來了解一下 Redis 的 SDS 吧(不是 SOS 哦~)。

01、SDS 數(shù)據(jù)結(jié)構(gòu)

Redis 底層是基于 C 語言來開發(fā)的,但是它沒有采用 C 語言傳統(tǒng)的字符串表示方式,而是自定義了一種叫做 SDS(Sample Dynamic String,簡單動態(tài)字符串)的數(shù)據(jù)結(jié)構(gòu)來表示字符串。傳統(tǒng)的 C 語言的字符串是采用空字符(\0)作為結(jié)尾的字符數(shù)組,SDS 的數(shù)據(jù)結(jié)構(gòu)稍微復(fù)雜一點,整個結(jié)構(gòu)包含三個部分,是 Redis 的基礎(chǔ)。(阿粉猜測這里就是傳說中的青出于藍(lán)而勝于藍(lán))。

1.1、數(shù)據(jù)結(jié)構(gòu)

在源碼 sds.h/sdshdr 結(jié)構(gòu)體中定義了 SDS 的數(shù)據(jù)結(jié)構(gòu),包括三個部分,free,len,buf[],依次含義如下

  1. buf[]:字節(jié)數(shù)組,用于存放實際的字符串;
  2. len:記錄 buf 數(shù)組中已經(jīng)使用的字節(jié)數(shù)量,等同于 SDS 所保存的字符串的長度;
  3. free:記錄 buf 數(shù)組中未使用的字節(jié)的數(shù)量。

說明

上圖中的 SDS 表示一個存放了 'RED' 字符串,已經(jīng)使用的長度為 3,未使用的長度為 2(這里用空白格表示未使用),其中的 '\0' 表示的是字符串的結(jié)束,不計算在 SDS 的 len 中,并且由 SDS 底層函數(shù)自動添加,對使用者來說是透明。這里統(tǒng)一采用空字符(\0)結(jié)尾是為了復(fù)用 C 語言的相關(guān)函數(shù)。這個相信大家也很能理解,畢竟有祖宗可以靠,沒必要全靠自己那么辛苦~。

02、為什么采用 SDS

2.1、SDS 與 C語言字符串的區(qū)別

在說明 Redis 為什么要自定義 SDS 之前,阿粉覺得我們應(yīng)該先看一下 SDS 與傳統(tǒng)的 C 語言的字符串有什么區(qū)別,知道了具體的區(qū)別我們才能知道這樣實現(xiàn)的原因是什么。

2.1.1、O(1) 獲取字符串的長度

傳統(tǒng)的 C 語言字符串如果要獲取字符串的長度,則需要遍歷整個字符串,直到遇到 '\0' 字符,才知道整個字符串的長度是多少,操作復(fù)雜度是 O(n) 的。但是在 SDS 中,由于我們記錄了字符串的長度,所以在獲取字符串長度的時候是可以直接獲取的,整個操作為 O(1)。

如上面的示例,我們可以直接獲取字符串的長度是 3,而不需要遍歷,另外字符串 Key 在 Redis 的底層實現(xiàn)就是采用 SDS 的,所以這個特性就保證了我們在計算 Key 的長度的時候不會出現(xiàn)任何瓶頸,對系統(tǒng)的性能不會有任何影響。

2.1.2、動態(tài)擴容

由于 SDS 中記錄了未使用的空間大小,所以如果出現(xiàn)對已有字符串進行修改或者賦值時,SDS 底層函數(shù)會自動檢測剩余空間是否能滿足此次修改,如果 free 空間足夠則直接修改;如果 free 空間不夠則會先進行動態(tài)擴容達到能滿足的空間大小,然后再執(zhí)行修改動作。整個擴容的動作是 SDS 底層函數(shù)自動完成,對使用者無感。

而對于傳統(tǒng)的 C 語言字符串,如果在修改前忘記手動擴容則會導(dǎo)致字符串后面的數(shù)據(jù)被覆蓋。這里阿粉就不得不說一句了,為了方便大眾程序員,另一些骨灰級程序員(嗯,仿佛看到了未來的阿粉)也是操碎了心啊~

2.1.3、減少內(nèi)存分配次數(shù)

在傳統(tǒng)的 C 語言的字符串,我們每次對字符串的修改都會涉及到字符串內(nèi)存的重新分配,不管是增加還是減少字符串的長度。這種情況下,如果我們多次對字符串的長度進行調(diào)整的時候就會導(dǎo)致多次的內(nèi)存重新分配。

而在 SDS 中我們在對一個 SDS 初始化的時候會根據(jù)實際 buf[] 字符串的長度進行預(yù)先空間分配,并且標(biāo)記為 free。這種方式叫做空間預(yù)分配,在很大程度上可以減少增加字符串長度導(dǎo)致內(nèi)存重新分配的情況。free 的空間分配的策略是根據(jù) buf[] 大小來決定的,如果 buf[] 大小小于 1MB,則 len 多大 free 就多大;如果 buf[] 大小大于 1MB,則 free 固定設(shè)置為 1MB。

上面說的是SDS 字符串的長度增加,另外如果 SDS 的字符串長度減少,那么 SDS 會將減少的長度存放到 free 中,而不是直接回收,這樣可以方便下次如果再次使用,減少內(nèi)存重新分配。這種策略叫做惰性空間釋放。

同樣的上面兩種操作對使用者是完全無感的,阿粉覺得這種方案還是很合理的,不知道“元芳”你怎么看?

2.1.4、二進制安全

我們都知道 Redis 是可以存儲各種類型數(shù)據(jù)的,不僅是字符串也可以存儲圖片,視頻等二進制數(shù)據(jù)流。這是由于 Redis 不依賴一 '\0' 空字符作為結(jié)束字符。C 語言之所以不支持就是因為二進制流中會攜帶 '\0' 字符,導(dǎo)致無法知道字符串真實的結(jié)束位置。這就帶來了另一個 Redis 特性,就是二進制的安全性。

2.2 為什么使用 SDS

通過上面阿粉提到的內(nèi)容我們知道了 SDS 比傳統(tǒng)的 C 語言的字符串有很多優(yōu)勢,也正是這些必不可少的優(yōu)勢才促成了 SDS的存在。Redis 是一個高性能的內(nèi)存數(shù)據(jù)庫,所以在性能方面要求特別高,這種設(shè)計方式雖然浪費了一定的空間,但是為了達到性能的要求也是值得的。有空間換時間的這種方式,在軟件設(shè)計的領(lǐng)域還是很多的。

2.3 SDS 常用 API

上面阿粉說的都是一些原理,下面從源碼上給大家展示一下。在 2.1 中提到有獲取長度 len 和釋放空間 free 的動作,那么對應(yīng)在 SDS 底層必定會有提供支持的 API,下面我們通過源碼來看幾個常用的 API。

1.源碼 sds.c 文件中 sdsfree 函數(shù)定義如下:

  1. /* Free an sds string. No operation is performed if 's' is NULL. */ 
  2. void sdsfree(sds s) { 
  3.     if (s == NULLreturn
  4.     s_free((char*)s-sdsHdrSize(s[-1])); 

2.在源碼 sds.h 文件中 sdslen 函數(shù)定義如下:

  1. static inline size_t sdslen(const sds s) { 
  2.     unsigned char flags = s[-1]; 
  3.     switch(flags&SDS_TYPE_MASK) { 
  4.         case SDS_TYPE_5: 
  5.             return SDS_TYPE_5_LEN(flags); 
  6.         case SDS_TYPE_8: 
  7.             return SDS_HDR(8,s)->len; 
  8.         case SDS_TYPE_16: 
  9.             return SDS_HDR(16,s)->len; 
  10.         case SDS_TYPE_32: 
  11.             return SDS_HDR(32,s)->len; 
  12.         case SDS_TYPE_64: 
  13.             return SDS_HDR(64,s)->len; 
  14.     } 
  15.     return 0; 

上面兩個是 SDS 底層對應(yīng)的 sdsfree 和 sdslen 函數(shù),用于釋放 SDS 空間和獲取 SDS 的長度。

3.在源碼 sds.c 文件中創(chuàng)建 sds 的函數(shù)定義如下:

  1. /* Create an empty (zero length) sds string. Even in this case the string 
  2.  * always has an implicit null term. */ 
  3. sds sdsempty(void) { 
  4.     return sdsnewlen("",0); 
  5.  
  6. /* Create a new sds string starting from a null terminated C string. */ 
  7. sds sdsnew(const char *init) { 
  8.     size_t initlen = (init == NULL) ? 0 : strlen(init); 
  9.     return sdsnewlen(init, initlen); 

上面兩個是 SDS 底層對應(yīng)的 sdsempty 和 sdsnew 函數(shù),顧名思義就是創(chuàng)建空的 SDS 和創(chuàng)建一個新的 SDS 字符串。

03、總結(jié)

這篇文章阿粉跟大家介紹了一下 Redis 的 SDS 和 SDS 底層的組成結(jié)構(gòu),并且與 C 語言傳統(tǒng)字符串進行的詳細(xì)的對比,闡述了 SDS 出現(xiàn)解決了哪些問題,最后帶大家從源碼中簡單的看了幾個底層的函數(shù)實現(xiàn)。

在走向骨灰級程序員的道路上,阿粉我從不懈怠,充滿斗志,那么你呢?是否跟阿粉一樣,對未來充滿期待!

今天是 2020 年的第一個周末,所以你想怎么過能?歡迎加入到我們 Java 極客技術(shù)的知識星球中進行留言,我們共同進步成長。

04、參考文檔

https://github.com/antirez/redis

https://redis.io/

《Redis 設(shè)計與實現(xiàn)(第二版)》——黃建宏

 

責(zé)任編輯:武曉燕 來源: Java極客技術(shù)
相關(guān)推薦

2020-06-29 07:44:36

Redis

2022-02-10 09:04:18

RediSDS數(shù)據(jù)結(jié)構(gòu)

2024-02-20 20:12:09

C語言字符串Redis

2016-11-29 14:57:49

SDS定義存儲

2016-11-29 15:44:02

SanDiskPCIeFlashSoft

2025-01-10 11:42:13

2019-03-07 15:43:22

Redis數(shù)據(jù)SDS

2022-07-05 14:49:25

Redis 6多線程

2017-01-17 14:47:24

SDS軟件定義存儲

2022-02-09 12:11:57

數(shù)據(jù)丟失數(shù)據(jù)恢復(fù)硬盤

2024-06-04 16:01:39

2023-01-09 08:42:04

String數(shù)據(jù)類型

2021-02-18 07:45:09

redis 字符串SDS

2021-04-27 10:53:58

Redis數(shù)據(jù)庫SDS

2021-02-23 09:35:33

redis字符串數(shù)據(jù)庫

2018-07-18 05:54:43

軟件定義存儲SDS網(wǎng)絡(luò)

2015-05-06 10:44:57

SDN虛擬化云計算構(gòu)架

2015-05-05 09:59:36

SDNSDS

2015-06-03 16:24:18

SDNSDS云構(gòu)架

2021-06-26 07:29:42

RedisHashtable數(shù)據(jù)
點贊
收藏

51CTO技術(shù)棧公眾號