RedisMySQL数据库缓存,必须解决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. }
// 该函数把结果集中的每一行转换为一个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。这一想法的实现代码如下:

  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. }
// 该函数把结果集中的每一行都存入一个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(二)--用Redis作MySQL数据库缓存的更多相关文章

  1. 用Redis作Mysql数据库缓存

    使用redis作mysql数据库缓存时,需要考虑两个问题: 1.确定用何种数据结构存储来自Mysql的数据; 2.在确定数据结构之后,用什么标识作为该数据结构的键. 直观上看,Mysql中的数据都是按 ...

  2. MySql数据库缓存

    对MySql查询缓存及SQL Server过程缓存的理解及总结 一.MySql的Query Cache 1.Query Cache   MySQL Query Cache是用来缓存我们所执行的SELE ...

  3. mysql 数据库缓存调优之解决The total number of locks exceeds the lock table size错误

    环境: mysql5.6.2  主从同步(备注:需操作主库和从库) 一.InnoDB表执行大批量数据的更新,插入,删除操作时会出现这个问题,需要调整InnoDB全局的innodb_buffer_poo ...

  4. 【数据库】mysql数据库缓存

    配置文件在 /etc/mysql/my.cnf ################### 第一部分:查询数据库缓存相关变量 ################### show variables like ...

  5. python-操作MySQL数据库

    1.安装MySQLdb? 为了用DB-API编写MySQL脚本,必须确保已经安装了MySQL.复制以下代码,并执行: #!/usr/bin/python # -*- coding: UTF-8 -*- ...

  6. MySQL实验准备(二)--Python模拟数据(MySQL数据库)

    Python模拟数据(MySQL数据库) 数据模拟 目的:模拟多个表的插入和查询数据的模拟,再通过基准测试脚本测试服务器性能和收集数据,仿真模拟. 备注: 如果需要基础的python环境,可以查看&l ...

  7. python + docker, 实现天气数据 从FTP获取以及持久化(二)-- python操作MySQL数据库

    前言 在这一节中,我们主要介绍如何使用python操作MySQL数据库. 准备 MySQL数据库使用的是上一节中的docker容器 “test-mysql”. Python 操作 MySQL 我们使用 ...

  8. FLUME安装&环境(二):拉取MySQL数据库数据到Kafka

    Flume安装成功,环境变量配置成功后,开始进行agent配置文件设置. 1.agent配置文件(mysql+flume+Kafka) #利用Flume将MySQL表数据准实时抽取到Kafka a1. ...

  9. Redis(二)、Redis持久化RDB和AOF

    一.Redis两种持久化方式 对Redis而言,其数据是保存在内存中的,一旦机器宕机,内存中的数据会丢失,因此需要将数据异步持久化到硬盘中保存.这样,即使机器宕机,数据能从硬盘中恢复. 常见的数据持久 ...

随机推荐

  1. ESLint 配置说明

    ESLint 有什么用,为什么要使用?   ESLint是一套可自定义规则的JS代码检查与修复工具 目标是保存团队代码的一致性和避免错误并且修复错误.减少团队沟通成本   "no-alert ...

  2. Facebook在代码里下毒,百度身受重伤。。。

    白首相知犹按剑     前两天看到有朋友分享说,WordPress停用了react.今天,在逛知乎时看到了另一个问题别细看这图,我赌你看不懂... 嗯...用人话来说就是百度内部要求他们的程序猿不要再 ...

  3. vim IDE配置

    参考: http://www.cnblogs.com/witcxc/archive/2011/12/28/2304704.html http://www.cnblogs.com/ma6174/arch ...

  4. WPF背景图

    方法一:xaml中:<控件>  <控件.Background><ImageBrush ImageSource="/程序集;component/images/xx ...

  5. Oracle表被锁无法问题处理

    1:查出锁定表的信息SELECT s.sid, s.serial#, s.username, s.schemaname, s.osuser, s.process, s.machine,s.termin ...

  6. Script Browser & Script Analyzer 1.3更新发布

    感谢Windows PowerShell MVP Kirk Munro.Laurent Dardenne在过去三个星期内为我们提出的各种想法和建议.针对这些的建议,我们对Script Browser ...

  7. 更新ruby:Error running 'requirements_osx_brew_update_system ruby-2.4.1报错解决

    更新ruby时,报错: Failed to update Homebrew, follow instructions here: https://github.com/Homebrew/homebre ...

  8. Delphi读取不Word中不规则表格数据并转换成标准表格

    程序需要,需要将word中不规则的表格数据转换为标准的表格,即合并的单元格按正常格式解析,word中的表格格式如下: 解析后数据如下: 借鉴了网上代码,如下处理: procedure TfrmMain ...

  9. 基于Centos搭建个人 Leanote 云笔记本

    系统要求:CentOS 7.2 64 位操作系统 下载启动 MongoDB Leanote 依赖 MongoDB 作为数据存储,下面开始安装 MongoDB: 下载 MongoDB 进入 /home  ...

  10. Spring Boot优化

    针对目前的容器优化,目前来说没有太多地方,需要考虑如下几个点: 线程数 超时时间 jvm优化 首先线程数是一个重点,初始线程数和最大线程数,初始线程数保障启动的时候,如果有大量用户访问,能够很稳定的接 ...