初学Redis(3)——简单实现Redis缓存中的排序功能

http://blog.csdn.net/qtyl1988/article/details/39545531

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

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

[plain] view plaincopy

  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 plaincopy

  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 plaincopy

  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 plaincopy

  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 plaincopy

  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中对结果集进行简单排序操作的功能就实现了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏菩提树下的杨过

java:hibernate + oracle之坑爹的clob

oracle + hibernate 环境,如果表中有 clob字段,hibernate的Entity类,如果Column注解打在私有成员上,则clob私有成员...

2939
来自专栏软件开发

MyBatis学习总结(二)——MyBatis核心配置文件与输入输出映射

在上一章中我们学习了《MyBatis学习总结(一)——ORM概要与MyBatis快速起步》,这一章主要是介绍MyBatis核心配置文件、使用接口+XML实现完整...

2563
来自专栏静晴轩

lua表排序

Lua作为一种很强大且轻量级脚本语言的存在,对于掌握其几乎无所不能的Table(其实就是一个Key Value的数据结构,它很像Javascript中的Obje...

48111
来自专栏北京马哥教育

AWK处理日志入门

前言 这两天自己挽起袖子处理日志,终于把AWK给入门了。其实AWK的基本使用,学起来也就半天的时间,之前总是靠同事代劳,惰性呀。 此文仅为菜鸟入门,运维们请勿...

3774
来自专栏Taylor技术日志

关于char/varchar(n)中n的探究:字符数or字节数

很多时候我们不确定某个字段的长度,会使用varchar类型,比如某个字段定义为varchar(100),那这100的长度能存多少个中文?

4497
来自专栏技术/开源

TypeScript设计模式之解释器

看看用TypeScript怎样实现常见的设计模式,顺便复习一下。 学模式最重要的不是记UML,而是知道什么模式可以解决什么样的问题,在做项目时碰到问题可以想到...

22410
来自专栏mySoul

设计模式-策略模式

将原先耦合的if进行拆分成类,然后使用的时候注入类即可。或者使用枚举类型,如果需要增加,需要实现抽象方法。

881
来自专栏区块链入门

【易错概念】Solidity语法constant/view/pure关键字定义

通过本文学习,熟悉了解以太坊智能合约语言Solidity语法中constant,view,pure的区别。

1643
来自专栏加米谷大数据

Redis数据存储优化机制详解

将一个对象存储在hash类型中会占用更少的内存,并且可以更方便的存取整个对象。省内存的原因是新建一个hash对象时开始是用zipmap来存储的。这个zipmap...

1222
来自专栏jouypub

MySQL中utf8和utf8mb4的区别

MySQL在5.5.3之后增加了这个utf8mb4的编码,mb4就是most bytes 4的意思,专门用来兼容四字节的unicode。好在utf8mb4是ut...

1718

扫码关注云+社区

领取腾讯云代金券