用Redis做Mysql數據庫緩存,必須解決2個問題。首先,應該肯定用何種數據結構存儲來自Mysql的數據;在肯定數據結構以後,還要考慮用什麼標識做爲該數據結構的鍵。mysql
直觀上看,Mysql中的數據都是按表存儲的;更微觀地看,這些表都是按行存儲的。每執行一次select查詢,Mysql都會返回一個結果集,這個結果集由若干行組成。因此,一個天然而然的想法就是在Redis中找到一種對應於Mysql行的數據結構。Redis中提供了五種基本數據結構,即字符串(string)、列表(list)、哈希(hash)、集合(set)和有序集合(sorted set)。通過調研,發現適合存儲行的數據結構有兩種,即string和hash。redis
要把Mysql的行數據存入string,首先須要對行數據進行格式化。事實上,結果集的每一行均可以看作若干由字段名和其對應值組成的鍵值對集合。這種鍵值對結構很容易讓咱們想起Json格式。所以,這裏選用Json格式做爲結果集每一行的格式化模板。根據這一想法,咱們能夠實現將結果集格式化爲若干Json對象,並將Json對象轉化爲字符串存入Redis的代碼:sql
[cpp] view plain copy 數據庫
// 該函數把結果集中的每一行轉換爲一個Json格式的字符串並存入Redis的STRING結構中, 緩存
// STRING鍵應該包含結果集標識符和STRING編號,形式如「cache.string:123456:1」 數據結構
string Cache2String(sql::Connection *mysql_connection, 函數
redisContext *redis_connection, spa
sql::ResultSet *resultset, .net
const string &resultset_id, int ttl) { code
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。這一想法的實現代碼以下:
[cpp] view plain copy
// 該函數把結果集中的每一行都存入一個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結果集的答案。下一篇文章將研究第二個問題,即數據結構鍵的標識符選擇問題。