初學Redis(2)——用Redis作爲Mysql數據庫的緩存

        用Redis作Mysql數據庫緩存,必須解決2個問題。首先,應該確定用何種數據結構存儲來自Mysql的數據;在確定數據結構之後,還要考慮用什麼標識作爲該數據結構的鍵。

        直觀上看,Mysql中的數據都是按表存儲的;更微觀地看,這些表都是按行存儲的。每執行一次select查詢,Mysql都會返回一個結果集,這個結果集由若干行組成。所以,一個自然而然的想法就是在Redis中找到一種對應於Mysql行的數據結構。Redis中提供了五種基本數據結構,即字符串(string)、列表(list)、哈希(hash)、集合(set)和有序集合(sorted set)。經過調研,發現適合存儲行的數據結構有兩種,即string和hash。

        要把Mysql的行數據存入string,首先需要對行數據進行格式化。事實上,結果集的每一行都可以看做若干由字段名和其對應值組成的鍵值對集合。這種鍵值對結構很容易讓我們想起Json格式。因此,這裏選用Json格式作爲結果集每一行的格式化模板。根據這一想法,我們可以實現將結果集格式化爲若干Json對象,並將Json對象轉化爲字符串存入Redis的代碼:

  1. // 該函數把結果集中的每一行轉換爲一個Json格式的字符串並存入Redis的STRING結構中,  
  2. // STRING鍵應該包含結果集標識符和STRING編號,形式如“cache.string:123456:1”  
  3. string Cache2String(sql::Connection *mysql_connection,  
  4.                     redisContext *redis_connection,  
  5.                     sql::ResultSet *resultset,  
  6.                     const string &resultset_id, int ttl) {  
  7.   if (resultset->rowsCount() == 0) {  
  8.     throw runtime_error("FAILURE - no rows");  
  9.   }  
  10.   // STRING鍵的前綴,包含了結果集的標識符  
  11.   string prefix("cache.string:" + resultset_id + ":");  
  12.   unsigned int num_row = 1;  // STRING編號,附加於STRING鍵的末尾,從1開始  
  13.   sql::ResultSetMetaData *meta = resultset->getMetaData();  
  14.   unsigned int num_col = meta->getColumnCount();  
  15.   // 將結果集中所有行對應的所有STRING鍵存入該SET,SET鍵包含了結果集的標識符  
  16.   string redis_row_set_key("resultset.string:" + resultset_id);  
  17.   redisReply *reply;  
  18.   string ttlstr;  
  19.   stringstream ttlstream;  
  20.   ttlstream << ttl;  
  21.   ttlstr = ttlstream.str();  
  22.   resultset->beforeFirst();  
  23.   // 將結果集中的每一行轉爲Json格式的字符串,將這些Json字符串存入STRING,  
  24.   // 每個STRING對應結果集中的一行  
  25.   while (resultset->next()) {  
  26.     string redis_row_key;  // STRING鍵名,由前綴和STRING編號組成  
  27.     stringstream keystream;  
  28.     keystream << prefix << num_row;  
  29.     redis_row_key = keystream.str();  
  30.     Json::Value row;  
  31.     for (int i = 1; i <= num_col; ++i) {  
  32.       string col_label = meta->getColumnLabel(i);  
  33.       string col_value = resultset->getString(col_label);  
  34.       row[col_label] = col_value;  
  35.     }  
  36.     Json::FastWriter writer;  
  37.     string redis_row_value = writer.write(row);  
  38.     // 將STRING鍵及Json格式的對應值對存入Redis  
  39.     reply = static_cast<redisReply*>(redisCommand(redis_connection,   
  40.                                                  "SET %s %s",  
  41.                                                  redis_row_key.c_str(),   
  42.                                                  redis_row_value.c_str()));  
  43.     freeReplyObject(reply);  
  44.     // 將STRING鍵加入SET中  
  45.     reply = static_cast<redisReply*>(redisCommand(redis_connection,   
  46.                                                  "SADD %s %s",  
  47.                                                  redis_row_set_key.c_str(),   
  48.                                                  redis_row_key.c_str()));  
  49.     freeReplyObject(reply);  
  50.     // 設置STRING的過期時間  
  51.     reply = static_cast<redisReply*>(redisCommand(redis_connection,   
  52.                                                  "EXPIRE %s %s",  
  53.                                                  redis_row_key.c_str(),   
  54.                                                  ttlstr.c_str()));  
  55.     freeReplyObject(reply);  
  56.     ++num_row;  
  57.   }  
  58.   // 設置SET的過期時間  
  59.   reply = static_cast<redisReply*>(redisCommand(redis_connection,   
  60.                                                "EXPIRE %s %s",  
  61.                                                redis_row_set_key.c_str(),   
  62.                                                ttlstr.c_str()));  
  63.   freeReplyObject(reply);  
  64.   return redis_row_set_key;  // 返回SET鍵,以便於其他函數獲取該SET中的內容  
  65. }  


        要把Mysql的行數據存入hash,過程要比把數據存入string直觀很多。這是由hash的結構性質決定的——hash本身就是一個鍵值對集合:一個“父鍵”下面包含了很多“子鍵”,每個“子鍵”都對應一個值。根據前面的分析可知,結果集中的每一行實際上也是鍵值對集合。用Redis鍵值對集合表示Mysql鍵值對集合應該再合適不過了:對於結果集中的某一行,字段對應於hash的“子鍵”,字段對應的值就是hash“子鍵”對應的值,即結果集的一行剛好對應一個hash。這一想法的實現代碼如下:

  1. // 該函數把結果集中的每一行都存入一個HASH結構。HASH鍵應當包括結果集標識符和HASH編號,  
  2. // 形如“cache.string:123456:1”  
  3. string Cache2Hash(sql::Connection *mysql_connection,  
  4.                   redisContext *redis_connection,  
  5.                   sql::ResultSet *resultset,  
  6.                   const string &resultset_id, int ttl) {  
  7.   if (resultset->rowsCount() == 0) {  
  8.     throw runtime_error("FAILURE - no rows");  
  9.   }  
  10.   // HASH鍵的前綴,包含了結果集的標識符  
  11.   string prefix("cache.hash:" + resultset_id + ":");  
  12.   unsigned int num_row = 1;  // HASH編號,附加於HASH鍵的末尾,從1開始  
  13.   sql::ResultSetMetaData *meta = resultset->getMetaData();  
  14.   unsigned int num_col = meta->getColumnCount();  
  15.   // 將結果集中所有行對應的所有HASH鍵存入該SET,SET鍵包含了結果集的標識符  
  16.   string redis_row_set_key("resultset.hash:" + resultset_id);  
  17.   redisReply *reply;  
  18.   string ttlstr;  
  19.   stringstream ttlstream;  
  20.   ttlstream << ttl;  
  21.   ttlstr = ttlstream.str();  
  22.   // 結果集中的每一行對應於一個HASH,將結果集的所有行都存入相應HASH中  
  23.   resultset->beforeFirst();  
  24.   while (resultset->next()) {  
  25.     string redis_row_key;  // HASH鍵名,由前綴和HASH編號組成  
  26.     stringstream keystream;  
  27.     keystream << prefix << num_row;  
  28.     redis_row_key = keystream.str();  
  29.     for (int i = 1; i <= num_col; ++i) {  
  30.       string col_label = meta->getColumnLabel(i);  
  31.       string col_value = resultset->getString(col_label);  
  32.       // 將結果集中一行的字段名和對應值存入HASH  
  33.       reply = static_cast<redisReply*>(redisCommand(redis_connection,  
  34.                                                    "HSET %s %s %s",  
  35.                                                    redis_row_key.c_str(),   
  36.                                                    col_label.c_str(),  
  37.                                                    col_value.c_str()));  
  38.       freeReplyObject(reply);  
  39.     }  
  40.     // 將HASH鍵加入SET中  
  41.     reply = static_cast<redisReply*>(redisCommand(redis_connection,   
  42.                                                  "SADD %s %s",  
  43.                                                  redis_row_set_key.c_str(),   
  44.                                                  redis_row_key.c_str()));   
  45.     freeReplyObject(reply);  
  46.     // 設置HASH的過期時間  
  47.     reply = static_cast<redisReply*>(redisCommand(redis_connection,   
  48.                                                  "EXPIRE %s %s",  
  49.                                                  redis_row_key.c_str(),   
  50.                                                  ttlstr.c_str()));  
  51.     freeReplyObject(reply);  
  52.     ++num_row;  
  53.   }  
  54.   // 設置SET的過期時間  
  55.   reply = static_cast<redisReply*>(redisCommand(redis_connection,   
  56.                                                "EXPIRE %s %s",  
  57.                                                redis_row_set_key.c_str(),   
  58.                                                ttlstr.c_str()));  
  59.   freeReplyObject(reply);  
  60.   return redis_row_set_key;  // 返回SET鍵,以便於其他函數獲取該SET中的內容  
  61. }  

        至此,我們已經給出了兩種存儲Mysql結果集的方案,這就是我們在篇首提出的第一個問題,即選擇何種數據結構存儲Mysql結果集的答案。下一篇文章將研究第二個問題,即數據結構鍵的標識符選擇問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章