初學Redis(2)——用Redis作為Mysql資料庫的緩衝,redismysql
用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結果集的答案。下一篇文章將研究第二個問題,即資料結構鍵的標識符選擇問題。
怎使用redis做mysql的緩衝
應用Redis實現資料的讀寫,同時利用隊列處理器定時將資料寫入mysql。
同時要注意避免衝突,在redis啟動時去mysql讀取所有表索引值存入redis中,往redis寫資料時,對redis主鍵自增並進行讀取,若mysql更新失敗,則需要及時清除緩衝及同步redis主鍵。
這樣處理,主要是即時讀寫redis,而mysql資料則通過隊列非同步處理,緩解mysql壓力,不過這種方法應用情境主要基於高並發,而且redis的高可用叢集架構相對更複雜,一般不是很推薦。
常用的記憶體快取資料庫redis 讀什?
中文發音:蕊黛絲