当前位置:首页 > 问答 > 正文

Redis里头怎么简单算个总和,SUM求和那些事儿聊聊

首先得说清楚,Redis本身不是一个像MySQL那样的关系型数据库,它没有那种直接对一张表里某个字段进行SELECT SUM(column)的现成命令,你要是想实现类似的功能,得根据你数据存储的方式,用一些“曲线救国”的办法,这事儿得从头说起。

来源参考:主要是根据Redis官方文档中对String、Hash、Set、Sorted Set等数据结构的命令描述,以及如何组合使用它们来实现计数和汇总的思路。

最直接、最简单的求和,就是针对一个简单的数字键,你想统计网站的总访问量,这个最简单,你用SET命令初始化一下,比如SET total_visits 0,然后每次有人访问,你就用INCR命令给它加1:INCR total_visits,这个INCR是原子操作,多个客户端同时加也不会出错,要看总和吗?直接GET total_visits,出来的数字就是总和,这可以算是最基础的“求和”了,只不过我们是慢慢累加起来的。

但现实情况往往更复杂,你有一个电商平台,想计算某个用户购物车里所有商品的总价,这时候,一个简单的键就不够用了,我们会用Redis的Hash(哈希)结构来存储购物车,键是cart:user123,里面的字段(field)是商品ID,值(value)是商品数量。

假设用户user123的购物车是这样的:

  • 商品1001,数量2
  • 商品1002,数量1

存储命令大概是HSET cart:user123 1001 2 1002 1,现在问题来了,怎么算总价?你光有这个数量不行,还得知道每个商品的单价,单价存在哪儿呢?通常也会存在Redis里,可能是一个叫price:1001的键,用GET就能拿到。

求总和的思路就来了:先在Redis里用HGETALL cart:user123把这个用户购物车里的所有商品ID和数量取出来,你的应用程序(比如用Python、Java写的)遍历每一个商品ID,去Redis里查它的单价(比如GET price:1001),再用数量乘以单价,最后把所有商品的总价累加起来,就得到了整个购物车的总和。

来源参考:这种通过应用程序端进行多次查询并计算的方式,是Redis这类内存键值数据库处理聚合计算的常见模式,在Redis官方文档的用例部分有提及。

这个方法很直观,但有个缺点:如果用户购物车里的商品特别多,你的应用程序就需要向Redis发起很多次查询(每个商品查一次单价),这会有一定的网络开销,我们常称之为“N+1查询”问题,对于性能要求极高的场景,这可能是个麻烦。

那有没有办法在Redis内部完成计算呢?也有,这就用到了Lua脚本,Redis允许你写一小段Lua脚本,然后把它发送到Redis服务器上去执行,这个脚本在服务器端原子性地运行,期间不会被打断。

还拿购物车总价的例子说,你可以写一个Lua脚本,这个脚本做以下几件事:

  1. 通过HGETALL拿到购物车所有商品和数量。
  2. 在一个循环里,对每个商品ID,用GET命令查询其单价。
  3. 在脚本内部完成“数量 * 单价”的计算并累加。
  4. 最后返回累加的总和。

这样,你的应用程序只需要向Redis发送一次Lua脚本,然后直接拿到最终的总和结果,所有中间步骤都在Redis服务器内部完成,节省了网络往返的时间,而且保证了原子性(不会在计算过程中,购物车数据被其他命令修改),这算是比较高级但也更高效的求和方式了。

来源参考:Redis官方文档中对EVAL命令和Lua脚本集成有详细说明,明确指出Lua脚本用于在服务器端执行复杂逻辑和保证原子性。

除了上面这两种常见情况,还有一种是用Sorted Set(有序集合),比如你想统计一段时间内某个指标的总和,你可以把时间戳作为分数(score),把数值作为成员(member)加到Sorted Set里,但问题是,Sorted Set的命令主要是针对分数的范围操作(比如ZRANGEBYSCORE),它本身不提供直接的范围求和功能。

这时候,你可能又得靠Lua脚本了:先用ZRANGEBYSCORE把指定时间范围内的所有成员(也就是数值)取出来,然后在Lua脚本里遍历这些值,把它们加起来,Redis 6.2版本之后,引入了一个新命令ZRANGE配合WITHSCORES选项,可以更高效地获取成员和分数,但最终的求和逻辑通常还是需要在脚本或客户端完成。

还有一种“预聚合”的思路,你不需要实时得到精确的总和,可以接受短时间内的近似值或者小幅延迟,那么你可以不用每次操作都去更新总和,你可以让应用程序先累加一段时间的变化量,比如每隔1秒钟,再把这一秒钟内增加的总量,通过INCRBY命令一次性加到一个表示总和的键上,这样,查询总和依然是一个简单的GET命令,但写入的压力就小了很多,这算是一种用空间(多一个键)和一点点延迟换性能的办法。

在Redis里“算个总和”没有万能钥匙,关键看你的数据是怎么存的,以及对实时性、准确性和性能的要求有多高。

  • 最简单的就是对一个计数器键用INCR/INCRBY,然后GET
  • 最常见的是结合Hash等结构,在应用程序里做查询和计算。
  • 最高效(对于复杂计算)的是使用Lua脚本在Redis服务器内部完成。
  • 最取巧的是采用预聚合的方式来降低实时计算的压力。

聊Redis的SUM求和,其实聊的是如何根据你的业务场景,巧妙地组合使用Redis提供的这些基础命令和特性。

Redis里头怎么简单算个总和,SUM求和那些事儿聊聊