2020 01 17 Redis系列(十)Redis對象系統

前言

其實關於本文,我猶豫再三。面試

  1. 對象系統值得寫一篇文章嗎?從技術上來說,固然是值。可是對於咱們大部分人來講,它都是隱身的。
  2. 寫的話,順序放在哪裏?在 Redis 系列(九)底層數據結構之五種基礎數據類型的實現中其實就提到了,那麼應該在此以前先介紹它嗎?

結論:想那麼多屁事,寫就完事了。redis

介紹

正如上一篇文章提到的,Redis 不是生硬的使用前面介紹過的數據結構,來實現了字符串,列表,字典等等數據結構,而是精心打造了一個對象系統。算法

對於 Redis 來講,全部的所謂的數據類型,本質上都是一個對象,並且同一個類型的對象,底層實現編碼不同。數據庫

Redis 對象的定義爲:後端

// 類型
typedef type:4;
// 編碼
unsigned encoding:4;
// 指向底層數據結構的指針
void *ptr;
...
複製代碼

類型

對象的 type 屬性,記錄了對象的類型,這個類型就是咱們所熟知的 Reids 的數據類型了,好比字符串,列表,集合,有序集合,散列等。緩存

對於 Redis 數據庫中的鍵值對來說,鍵值永遠是一個字符串對象,值能夠是不少種。微信

編碼和底層數據結構

對象的 ptr 指針,指向對象的底層數據結構,而這個數據結構是什麼,則由 encoding 來決定。它能夠是如下任意一種:數據結構

  • REDIS_ENCODING_INT long 類型的整數
  • REDIS_ENCODING_ENBSTR embstr 編碼的簡單動態字符串(不知道的能夠去看上一篇文章)
  • REDIS_ENCODING_RAW 簡單動態字符串
  • REDIS_ENCODING_HT 字典
  • REDIS_ENCODING_LINKEDLIST 雙端鏈表
  • REDIS_ENCODING_ZIPLIST 壓縮列表
  • REDIS_ENCODING_INTSET 整數集合
  • REDIS_ENCODING_SKIPLIST 跳躍表和字典

每種類型的對象都至少可使用兩種不一樣的編碼。以下表:性能

2020-01-16-20-48-29

五種常見的對象類型

對於咱們而言,工做中最經常使用以及面試中最常被問到的五種數據類型,他們的底層分別使用了什麼編碼及數據結構,多種編碼之間的切換條件是怎樣的?學習

這些問題你均可以在上一篇文章中找到答案。敬請查看 Redis 系列(九)底層數據結構之五種基礎數據類型的實現

類型檢查與命令多態

若是讀者熟悉 Redis 的命令的話(不熟不要緊,看下一篇文章), 就會發現,Redis 的命令設計維度不是單一的。

好比有一類命令只能對指定的 數據類型執行。好比 ZADD 及各類 ADD.

而有一些命令是能夠對全部類型操做的,好比 TYPE DEL 等等。

爲了確保命令能夠被正確的執行,Redis 須要進行命令的檢查,由於相信用戶不會亂用是十分愚蠢的。

在全部命令被執行以前,Redis 會首先檢查輸入的鍵的類型是否與命令匹配,這個檢查就是應用 redisObject 中的 type字段進行的。

若是匹配,則繼續執行命令,若是不匹配則返回特定的錯誤信息。

除了進行類型檢查以外,Redis 還應用對象的類型進行命令的多態。

設想一下,列表對象能夠 使用LLEN命令來求出當前元素的個數,而在之前,列表對象的實現多是壓縮列表,也能夠是雙端鏈表,那麼對於他們而言,求出長度的方法固然是不同的。

Redis 會首先進行類型檢查,以後根據當前對象的編碼來決定當前命令應該調用哪一個數據結構的 API. 以此來實現命令的多態。

內存回收

學習 Java 的同志們看到這裏是否是倍感親切,彷彿看到了家人。

衆所周知,c 語言是沒有自動化的內存管理的,可是 Redis 這麼大的系統又不可能徹底手動的控制內存使用,所以須要一套自動化的內存回收機制。

Redis 在本身的對象系統中,基於引用計數實現了內存回收。

在 redisObject 對象中,還有一個額外的書序 refcount.

  • 建立對象時,引用計數爲 1.
  • 當對象被一個新程序使用時,引用計數+1.
  • 當對象被一個程序拋棄的時候,引用計數-1;
  • 當對象的引用計數爲 0, 對象會被回收,它所佔用的內存被釋放掉。

對於這一塊的具體實現我也沒看,可是引用計數的原理想必各位都很清楚了,若是不清楚的話隨便 google 一下JVM 內存回收基本上都會順手講到引用計數的。

對象共享

除了用於使用基於引用計數的內存回收以外,對象的引用計數屬性,還被用來作一些對象共享的工做。

設想一下,首先你建立了一個 kye=a, value=100的對象,過一會你又建立了一個key=b, value=100的對象,如此循環往復。內存會無線增大,可是其實保存的是同一個信息。

這些對象理論上來說是徹底能夠進行共享的,即,首先我建立一個value=100的對象放在這裏,每當你新建立一個上面那樣的對象時,我就把指針指過來就行了。

Redis 有選擇性的這樣子作了,當它共享以前,會先給對應的對象的引用計數+1, 以後把指針指過來。

爲何說是有選擇性的呢?由於 Redis 只會緩存0-9999的數字字符串,若是你建立的鍵值對的值是這個,Redis 就會直接使用共享對象了。

爲何很少緩存一點呢?最好是把系統中全部相同的值全緩存起來,這樣子最省內存了。Redis 不是最缺內存了嗎?

是的,這樣子固然是省內存,可是** Redis 是一個高性能的內存數據庫**.

性能這一塊,Redis 卡的死死的。

想要判斷兩個對象的值是否相同,若是都是整數,只須要 O(1). 若是都是字符串,那麼須要 O(N). 若是都是複雜對象(好比 hash), 那麼可能須要 O(N2). Redis 爲了更好的性能,放棄了緩存更加複雜的對象。

對象淘汰:空轉時長

RedisObject 還有一個屬性,unsigned lru:32;.

從名字咱們就能夠看出來它是作什麼的了,它記錄了當前對象最後一次被訪問的時間。

這個時間會在 Redis 的內存使用滿了以後,Redis 會進行對象的淘汰,其中有一種算法是LRU. 會用到對象上一次被訪問的時間。

同時,咱們也能夠手動的查看某一個對象的空轉時長。空轉時長=當前時間-最後一次訪問時間.

2020-01-16-21-19-56

總結

這篇文章大概了講了一下 Redis 中的對象系統設計,及對象系統能夠用來作什麼。

  • 能夠爲數據類型的多種實現方式提供土壤
  • 類型檢查與命令多態
  • 基於引用計數的內存回收
  • 對象共享,節省內存
  • 記錄對象的空轉時長,用於 LRU 等。

參考文章

《Redis 的設計與實現(第二版)》


完。

聯繫我

最後,歡迎關注個人我的公衆號【 呼延十 】,會不按期更新不少後端工程師的學習筆記。 也歡迎直接公衆號私信或者郵箱聯繫我,必定知無不言,言無不盡。


以上皆爲我的所思所得,若有錯誤歡迎評論區指正。

歡迎轉載,煩請署名並保留原文連接。

聯繫郵箱:huyanshi2580@gmail.com

更多學習筆記見我的博客或關注微信公衆號 < 呼延十 >------>呼延十

相關文章
相關標籤/搜索