Redis官网对Redis的定义是:“Redis is an open source, BSD licensed, advanced key-value cache and store”,可以看出,Redis是一种键值系统,可以用来缓存或存储数据。Redis是“Remote Dictionary Server”(远程字典服务)的缩写,提供了字符串(string),列表(list),哈希(hash),集合(set)和有序集合(sorted set)等5种数据结构,这些数据结构使它成为一种便于使用的键值系统。

        作为Redis提供的最简单的数据结构,字符串示意图及其常用命令如下图所示:


图1

        可见,Redis字符串类型实际上类似于C++中的map,一个键对应一个值。列表类型则是一种相对高级的数据结构,其示意图和常用命令如下图所示:

       

图2

        列表实际上由一个双向链表实现,所以在列表两端插入或删除数据效率极高,可以用于实现类似于微博、人人网上的“新鲜事”功能。 Redis提供的一个更有趣的结构称为哈希,其示意图和常用命令如下图所示:


图3

        一个哈希实际上是一组键值对的集合,哈希本身的键是“父键”,哈希中包含的若干字段是“子键”,每个“子键”都有相应的值。“子键”之间并没有一定的联系,但是它们共同组成了一个完整的哈希结构。我们可以把一个哈希看做关系数据库中的一行,哈希的每个子键对应行的一个字段。因此,当把关系数据库中的数据缓存至Redis时,使用哈希结构可能会带来方便。需要注意的是,哈希结构内部的子键之间是没有顺序关系的。Redis提供的另一个无顺序关系的结构是集合,其示意图和常用命令如下图所示:


图4

        集合中的数据满足唯一性和无序性。集合在Redis内部是用哈希表实现的,所以插入和删除操作的时间复杂度均为O(1)。Redis为集合提供了求交并差等运算命令,使用起来非常方便。与集合密切相关的另一个数据结构是有序集合,其示意图和常用命令如下图所示:


图5

        有序集合与集合的最大差异在于有序集合中的每个数据都有一个对应的分数,集合中的元素就是按照这些分数进行排序的。从结构功能和使用方法上来看,有序集合可能是Redis提供的5种数据结构中最高级的一种。

        Redis是一种内存数据库,这使其在数据存取效率方面表现突出。由于内存中的数据时刻面临丢失的危险,Redis提供了两种持久化机制,及时将内存中的数据写入硬盘。第一种机制为RDB,利用存储快照的方式把内存数据定期写入硬盘;第二种机制为AOF,利用写日志的方式,每执行一条更改Redis数据的命令,就在日志里附加上该命令并保存在硬盘上。Redis默认开启RDB模式,关闭AOF模式,这是因为AOF更加耗时。但是,如果对数据安全要求极高,无法承担任何数据丢失的后果,AOF模式就变成了持久化的首选。

        当然,即使Redis具备持久化机制,但是一旦本地硬盘损坏,数据丢失依然在所难免。所以,Redis又提供了复制功能,作用是将一个主数据库(master)的数据自动同步到多个从数据库(slave),从而尽可能防止数据丢失。Redis主从机制可以简单描述为:从数据库启动后,先向主数据库发送SYNC命令;主数据库接到SYNC命令后就开始保存快照,在此期间,所有发给主数据库的命令都被缓存起来;快照保存完成后,主数据库把快照和缓存的命令一起发给从数据库;从数据库保存主数据库发来的快照文件,并依次执行主数据库发来的缓存命令。在同步过程中,从数据库不会阻塞,它默认使用同步之前的数据继续响应客户端发来的命令。

        事务是Redis提供的特性之一。首先利用MULTI命令通知Redis,接下来的若干命令属于同一事务;然后输入若干命令,这些命令都被放入命令队列而不会被立即执行;最后,利用EXEC命令通知Redis,属于同一事务的所有命令均已输入完成,现在开始执行该事物。属于同一个事务的所有有效命令或者全部被执行,或者全部不执行,且在执行过程中不会插入其他命令。管道(pipeline)是Redis提供的另一个特性。该特性使Redis能够一次性接收多个命令,执行之后再一次性返回结果。这样能够减少客户端与Redis服务器的通信次数,从而降低往返时延。

        除事务和管道以外,Redis还提供了很多其他特性,如设置键的过期时间,利用BLPOP/BRPOP命令实现优先级队列,利用PUBLISH/SUBSCRIBE命令实现消息订阅和发布等,这里不再赘述。

        Redis由ANSI C写成,能够运行在包括Linux和OS X在内的大部分POSIX系统上,官方推荐的操作系统为Linux。Redis没有官方的Windows版本,但是微软移植并维护了一个能够运行在Windows上的Redis并放在github上(https://github.com/MSOpenTech/redis)。

        Redis的编程接口被称为客户端(clients),绝大部分主流编程语言都有官方推荐的客户端。下图中,具有Redis官方推荐客户端的语言标为绿色,没有官方推荐客户端的标为蓝色:


图6

        由于Redis没有官方推荐的C++客户端,因此可以使用C客户端予以代替。官方推荐的C客户端是hiredis,可以从github上找到(https://github.com/redis/hiredis)。

        作为一款功能强大,效率极高的NoSQL数据库,Redis已被包括新浪微博,知乎,Stackoverflow,暴雪和Flickr在内的多家公司成功用于实战。

注:图1-5均来自《Redis in Action》,Josiah L.Carlson, Manning Publications, 2013


我们用RedisMySQL数据库缓存举例:

RedisMySQL数据库缓存,必须解决2个问题。首先,应该确定用何种数据结构存储来自Mysql的数据;在确定数据结构之后,还要考虑用什么标识作为该数据结构的键。

        直观上看,Mysql中的数据都是按表存储的;更微观地看,这些表都是按行存储的。每执行一次select查询,Mysql都会返回一个结果集,这个结果集由若干行组成。所以,一个自然而然的想法就是在Redis中找到一种对应于Mysql行的数据结构。Redis中提供了五种基本数据结构,即字符串(string)、列表(list)、哈希(hash)、集合(set)和有序集合(sorted set)。经过调研,发现适合存储行的数据结构有两种,即string和hash。

        要把Mysql的行数据存入string,首先需要对行数据进行格式化。事实上,结果集的每一行都可以看做若干由字段名和其对应值组成的键值对集合。这种键值对结构很容易让我们想起Json格式。因此,这里选用Json格式作为结果集每一行的格式化模板。根据这一想法,我们可以实现将结果集格式化为若干Json对象,并将Json对象转化为字符串存入Redis的代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  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. }  


        要把Mysql的行数据存入hash,过程要比把数据存入string直观很多。这是由hash的结构性质决定的——hash本身就是一个键值对集合:一个“父键”下面包含了很多“子键”,每个“子键”都对应一个值。根据前面的分析可知,结果集中的每一行实际上也是键值对集合。用Redis键值对集合表示Mysql键值对集合应该再合适不过了:对于结果集中的某一行,字段对应于hash的“子键”,字段对应的值就是hash“子键”对应的值,即结果集的一行刚好对应一个hash。这一想法的实现代码如下:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  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. }  

        至此,我们已经给出了两种存储Mysql结果集的方案,这就是我们在篇首提出的第一个问题,即选择何种数据结构存储Mysql结果集的答案。下一个将研究第二个问题,即数据结构键的标识符选择问题。


MySQL结果集缓存到Redis的字符串或哈希结构中以后,我们面临一个新的问题,即如何为这些字符串或哈希命名,也就是如何确定它们的键。因为这些数据结构所对应的行都属于某个结果集,假如可以找到一种唯一标识结果集的方法,那么只需为这些数据结构分配一个唯一的序号,然后把结果集标识符与该序号结合起来,就能唯一标识一个数据结构了。于是,为字符串和哈希命名的问题就转化为确定结果集标识符的问题。

        经过调研,发现一种较为通用的确定结果集标识符的方法。正如我们所知道的,缓存在Redis中的结果集数据都是利用select等sql语句从Mysql中获取的。同样的查询语句会生成同样的结果集(这里暂时不讨论结果集中每条记录的顺序问题),这一性质刚好可以用来确定结果集的唯一标识符。当然,简单地把整个sql语句作为结果集标识符是不可取的,一个显而易见的理由是,未经处理的sql查询语句均包含若干空格,而Redis的键是不允许存在空格的。这时,我们需要一个可以把sql语句转换为唯一标识符的函数。通常,这一功能由散列函数完成,包括MD5,SHA系列等加密散列函数在内的很多算法均可达到这一目的。

        确定结果集标识符之后,从Redis读数据或向Redis写数据的思路就很清晰了。对于一个sql语句格式的数据请求,首先计算该语句的MD5并据此得到结果集标识符,然后利用该标识符在Redis中查找该结果集。注意,结果集中的每一行都有一个相应的键,这些键都存储在一个Redis集合结构中。这个集合恰好对应了所需的结果集,所以,该集合的键必须包含结果集标识符。如果Redis中不存在这样一个集合,说明要找的结果集不在Redis中,所以需要执行相应的sql语句,在Mysql中查询到相应的结果集,然后按照上面所说的办法把结果集中的每一行以字符串或哈希的形式存入Redis。在Redis中查找相应结果集的代码如下:

[cpp]  view plain  copy
  1. // 该函数根据sql语句在Redis中查询相应的结果集,并返回结果集中每一行所对应的数据结构的键  
  2. vector<string> GetCache(sql::Connection *mysql_connection,  
  3.                       redisContext *redis_connection,  
  4.                       const string &sql, int ttl, int type) {  
  5.   vector<string> redis_row_key_vector;  
  6.   string resultset_id = md5(sql);  // 计算sql语句的md5,这是唯一标识结果集的关键  
  7.   // type==1时,该函数将查询相应的STRING集合或将结果集写入若干STRING  
  8.   string cache_type = (type == 1) ? "string" : "hash";  
  9.   // 根据type信息和结果集标识符合成SET键  
  10.   string redis_row_set_key = "resultset." + cache_type + ":" + resultset_id;  
  11.   redisReply *reply;  
  12.   // 尝试从reply中获取SET中保存的所有键  
  13.   reply = static_cast<redisReply*>(redisCommand(redis_connection,   
  14.                                                "SMEMBERS %s",  
  15.                                                redis_row_set_key.c_str()));  
  16.   if (reply->type == REDIS_REPLY_ARRAY) {  
  17.     // 如果要找的SET不存在,说明Redis中没有相应的结果集,需要调用Cache2String或  
  18.     // Cache2Hash函数把数据从Mysql拉取到Redis中  
  19.     if (reply->elements == 0) {  
  20.       freeReplyObject(reply);  
  21.       sql::Statement *stmt = mysql_connection->createStatement();  
  22.       sql::ResultSet *resultset = stmt->executeQuery(sql);  
  23.       if (type == 1) {  
  24.         redis_row_set_key = Cache2String(mysql_connection, redis_connection,  
  25.                                          resultset, resultset_id, ttl);  
  26.       } else {  
  27.         redis_row_set_key = Cache2Hash(mysql_connection, redis_connection,   
  28.                                        resultset, resultset_id, ttl);  
  29.       }  
  30.       // 再次尝试从reply中获取SET中保存的所有键  
  31.       reply = static_cast<redisReply*>(redisCommand(redis_connection,   
  32.                                                    "SMEMBERS %s",  
  33.                                                    redis_row_set_key.c_str()));  
  34.       delete resultset;  
  35.       delete stmt;  
  36.     }  
  37.     // 把SET中的每个STRING或HASH键存入redis_row_key_vector中  
  38.     string redis_row_key;  
  39.     for (int i = 0; i < reply->elements; ++i) {  
  40.       redis_row_key = reply->element[i]->str;  
  41.       redis_row_key_vector.push_back(redis_row_key);  
  42.     }  
  43.     freeReplyObject(reply);  
  44.   } else {  
  45.     freeReplyObject(reply);  
  46.     throw runtime_error("FAILURE - SMEMBERS error");  
  47.   }  
  48.   return redis_row_key_vector;  
  49. }  

        现在我们已经掌握了确定Redis中的结果集标识符以及各数据结构的键的方法。下一个将研究结果集在Redis中的排序和分页问题。


在实现缓存排序功能之前,必须先明白这一功能的合理性。不妨思考一下,既然可以在数据库中排序,为什么还要把排序功能放在缓存中实现呢?这里简单总结了两个原因:首先,排序会增加数据库的负载,难以支撑高并发的应用;其次,在缓存中排序不会遇到表锁定的问题。Redis恰好提供了排序功能,使我们可以方便地实现缓存排序。

        Redis中用于实现排序功能的是SORT命令。该命令提供了多种参数,可以对列表,集合和有序集合进行排序。SORT命令格式如下:

[plain]  view plain  copy
  1. SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC | DESC] [ALPHA] [STORE destination]  

        BY参数用于指定排序字段,功能类似于SQL中的order by。对于列表和集合而言,仅按照它们的值进行排序往往没有实际意义。以函数Cache2Hash返回的集合为例(实际上返回的是集合键),该集合中存储的是一系列完整的哈希键,只按照这些键进行排序,结果无非是按照数字或字典顺序排列,其用处显然不大。这是因为真正存储行数据的是哈希结构本身,而非哈希键。假设集合键为"resultset.hash:123456",集合中每个哈希键对应的哈希结构中都有一个名为“timestamp”的字段,现在要把集合中的所有哈希键按照timestamp字段进行排序,这时,只需执行以下命令:

[plain]  view plain  copy
  1. SORT resultset.hash:123456 BY *->timestamp  

        从上例可以看出,BY的真正威力在于它可以让SORT命令按照一个指定的外部键的外部字段进行排序。SORT用集合 resultset.hash:123456中的每个值(即每个哈希键)替换BY参数后的第一个“*”,并依据“->”后面给出的字段获取其值,最后根据这些字段值对哈希键进行排序。

        LIMIT参数用于限制排序以后返回元素的数量,功能类似于SQL中的limit。该参数接受另外两个参数,即offset和count,LIMIT offset count表示跳过前offset个元素,返回之后的连续count个元素。可见,LIMIT参数可以用于实现分页功能。

        GET参数用于返回指定的字段值。以集合resultset.hash:123456为例,使用BY参数对集合中的所有哈希键按照哈希结构中的timestamp字段排序后,SORT命令返回所有排序之后的哈希键。如果某个请求需要不是键而是某些字段值,这时就要使用GET参数,使SORT命令返回指定字段值。假设除timestamp字段以外,集合中每个哈希键对应的哈希结构中还有一个名为“id”的字段,通过以下命令可以使SORT返回按照timestamp排序以后的每个哈希键对应的哈希结构中的timestamp和id值:

[plain]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. SORT resultset.hash:123456 BY *->timestamp GET *->timestamp GET *->id  

        SORT用 集合 resultset.hash:123456中的每个值(即每个哈希键)替换GET参数之后的第一个“*”,并将其作为返回值。值得注意的是,利用GET #能够得到集合中的哈希键本身。

        ASC和DESC参数用于指定排序顺序(默认为ASC,即从低到高),ALPHA参数用于按照字典顺序排列非数字元素。

        STORE参数用于将SORT命令的返回值,即排序结果存入一个指定的列表。加上STORE参数后,SORT命令的返回值就变为排序结果的个数。

        下面的代码实现了按照哈希的某个字段对集合中的哈希键排序,并将结果存入列表的过程:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // 该函数对集合中的所有HASH键进行排序,排序依据是HASH键所对应的HASH中的某个字段,  
  2. // 排序结果被存入一个LIST结构,LIST键应当包含结果集标识符和排序字段标识符,  
  3. // 形如“sorted:123456:1234”  
  4. string SortHash(sql::Connection *mysql_connection,  
  5.                 redisContext *redis_connection,   
  6.                 const string &resultset_id,   
  7.                 const string &sort_field,   
  8.                 int offset, int count, int order, int ttl) {  
  9.   // 只考虑存储HASH键的SET  
  10.   string redis_row_set_key = "resultset.hash:" + resultset_id;  
  11.   redisReply *reply;  
  12.   // 检测SET是否存在  
  13.   reply = static_cast<redisReply*>(redisCommand(redis_connection,   
  14.                                                "EXISTS %s",  
  15.                                                redis_row_set_key.c_str()));  
  16.   if (reply->integer == 0) {  
  17.     freeReplyObject(reply);  
  18.     throw runtime_error("FAILURE - no resultsets");  
  19.   } else {  
  20.     freeReplyObject(reply);  
  21.   }  
  22.   string field_md5 = md5(sort_field);  // 利用MD5排除排序字段中空格造成的影响   
  23.   // 将排序结果存入该LIST  
  24.   string redis_sorted_list_key = "sorted:" + resultset_id + ":" + field_md5;  
  25.   string by("*->" + sort_field);  //确定排序字段  
  26.   string ord = (order == 1) ? "ASC" : "DESC";  //order==1时按照升序排列;否则为降序  
  27.   stringstream ofsstream, cntstream;  
  28.   ofsstream << offset;  
  29.   cntstream << count;  
  30.   // 执行排序命令,并把排序结果存入LIST  
  31.   reply = static_cast<redisReply*>(redisCommand(  
  32.                                       redis_connection,   
  33.                                       "SORT %s BY %s LIMIT %s %s GET %s ALPHA STORE %s",  
  34.                                       redis_row_set_key.c_str(),   
  35.                                       by.c_str(),   
  36.                                       ofsstream.str().c_str(),   
  37.                                       cntstream.str().c_str(),   
  38.                                       "#",   
  39.                                       redis_sorted_list_key.c_str()));  
  40.   freeReplyObject(reply);  
  41.   stringstream ttlstream;  
  42.   ttlstream << ttl;  
  43.   // 设置LIST的过期时间  
  44.   reply = static_cast<redisReply*>(redisCommand(redis_connection,   
  45.                                                "EXPIRE %s %s",  
  46.                                    redis_sorted_list_key.c_str(),   
  47.                                                ttlstream.str().c_str()));  
  48.   freeReplyObject(reply);  
  49.   return redis_sorted_list_key;  // 返回LIST键,以便于其他函数获取该LIST中的内容  

        显然,对结果集中的哈希键进行排序要比对字符串键排序更加直观和方便。借助于排序函数,可以方便地实现在Redis中查询排序后的结果集,代码如下:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // 该函数根据sql语句和排序参数,在Redis中查询相应的结果集并进行排序,最后返回  
  2. // 排序之后的HASH键  
  3. vector<string> GetSortedCache(sql::Connection *mysql_connection,  
  4.                               redisContext *redis_connection,  
  5.                               const string &sql, const string &sort_field,   
  6.                               int offset, int count, int order, int ttl) {  
  7.   vector<string> redis_row_key_vector;  
  8.   redisReply *reply;  
  9.   string resultset_id = md5(sql);  // 结果集标识符  
  10.   string field_md5 = md5(sort_field);  // 排序字段标识符  
  11.   // 尝试获取LIST中的所有HASH键  
  12.   string redis_sorted_list_key = "sorted:" + resultset_id + ":" + field_md5;  
  13.   // 尝试获取LIST中的所有HASH键  
  14.   reply = static_cast<redisReply*>(redisCommand(redis_connection,   
  15.                                                "LRANGE %s %s %s",  
  16.                                                redis_sorted_list_key.c_str(),   
  17.                                                "0",   
  18.                                                "-1"));  
  19.   if (reply->type == REDIS_REPLY_ARRAY) {  
  20.     // 如果LIST不存在,调用Cache2Hash函数从Mysql中拉取数据到Redis,然后调用SortHash函数  
  21.     // 对结果集进行排序并将排序后的HASH键存入LIST  
  22.     if (reply->elements == 0) {   
  23.       freeReplyObject(reply);  
  24.       sql::Statement *stmt = mysql_connection->createStatement();  
  25.       sql::ResultSet *resultset = stmt->executeQuery(sql);  
  26.       Cache2Hash(mysql_connection, redis_connection, resultset,   
  27.                  resultset_id, ttl);  
  28.       redis_sorted_list_key = SortHash(mysql_connection, redis_connection,   
  29.                                        resultset_id, sort_field, offset,   
  30.                                        count, order, ttl);  
  31.       // 再次尝试获取LIST中的所有HASH键  
  32.       reply = static_cast<redisReply*>(redisCommand(  
  33.                                           redis_connection,   
  34.                                           "LRANGE %s %s %s",  
  35.                                           redis_sorted_list_key.c_str(),   
  36.                                           "0",   
  37.                                           "-1"));  
  38.       delete resultset;  
  39.       delete stmt;  
  40.     }  
  41.     // 将LIST中的所有HASH键存入redis_row_key_vector中  
  42.     string redis_row_key;  
  43.     for (int i = 0; i < reply->elements; ++i) {  
  44.       redis_row_key = reply->element[i]->str;  
  45.       redis_row_key_vector.push_back(redis_row_key);  
  46.     }  
  47.     freeReplyObject(reply);  
  48.   } else {  
  49.     freeReplyObject(reply);  
  50.     throw runtime_error("FAILURE - LRANGE error");  
  51.   }  
  52.   return redis_row_key_vector;  
  53. }  

        这样,在Redis中对结果集进行简单排序操作的功能就实现了。



Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐