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

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

  1. SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC | DESC] [ALPHA] [STORE destination]
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字段进行排序,这时,只需执行以下命令:

  1. SORT resultset.hash:123456 BY *->timestamp
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值:

  1. SORT resultset.hash:123456 BY *->timestamp GET *->timestamp GET *->id
SORT resultset.hash:123456 BY *->timestamp GET *->timestamp GET *->id

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

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

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

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

  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中的内容
// 该函数对集合中的所有HASH键进行排序,排序依据是HASH键所对应的HASH中的某个字段,
// 排序结果被存入一个LIST结构,LIST键应当包含结果集标识符和排序字段标识符,
// 形如“sorted:123456:1234”
string SortHash(sql::Connection *mysql_connection,
redisContext *redis_connection,
const string &resultset_id,
const string &sort_field,
int offset, int count, int order, int ttl) {
// 只考虑存储HASH键的SET
string redis_row_set_key = "resultset.hash:" + resultset_id;
redisReply *reply;
// 检测SET是否存在
reply = static_cast<redisReply*>(redisCommand(redis_connection,
"EXISTS %s",
redis_row_set_key.c_str()));
if (reply->integer == 0) {
freeReplyObject(reply);
throw runtime_error("FAILURE - no resultsets");
} else {
freeReplyObject(reply);
}
string field_md5 = md5(sort_field); // 利用MD5排除排序字段中空格造成的影响
// 将排序结果存入该LIST
string redis_sorted_list_key = "sorted:" + resultset_id + ":" + field_md5;
string by("*->" + sort_field); //确定排序字段
string ord = (order == 1) ? "ASC" : "DESC"; //order==1时按照升序排列;否则为降序
stringstream ofsstream, cntstream;
ofsstream << offset;
cntstream << count;
// 执行排序命令,并把排序结果存入LIST
reply = static_cast<redisReply*>(redisCommand(
redis_connection,
"SORT %s BY %s LIMIT %s %s GET %s ALPHA STORE %s",
redis_row_set_key.c_str(),
by.c_str(),
ofsstream.str().c_str(),
cntstream.str().c_str(),
"#",
redis_sorted_list_key.c_str()));
freeReplyObject(reply);
stringstream ttlstream;
ttlstream << ttl;
// 设置LIST的过期时间
reply = static_cast<redisReply*>(redisCommand(redis_connection,
"EXPIRE %s %s",
redis_sorted_list_key.c_str(),
ttlstream.str().c_str()));
freeReplyObject(reply);
return redis_sorted_list_key; // 返回LIST键,以便于其他函数获取该LIST中的内容

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

  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. }
// 该函数根据sql语句和排序参数,在Redis中查询相应的结果集并进行排序,最后返回
// 排序之后的HASH键
vector<string> GetSortedCache(sql::Connection *mysql_connection,
redisContext *redis_connection,
const string &sql, const string &sort_field,
int offset, int count, int order, int ttl) {
vector<string> redis_row_key_vector;
redisReply *reply;
string resultset_id = md5(sql); // 结果集标识符
string field_md5 = md5(sort_field); // 排序字段标识符
// 尝试获取LIST中的所有HASH键
string redis_sorted_list_key = "sorted:" + resultset_id + ":" + field_md5;
// 尝试获取LIST中的所有HASH键
reply = static_cast<redisReply*>(redisCommand(redis_connection,
"LRANGE %s %s %s",
redis_sorted_list_key.c_str(),
"0",
"-1"));
if (reply->type == REDIS_REPLY_ARRAY) {
// 如果LIST不存在,调用Cache2Hash函数从Mysql中拉取数据到Redis,然后调用SortHash函数
// 对结果集进行排序并将排序后的HASH键存入LIST
if (reply->elements == 0) {
freeReplyObject(reply);
sql::Statement *stmt = mysql_connection->createStatement();
sql::ResultSet *resultset = stmt->executeQuery(sql);
Cache2Hash(mysql_connection, redis_connection, resultset,
resultset_id, ttl);
redis_sorted_list_key = SortHash(mysql_connection, redis_connection,
resultset_id, sort_field, offset,
count, order, ttl);
// 再次尝试获取LIST中的所有HASH键
reply = static_cast<redisReply*>(redisCommand(
redis_connection,
"LRANGE %s %s %s",
redis_sorted_list_key.c_str(),
"0",
"-1"));
delete resultset;
delete stmt;
}
// 将LIST中的所有HASH键存入redis_row_key_vector中
string redis_row_key;
for (int i = 0; i < reply->elements; ++i) {
redis_row_key = reply->element[i]->str;
redis_row_key_vector.push_back(redis_row_key);
}
freeReplyObject(reply);
} else {
freeReplyObject(reply);
throw runtime_error("FAILURE - LRANGE error");
}
return redis_row_key_vector;
}

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

redis(四)--简单实现Redis缓存中的排序功能的更多相关文章

  1. 简单实现Redis缓存中的排序功能

    1.在实现缓存排序功能之前,必须先明白这一功能的合理性.不妨思考一下,既然可以在数据库中排序,为什么还要把排序功能放在缓存中实现呢?这里简单总结了两个原因:首先,排序会增加数据库的负载,难以支撑高并发 ...

  2. 使用Redis进行简单的数据缓存

    引入spring-data-redis包.jedis.connection-pool包 applicationContext.xml的配置 <!-- redis Connection --> ...

  3. Ubuntu18---安装Redis和简单使用Redis

    前言 Redis是常用基于内存的Key-Value数据库,比Memcache更先进,支持多种数据结构,高效,快速.用Redis可以很轻松解决高并发的数据访问问题:作为实时监控信号处理也非常不错. 环境 ...

  4. 四十七、在SAP中,把功能区块整合成一个函数,通过调用函数的办法使代码简洁明了

    一.我们查看上一次的代码,非常之凌乱,大体可以分为以下这几个区块 二.我们把最后的2个部分,用函数的方式来写,写法如下: 三.执行程序,和之前一样 四.输出结果

  5. Redis的简单了解以及主从复制

    1.Redis的简单了解 Redis是一种高性能的分布式NoSql数据库,持久存储,高并发,数据类型丰富,通过现场申请内存空间,同时可以配置虚拟内存.五种数据类型:string(字符串,这种格式和me ...

  6. 高并发简单解决方案————redis队列缓存+mysql 批量入库(ThinkPhP)

    问题分析 问题一:要求日志最好入库:但是,直接入库mysql确实扛不住,批量入库没有问题,done.[批量入库和直接入库性能差异] 问题二:批量入库就需要有高并发的消息队列,决定采用redis lis ...

  7. 简单的redis缓存操作(get、put)

    简单的redis缓存操作(get.put) 本文介绍简单的redis缓存操作,包括引入jedisjar包.配置redis.RedisDao需要的一些工具.向redis中放数据(put).从redis中 ...

  8. SpringBoot使用@Cacheable实现最简单的Redis缓存

    前言 之前我们使用过RedisTemplate来实现redis缓存,然后使用工具类来实现操作redis的存储.这样的方式好处是很自由,但是还不是最简单的处理方式.对于一些简单的应用来说,其实redis ...

  9. 简单封装Redis做缓存

    基于Redis封装一个简单的Python缓存模块 0. Docker Redis安装 参考: Get Docker CE for CentOS Docker 安装 Redis 安装Docker时错误s ...

随机推荐

  1. An entry point cannot be marked with the 'async' modifier

    I copied below code from this link.But when I am compiling this code I am getting an entry point can ...

  2. chmod不起作用的原因分析 - Linux下查看分区文件系统类型

    chmod不起作用 可能的原因: chmod对应的是windows下的一个磁盘分区,ntfs不支持linux权限 附:Linux下如何查看分区文件系统类型 1,fdisk -l fdisk -l 只能 ...

  3. 发现一个“佛系记账本”

    因为这是一款微信小程序,张小龙大力推崇的"用完即走"完美地适合记账应用. 不用下载.不用安装.不用注册.不用各种授权,只要从微信进入,就能记账,账本只与微信关联. 换手机.换PAD ...

  4. ZABBIX 4.0 LTS 部署

    1. 环境说明 关于zabbix的详细使用可以参考之前的3.0 版本,该文档仅记录zabbix 4.0 编译安装过程!ZABBIX 3.0 从入门到精通(zabbix使用详解) : https://w ...

  5. Deep Learning.ai学习笔记_第二门课_改善深层神经网络:超参数调试、正则化以及优化

    目录 第一周(深度学习的实践层面) 第二周(优化算法) 第三周(超参数调试.Batch正则化和程序框架) 目标: 如何有效运作神经网络,内容涉及超参数调优,如何构建数据,以及如何确保优化算法快速运行, ...

  6. oracle无效索引重建

    问题描述: 执行失败!错误信息[Exception message:无效的列索引 解决思路: 分析是表索引,大部分都是表索引失效导致的,只需要花重建表索引即可! 00.查看此表归属账户select * ...

  7. Mysql中的定时任务

    一.说明 后台周期定时任务可以有多种解决方案,我所知道的大概有以下几种: 后台框架自带定时任务.比如php中的Laravel框架里有提供定时任务操作接口,其他的框架大家可以单独针对了解. 服务器操作系 ...

  8. R语言|数据特征分析

    对数据进行质量分析以后,接下来可通过绘制图表.计算某些特征量等手段进行数据的特征分析. 主要通过分布分析.对比分析.统计量分析.周期性分析.贡献度分析.相关性分析等角度进行展开. 2.1 分布分析 分 ...

  9. MATLAB 统计不同区间中元素的个数

    使用 find 命令: x = :;%生成数组 k = find( x > & x < );%查找大于2小于5的元素的数组下标 size(k,) %统计的元素的个数

  10. Atitit 如何创新 创新只有在两种条件下发生:自由、效率。

    Atitit 如何创新 创新只有在两种条件下发生:自由.效率. 创新是如何发生的呢? 创新只有在两种条件下发生:自由.效率.在自由的环境下,对效率的追逐等于创新.如果你不自由,你的思想不够开阔,你脑洞 ...