用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结果集的答案。下一篇文章将研究第二个问题,即数据结构键的标识符选择问题。