Redis實現之數據庫(一)


服務器中的數據庫

Redis服務器將所有數據庫都保存在服務器狀態redis.h/redisServer結構體的db數組中,db數組的每個項都是一個redis.h/redisDb結構體,每個redisDb結構體代表一個數據庫

redis.h

struct redisServer {
    ……
	//一個數組,保存着服務器中所有數據庫
    redisDb *db;
    ……
};

  

在初始化服務器時,程序會根據服務器狀態的dbnum屬性來決定應該創建多少個數據庫:

redis.h

struct redisServer {
    ……
	//服務器的數據庫數量
    int dbnum;                     
    ……
};

  

dbnum屬性由服務器配置的database選項決定,默認情況下,該選項的值為16,所以Redis服務器默認會創建16個數據庫,如圖1-1所示

圖1-1   服務器數據庫示例

切換數據庫

每個Redis客戶端都有自己的目標數據庫,每當客戶端執行數據庫寫命令或者數據庫讀命令的時候,目標數據庫就會成為這些命令的操作對象。默認情況下,Redis客戶端的目標數據庫為0號數據庫,但客戶端可以通過執行SELECT命令來切換目標數據庫。以下代碼示例演示了客戶端在0號數據庫設置並讀取鍵msg,之后切換到2號數據庫並執行類似操作的過程:

127.0.0.1:6379> SET msg "hello world"
OK
127.0.0.1:6379> GET msg
"hello world"
127.0.0.1:6379> SELECT 2
OK
127.0.0.1:6379[2]> GET msg
(nil)
127.0.0.1:6379[2]> SET msg "another world"
OK
127.0.0.1:6379[2]> GET msg
"another world"

  

 在服務器內部,客戶端狀態redisClient結構的db屬性記錄了客戶端當前的目標數據庫,這個屬性是一個指向redisDb結構的指針:

redis.h

typedef struct redisClient {
    ……
	//記錄客戶端當前正在使用的數據庫
    redisDb *db;
    ……
} redisClient;

  

redisClient.db指針指向redisServer.db數組的其中一個元素,而被指向的元素就是客戶端你的目標數據庫。如果某個客戶端的目標數據庫為1號數據庫,那么這個客戶端所對應的客戶端狀態和服務器狀態之間的關系如圖1-2所示

 

圖1-2   客戶端的目標數據庫為1號數據庫

如果這時客戶端執行命令SELECT 2,將目標數據庫改為2號數據庫,那么客戶端的狀態和服務器之間的關系將更新到如圖1-3

圖1-3   客戶端的目標數據庫為2號數據庫

通過修改redisClient.db指針,讓它指向服務器中的不同數據庫,從而實現切換目標數據庫的功能,這就是SELECT命令的實現原理

數據庫鍵空間

Redis是一個鍵值對數據庫服務器,服務器中的每個數據庫都由redis.h/redisDb結構體表示,其中,redisDb結構體的dict字典保存了數據庫中的所有鍵值對,我們將這個字典稱為鍵空間

redis.h

typedef struct redisDb {
	//數據庫鍵空間,保存着數據庫中所有鍵值對
    dict *dict;                
    ……
} redisDb;

  

鍵空間和用戶所見的數據庫是直接對應的:

  • 鍵空間的鍵也就是數據庫的鍵,每個鍵都是一個字符串對象
  • 鍵空間的值也就是數據庫的值,每個值可以是字符串對象、列表對象、哈希表對象、集合對象和有序集合對象中任意一種Redis對象、集合對象和有序集合對象中的任意一種Redis對象

舉個栗子,如果我們在空白的數據庫中執行以下命令:

127.0.0.1:6379> SET message "hello world"
OK
127.0.0.1:6379> RPUSH alphabet "a" "b" "c"
(integer) 3
127.0.0.1:6379> HSET book name "Redis in Action"
(integer) 1
127.0.0.1:6379> HSET book author "Josiah L. Carlson"
(integer) 1
127.0.0.1:6379> HSET book publisher "Manning"
(integer) 1

  

那么在這些命令之后,數據庫鍵空間將會是圖1-4所展示的樣子:

  • alphabet是一個列表鍵,鍵的名字是一個包含字符串"alphabet"的字符串對象,鍵的值則是一個包含三個元素的列表對象
  • book是一個哈希表鍵,鍵的名字是一個包含字符串"book"的字符串對象,鍵的值則是一個包含三個鍵值對的哈希表對象
  • message是一個字符串鍵,鍵的名字是一個包含字符串"message"的字符串對象,鍵的值則是一個包含字符串"hello world"的字符串對象

圖1-4   數據庫鍵空間例子

因為數據庫的鍵空間是一個字典,所以所有針對數據庫的操作,比如添加一個鍵值對到數據庫,或者從數據庫中刪除一個鍵值對,又或者在數據庫中獲取某個鍵值對等,實際上都是通過對鍵空間字典進行操作來實現,以下幾個小節將分別介紹數據庫的增刪改查操作的實現原理

添加新鍵

添加一個新的鍵值對到數據庫,實際上就是將一個新的鍵值對添加到鍵空間字典中,其中鍵為字符串對象,而值可以是任意一種類型的Redis對象。舉個栗子,如果鍵空間當前的狀態如圖1-4所示,那么在執行以下命令后:

127.0.0.1:6379> SET date "2013.12.1"
OK

  

 

鍵空間將添加一個新的鍵值對,這個新鍵值對的鍵是一個包含字符串"date"的字符串對象,而值對象則是一個包含字符串"2013.12.1"的字符串對象,如圖1-5所示

圖1-5   添加date鍵之后的鍵空間

刪除鍵

刪除數據庫中的一個鍵,實際上就是在鍵空間刪除一個鍵值對對象。舉個栗子,如果鍵空間當前的狀態如1-4所示,那么執行以下命令后:

127.0.0.1:6379> DEL book
(integer) 1

  

鍵book以及它的值將從鍵空間中被刪除,如圖1-6所示

圖1-6   刪除book鍵之后的鍵空間

更新鍵

對一個數據庫鍵進行更新,實際上就是對鍵空間里面鍵所對應的值對象進行更新,根據值對象的類型不同,更新的具體方法也會有所不同。舉個例子,如果鍵空間當前狀態如圖1-4所示,那么在執行以下命令后:

127.0.0.1:6379> SET message "blah blah"
OK

  

鍵message的值對象將從之前包含"hello world"字符串更新為"blah blah"字符串,如圖1-7所示

圖1-7   使用SET命令更新message鍵

再舉個例子,如果我們繼續執行以下命令:

127.0.0.1:6379> HSET book page 320
(integer) 1

  

那么鍵空間中book鍵的值對象(一個哈希鍵)將被更新,新的鍵值對page和320會被添加到值對象中,如圖1-8所示

圖1-8   使用HSET更新book

對鍵取值

對一個數據庫鍵進行取值,實際上就是在鍵空間中取出鍵所對應的值對象,根據值對象的類型不同,具體的取值方法也會有所不同。舉個栗子,如果鍵空間當前的狀態如圖1-4所示,那么當執行以下命令時:

127.0.0.1:6379> GET message 
"hello world"

  

GET命令將首先在鍵空間中查找鍵message,找到鍵之后接着取得該鍵所對應的字符串對象值,之后再返回值對象所包含的字符串"hello world",取值過程如圖1-9所示

圖1-9   使用GET命令取值的過程

再舉個例子,當執行以下命令時:

127.0.0.1:6379> LRANGE alphabet 0 -1
1) "a"
2) "b"
3) "c"

  

LRANGE命令將首先在鍵空間查找鍵alphabet,找到鍵之后接着取得該鍵所對應的列表對象值,之后再返回列表對象中包含的三個字符串對象的值,取值過程如圖1-10所示

圖1-10   使用LRANGE命令取值的過程

其他鍵空間操作

除了上面列出的添加、刪除、更新、取值操作外,還有很多針對數據庫本身的Redis命令,也是通過對鍵空間進行處理來完成的。比如說,用於清空整個數據庫的FLUSHDB命令,就是通過刪除鍵空間中的所有鍵值對來實現的。又比如說,用於隨機返回數據庫中某個鍵的RANDOMKEY命令,就是通過在鍵空間中隨機返回一個鍵來實現的。另外,用於返回數據庫鍵數量的DBSIZE命令,就是通過返回鍵空間中包含的鍵值對的數量來實現的。類似還有EXISTS、RENAME、KEYS等,這些命令都是通過對鍵空間進行操作來實現的

讀寫鍵空間的維護操作

當使用Redis命令對數據庫進行讀寫時,服務器不僅會對鍵空間執行指定的讀寫操作,還會執行一些額外的維護操作,其中包括:

  • 在讀取一個鍵之后(讀操作和寫操作都要對鍵進行讀取),服務器會根據鍵是否存在來更新服務器的鍵空間命中次數或鍵空間不命中次數,這兩個值可以在INFO stats命令的keyspace_hits屬性和keyspace_misses屬性中查看
  • 在讀取一個鍵之后,服務器會更新鍵的LRU(最后一次使用)時間,這個值可以用於計算鍵的閑置時間,使用OBJECT idletime<key>命令可以查看鍵key的閑置時間
  • 如果服務器在讀取一個鍵時發現該鍵已過期,那么服務器會先刪除這個過期鍵,然后才執行余下的其他操作
  • 如果客戶端使用WATCH命令監視某個鍵,那么服務器在對被監視的鍵進行修改之后,會將這個鍵標記為臟,從而讓事物程序注意到這個鍵已經被修改了
  • 服務器每次修改一個鍵之后,都會對臟鍵計數器的值加1,這個計數器會觸發服務器的持久化以及復制操作
  • 如果服務器開啟了數據庫通知功能,那么在對鍵進行修改之后,服務器將按配置發送相應的數據庫通知


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM