除了用于實現引用計數內存回收機制之外, 對象的引用計數屬性還帶有對象共享的作用。
舉個例子, 假設鍵 A 創建了一個包含整數值?`100`?的字符串對象作為值對象, 如圖 8-20 所示。

如果這時鍵 B 也要創建一個同樣保存了整數值?`100`?的字符串對象作為值對象, 那么服務器有以下兩種做法:
1. 為鍵 B 新創建一個包含整數值?`100`?的字符串對象;
2. 讓鍵 A 和鍵 B 共享同一個字符串對象;
以上兩種方法很明顯是第二種方法更節約內存。
在 Redis 中, 讓多個鍵共享同一個值對象需要執行以下兩個步驟:
1. 將數據庫鍵的值指針指向一個現有的值對象;
2. 將被共享的值對象的引用計數增一。
舉個例子, 圖 8-21 就展示了包含整數值?`100`?的字符串對象同時被鍵 A 和鍵 B 共享之后的樣子, 可以看到, 除了對象的引用計數從之前的?`1`?變成了?`2`?之外, 其他屬性都沒有變化。

共享對象機制對于節約內存非常有幫助, 數據庫中保存的相同值對象越多, 對象共享機制就能節約越多的內存。
比如說, 假設數據庫中保存了整數值?`100`?的鍵不只有鍵 A 和鍵 B 兩個, 而是有一百個, 那么服務器只需要用一個字符串對象的內存就可以保存原本需要使用一百個字符串對象的內存才能保存的數據。
目前來說, Redis 會在初始化服務器時, 創建一萬個字符串對象, 這些對象包含了從?`0`?到?`9999`?的所有整數值, 當服務器需要用到值為?`0`到?`9999`?的字符串對象時, 服務器就會使用這些共享對象, 而不是新創建對象。
注意
創建共享字符串對象的數量可以通過修改?`redis.h/REDIS_SHARED_INTEGERS`?常量來修改。
舉個例子, 如果我們創建一個值為?`100`?的鍵?`A`?, 并使用?OBJECT REFCOUNT?命令查看鍵?`A`?的值對象的引用計數, 我們會發現值對象的引用計數為?`2`?:
~~~
redis> SET A 100
OK
redis> OBJECT REFCOUNT A
(integer) 2
~~~
引用這個值對象的兩個程序分別是持有這個值對象的服務器程序, 以及共享這個值對象的鍵?`A`?, 如圖 8-22 所示。

如果這時我們再創建一個值為?`100`?的鍵?`B`?, 那么鍵?`B`?也會指向包含整數值?`100`?的共享對象, 使得共享對象的引用計數值變為?`3`?:
~~~
redis> SET B 100
OK
redis> OBJECT REFCOUNT A
(integer) 3
redis> OBJECT REFCOUNT B
(integer) 3
~~~
圖 8-23 展示了共享值對象的三個程序。

另外, 這些共享對象不單單只有字符串鍵可以使用, 那些在數據結構中嵌套了字符串對象的對象(`linkedlist`?編碼的列表對象、?`hashtable`?編碼的哈希對象、?`hashtable`?編碼的集合對象、以及?`zset`?編碼的有序集合對象)都可以使用這些共享對象。
為什么 Redis 不共享包含字符串的對象?
當服務器考慮將一個共享對象設置為鍵的值對象時, 程序需要先檢查給定的共享對象和鍵想創建的目標對象是否完全相同, 只有在共享對象和目標對象完全相同的情況下, 程序才會將共享對象用作鍵的值對象, 而一個共享對象保存的值越復雜, 驗證共享對象和目標對象是否相同所需的復雜度就會越高, 消耗的 CPU 時間也會越多:
* 如果共享對象是保存整數值的字符串對象, 那么驗證操作的復雜度為??;
* 如果共享對象是保存字符串值的字符串對象, 那么驗證操作的復雜度為??;
* 如果共享對象是包含了多個值(或者對象的)對象, 比如列表對象或者哈希對象, 那么驗證操作的復雜度將會是??。
因此, 盡管共享更復雜的對象可以節約更多的內存, 但受到 CPU 時間的限制, Redis 只對包含整數值的字符串對象進行共享。
- 介紹
- 前言
- 致謝
- 簡介
- 第一部分:數據結構與對象
- 簡單動態字符串
- SDS 的定義
- SDS 與 C 字符串的區別
- SDS API
- 重點回顧
- 參考資料
- 鏈表
- 鏈表和鏈表節點的實現
- 鏈表和鏈表節點的 API
- 重點回顧
- 字典
- 字典的實現
- 哈希算法
- 解決鍵沖突
- rehash
- 漸進式 rehash
- 字典 API
- 重點回顧
- 跳躍表
- 跳躍表的實現
- 跳躍表 API
- 重點回顧
- 整數集合
- 整數集合的實現
- 升級
- 升級的好處
- 降級
- 整數集合 API
- 重點回顧
- 壓縮列表
- 壓縮列表的構成
- 壓縮列表節點的構成
- 連鎖更新
- 壓縮列表 API
- 重點回顧
- 對象
- 對象的類型與編碼
- 字符串對象
- 列表對象
- 哈希對象
- 集合對象
- 有序集合對象
- 類型檢查與命令多態
- 內存回收
- 對象共享
- 對象的空轉時長
- 重點回顧
- 第二部分:單機數據庫的實現
- 數據庫
- 數據庫鍵空間
- 重點回顧
- RDB 持久化
- RDB 文件結構
- 重點回顧
- AOF 持久化
- AOF 持久化的實現
- 重點回顧
- 事件
- 文件事件
- 重點回顧
- 參考資料
- 客戶端
- 客戶端屬性
- 重點回顧
- 服務器
- 命令請求的執行過程
- 重點回顧
- 第三部分:多機數據庫的實現
- 復制
- 舊版復制功能的實現
- 重點回顧
- Sentinel
- 啟動并初始化 Sentinel
- 重點回顧
- 參考資料
- 集群
- 節點
- 重點回顧
- 第四部分:獨立功能的實現
- 發布與訂閱
- 頻道的訂閱與退訂
- 重點回顧
- 參考資料
- 事務
- 事務的實現
- 重點回顧
- Lua 腳本
- 創建并修改 Lua 環境
- 重點回顧
- 排序
- SORT <key> 命令的實現
- 重點回顧
- 二進制位數組
- GETBIT 命令的實現
- 重點回顧
- 慢查詢日志
- 慢查詢記錄的保存
- 慢查詢日志的閱覽和刪除
- 添加新日志
- 重點回顧
- 監視器
- 成為監視器
- 向監視器發送命令信息
- 重點回顧
- 源碼、相關資源和勘誤