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

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

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

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字段进行排序,这时,只需执行以下命令:

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值:

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

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

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

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

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

// 该函数对集合中的所有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中查询排序后的结果集,代码如下:

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

时间: 2024-10-01 03:12:15

初学Redis(4)——简单实现Redis缓存中的排序功能的相关文章

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

1.在实现缓存排序功能之前,必须先明白这一功能的合理性.不妨思考一下,既然可以在数据库中排序,为什么还要把排序功能放在缓存中实现呢?这里简单总结了两个原因:首先,排序会增加数据库的负载,难以支撑高并发的应用:其次,在缓存中排序不会遇到表锁定的问题.Redis恰好提供了排序功能,使我们可以方便地实现缓存排序. 2. 应用Redis实现数据的读写,同时利用队列处理器定时将数据写入mysql.3. 在redis启动时去mysql读取所有表键值存入redis中,往redis写数据时,对redis主键自增

Ubuntu18.04,安装Redis和简单使用Redis

前言 Redis是常用基于内存的Key-Value数据库,比Memcache更先进,支持多种数据结构,高效,快速.用Redis可以很轻松解决高并发的数据访问问题:作为实时监控信号处理也非常不错. 环境 Ubuntu 18.04 安装Redis服务器端 ~ sudo apt-get install redis-server 安装完成后,Redis服务器会自动启动,我们检查Redis服务器程序 检查Redis服务器系统进程 ~ ps -agx|grep redis 1 10382 10382 103

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

1.Redis的简单了解 Redis是一种高性能的分布式NoSql数据库,持久存储,高并发,数据类型丰富,通过现场申请内存空间,同时可以配置虚拟内存.五种数据类型:string(字符串,这种格式和memcache是一致的).hash(哈希).list(列表,包括队列和栈).set(集合).zset(有序集合),使用key-value对的时候,可以直接存入对象,无需进行序列化,默认的使用了.net中自己的序列化,进行处理,所以也会出现对象循环引用的问题,此时需要使用json.net序列化为字符串,

关于ehcache缓存的使用(简单对比redis)

前言 最近在做一个项目,某个接口查询数据到返回数据总计需要7000+毫秒,一直在考虑优化的问题,优化也有原来的一家一家查询到一次查询所有的,在查询不同天数.结果是1500+,虽然优化了不少,但是数据结构会变化,前台渲染数据会更加麻烦,暂时也没有更新.所以后来就采用了缓存的机制,查询的数据缓存10小时,虽然第一次查询比较慢,但是以后会好很多. 正文 1. 关于选型:redis or ehcache 在使用缓存的时候,第一反应是:redis,但是后来还是决定使用Ehcache,ehcache主要是用

CentOS6.4 安装OpenResty和Redis 并在Nginx中利用lua简单读取Redis数据

1.下载OpenResty和Redis OpenResty下载地址:wget http://openresty.org/download/ngx_openresty-1.4.3.6.tar.gz Redis下载地址:wget http://download.redis.io/releases/redis-2.8.6.tar.gz 2.安装依赖包 yum install -y gcc gcc-c++ readline-devel pcre-devel openssl-devel tcl perl

初学Redis(3)——用Redis作为Mysql数据库的缓存

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

用Redis作为Mysql数据库的缓存【转】

用Redis作Mysql数据库缓存,必须解决2个问题.首先,应该确定用何种数据结构存储来自Mysql的数据:在确定数据结构之后,还要考虑用什么标识作为该数据结构的键. 直观上看,Mysql中的数据都是按表存储的:更微观地看,这些表都是按行存储的.每执行一次select查询,Mysql都会返回一个结果集,这个结果集由若干行组成.所以,一个自然而然的想法就是在Redis中找到一种对应于Mysql行的数据结构.Redis中提供了五种基本数据结构,即字符串(string).列表(list).哈希(has

Redis整合Spring结合使用缓存实例

摘要:本文介绍了如何在Spring中配置redis,并通过Spring中AOP的思想,将缓存的方法切入到有需要进入缓存的类或方法前面. 一.Redis介绍 什么是Redis? redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set –有序集合)和hash(哈希类型).这些数据类型都支持push/pop.add/remove及取交集并集和差集及更丰富的操作

Redis 一二事 - 在spring中使用jedis 连接调试单机redis以及集群redis

Redis真是好,其中的键值用起来真心强大啊有木有, 之前的文章讲过搭建了redis集群 那么咋们该如何调用单机版的redis以及集群版的redis来使用缓存服务呢? 先讲讲单机版的,单机版redis安装非常简单,不多说了,直接使用命令: 1 [[email protected] bin]# ./redis-server redis.conf 启动就行 在sprig文件中配置如下 1 <!-- 2 TODO: 3 开发环境使用单机版 4 生产环境务必切换成集群 5 --> 6 <!--