天天看點

《Redis設計與實作》閱讀:Redis底層研究之簡單動态字元串SDS

        除僅用于字元串字面量的情況外,對于可以被修改值的字元串的表示,Redis底層并沒有采用C語言傳統的字元串表示,即以空字元結尾的字元數組,而是采用專門為其設計的簡單動态字元串作為其預設字元串表示,其英文全稱為Simple Dynamic String,簡稱SDS。除了用于儲存資料庫中字元串值外,SDS也可以用于緩沖區buffer,比如AOF中的緩沖區、用戶端輸入緩沖區等。本文,我們将詳細研究簡單動态字元串SDS的實作及其在性能等方面的獨特之處。

        注:内容總結于《Redis設計與實作》一書!

        SDS實作

        SDS整體結構如下:

        可以看到,SDS依然依靠位元組數組char buf[]來儲存字元串,但是,它還儲存了位元組數組char buf[]中已使用和未使用位元組數量len、free,而len的含義即SDS所儲存的字元串長度,free的含義則是SDS剩餘可以容納字元串的長度。一個簡單的示例如下:

《Redis設計與實作》閱讀:Redis底層研究之簡單動态字元串SDS

        上圖所示的SDS,存儲了字元串"Redis",其長度為5,同時尚有4個位元組的空間未被利用。而且,你會發現,其實buf數組的大小實際為10,在字元串末尾還有一個表示空字元的'\0',為什麼會這樣呢?這就是SDS設計的巧妙之處,它為了能夠直接重用C字元串函數庫裡的一些字元串常用函數,而這個空字元是SDS自動添加的,且不計算在len和free内,對使用者而言是透明的。

        SDS較C字元串的優點

        SDS為什麼要做以上設計呢,它對于C字元串而言,有哪些優點?

        其相比較于C字元串的優點總結如下:

        1、常數複雜度擷取字元串長度

              C字元串并不會記錄字元串的長度,必須周遊整個字元串,對遇到的每個非空字元計數,直到遇到代表字元串結尾的空字元,才能計算出字元串長度,其時間複雜度為O(N),而SDS則直接擷取len屬性值即可獲知字元串長度,其時間複雜度為O(1),而len屬性是SDS相關函數自動完成的,對于使用者而言是透明的。這個優點對任何一個,即使非常長的字元串反複執行STRLEN指令,也不會對系統性能産生影響,確定了擷取字元串長度不會成為Redis的性能瓶頸。

        2、杜絕緩沖區溢出

              當修改或替換C字元串中的值時,C字元串由于不會記錄本身長度,也不會預配置設定空間,會産生緩沖區溢出,甚至偷偷修改其他字元串内容的情況,如下圖所示:

《Redis設計與實作》閱讀:Redis底層研究之簡單動态字元串SDS

              如果我們想在S1現有字元串基礎上追加一個Cluster,而又不對S1進行記憶體重配置設定,那麼這個操作會造成緩沖區溢出,同時會偷偷修改掉S2字元串的值。而SDS的空間配置設定政策則會避免緩沖區溢出的情況發生,它會先檢查len和free,確定要追加、修改、替換的長度能夠得到滿足,如不滿足,則會自動進行空間再配置設定,進而避免緩沖區溢出。

        3、減少字元串修改記憶體重配置設定次數

              顯然,Redis是使用場景決定了存儲于其内的字元串會頻繁的被修改,而如果是在C字元串情況下,就會發生以下兩種情況:

              3.1、對于增長性字元串修改操作,程式每次都需要通過記憶體重配置設定來滿足字元串空間要求,如果忘了,則會産生2中所說的緩沖區溢出;

              3.2、對于縮短性字元串修改操作,程式需要通過記憶體重配置設定來釋放不再使用的空間,如果忘了,則會産生記憶體洩露的問題。

             而記憶體重配置設定算法比較複雜,且涉及到系統調用,通常是一種比較耗時的操作,而SDS則依靠空間預配置設定和惰性空間釋放兩種政策解決了上述兩個問題,減少了頻繁的空間重配置設定等,提供了系統性能。總結如下:

             (1)空間預配置設定

                      如果SDS修改後,其長度小于1M,也就是len小于1M,則程式會配置設定與len屬性同樣大小的未使用空間,即len=free,buf實際大小則還要加1,因為有上述相容C字元串庫函數所使用的空字元;如果SDS修改後其長度大于等于1M,則程式每次會配置設定1M的未使用空間,此時free等于1M,buf實際大小也是還要加,原因同上。

             (2)惰性空間釋放

                      如果SDS字元串被縮短,未使用位元組數增大,則SDS并不會使用記憶體重配置設定立即回收縮短後的未使用空間,而是記錄在free屬性中,等待将來使用,這樣,惰性空間釋放政策避免了SDS縮短字元串時所必須的記憶體重配置設定回收空間操作,為将來可能的增長操作使用,提高了Redis字元串處理的性能。同時,對于真正需要釋放空間的情況,SDS則提供了專門的API,供使用者使用,避免空間的持續浪費。

         4、二進制安全

               C字元串以空字元作為字元串結尾的特點,決定了其隻能儲存文本資料,而不能存儲圖檔、視訊、音頻等二進制資料,而SDS通過len屬性則避免了這一情況,使其可以存儲諸如上述圖檔、視訊、音頻等任意格式的二進制資料。

        5、相容部分C字元串函數

             buf中末尾自動追加的空字元實作了SDS可以相容部分C字元串函數,比如對比strcasecmp、追加strcat等函數。

        SDS與C字元串對比如下:

《Redis設計與實作》閱讀:Redis底層研究之簡單動态字元串SDS

        SDS簡單總結如下:

《Redis設計與實作》閱讀:Redis底層研究之簡單動态字元串SDS