1. 程式人生 > >初學Redis(2)——用Redis作為Mysql資料庫的快取

初學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的程式碼:

// 該函式把結果集中的每一行轉換為一個Json格式的字串並存入Redis的STRING結構中,
// STRING鍵應該包含結果集識別符號和STRING編號,形式如“cache.string:123456:1”
string Cache2String(sql::Connection *mysql_connection,
                    redisContext *redis_connection,
                    sql::ResultSet *resultset,
                    const string &resultset_id, int ttl) {
  if (resultset->rowsCount() == 0) {
    throw runtime_error("FAILURE - no rows");
  }
  // STRING鍵的字首,包含了結果集的識別符號
  string prefix("cache.string:" + resultset_id + ":");
  unsigned int num_row = 1;  // STRING編號,附加於STRING鍵的末尾,從1開始
  sql::ResultSetMetaData *meta = resultset->getMetaData();
  unsigned int num_col = meta->getColumnCount();
  // 將結果集中所有行對應的所有STRING鍵存入該SET,SET鍵包含了結果集的識別符號
  string redis_row_set_key("resultset.string:" + resultset_id);
  redisReply *reply;
  string ttlstr;
  stringstream ttlstream;
  ttlstream << ttl;
  ttlstr = ttlstream.str();
  resultset->beforeFirst();
  // 將結果集中的每一行轉為Json格式的字串,將這些Json字串存入STRING,
  // 每個STRING對應結果集中的一行
  while (resultset->next()) {
    string redis_row_key;  // STRING鍵名,由字首和STRING編號組成
    stringstream keystream;
    keystream << prefix << num_row;
    redis_row_key = keystream.str();
    Json::Value row;
    for (int i = 1; i <= num_col; ++i) {
      string col_label = meta->getColumnLabel(i);
      string col_value = resultset->getString(col_label);
      row[col_label] = col_value;
    }
    Json::FastWriter writer;
    string redis_row_value = writer.write(row);
	// 將STRING鍵及Json格式的對應值對存入Redis
    reply = static_cast<redisReply*>(redisCommand(redis_connection, 
                                                 "SET %s %s",
                                                 redis_row_key.c_str(), 
                                                 redis_row_value.c_str()));
    freeReplyObject(reply);
    // 將STRING鍵加入SET中
    reply = static_cast<redisReply*>(redisCommand(redis_connection, 
                                                 "SADD %s %s",
                                                 redis_row_set_key.c_str(), 
                                                 redis_row_key.c_str()));
    freeReplyObject(reply);
    // 設定STRING的過期時間
    reply = static_cast<redisReply*>(redisCommand(redis_connection, 
                                                 "EXPIRE %s %s",
                                                 redis_row_key.c_str(), 
                                                 ttlstr.c_str()));
    freeReplyObject(reply);
    ++num_row;
  }
  // 設定SET的過期時間
  reply = static_cast<redisReply*>(redisCommand(redis_connection, 
                                               "EXPIRE %s %s",
                                               redis_row_set_key.c_str(), 
                                               ttlstr.c_str()));
  freeReplyObject(reply);
  return redis_row_set_key;  // 返回SET鍵,以便於其他函式獲取該SET中的內容
}


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

// 該函式把結果集中的每一行都存入一個HASH結構。HASH鍵應當包括結果集識別符號和HASH編號,
// 形如“cache.string:123456:1”
string Cache2Hash(sql::Connection *mysql_connection,
                  redisContext *redis_connection,
                  sql::ResultSet *resultset,
                  const string &resultset_id, int ttl) {
  if (resultset->rowsCount() == 0) {
    throw runtime_error("FAILURE - no rows");
  }
  // HASH鍵的字首,包含了結果集的識別符號
  string prefix("cache.hash:" + resultset_id + ":");
  unsigned int num_row = 1;  // HASH編號,附加於HASH鍵的末尾,從1開始
  sql::ResultSetMetaData *meta = resultset->getMetaData();
  unsigned int num_col = meta->getColumnCount();
  // 將結果集中所有行對應的所有HASH鍵存入該SET,SET鍵包含了結果集的識別符號
  string redis_row_set_key("resultset.hash:" + resultset_id);
  redisReply *reply;
  string ttlstr;
  stringstream ttlstream;
  ttlstream << ttl;
  ttlstr = ttlstream.str();
  // 結果集中的每一行對應於一個HASH,將結果集的所有行都存入相應HASH中
  resultset->beforeFirst();
  while (resultset->next()) {
    string redis_row_key;  // HASH鍵名,由字首和HASH編號組成
    stringstream keystream;
    keystream << prefix << num_row;
    redis_row_key = keystream.str();
    for (int i = 1; i <= num_col; ++i) {
      string col_label = meta->getColumnLabel(i);
      string col_value = resultset->getString(col_label);
	  // 將結果集中一行的欄位名和對應值存入HASH
      reply = static_cast<redisReply*>(redisCommand(redis_connection,
                                                   "HSET %s %s %s",
                                                   redis_row_key.c_str(), 
                                                   col_label.c_str(),
                                                   col_value.c_str()));
      freeReplyObject(reply);
    }
	// 將HASH鍵加入SET中
    reply = static_cast<redisReply*>(redisCommand(redis_connection, 
                                                 "SADD %s %s",
                                                 redis_row_set_key.c_str(), 
                                                 redis_row_key.c_str())); 
    freeReplyObject(reply);
	// 設定HASH的過期時間
    reply = static_cast<redisReply*>(redisCommand(redis_connection, 
                                                 "EXPIRE %s %s",
                                                 redis_row_key.c_str(), 
                                                 ttlstr.c_str()));
    freeReplyObject(reply);
    ++num_row;
  }
  // 設定SET的過期時間
  reply = static_cast<redisReply*>(redisCommand(redis_connection, 
                                               "EXPIRE %s %s",
                                               redis_row_set_key.c_str(), 
                                               ttlstr.c_str()));
  freeReplyObject(reply);
  return redis_row_set_key;  // 返回SET鍵,以便於其他函式獲取該SET中的內容
}

        至此,我們已經給出了兩種儲存Mysql結果集的方案,這就是我們在篇首提出的第一個問題,即選擇何種資料結構儲存Mysql結果集的答案。下一篇文章將研究第二個問題,即資料結構鍵的識別符號選擇問題。