一架梯子,一头程序猿,仰望星空!
Redis面试题 > 内容正文

Redis计数器怎么实现? 如何避免计数器出现负数?


问题简答

Redis可以通过INCR和DECR命令实现计数器的功能。通过INCR命令可以对指定的key进行自增操作,DECR命令可以对指定的key进行自减操作,可以在递减的时候检测避免出现负数,检查逻辑可以基于Redis+lua机制,也可以在业务代码做检测。

问题详解:

1.Redis计数器实现方案

1.1.INCR命令

使用Redis内置的INCR和INCRBY命令可以实现对计数器的自增和自减,是最简单和高效的方案之一。

1.2.HyperLogLog

Redis的HyperLogLog数据类型可以用于实现计数器,用于统计一个数据集合中的元素个数,计数时使用PFADD命令将元素添加到HyperLogLog中,使用PFCOUNT命令计算元素个数的近似值。

1.3.方案对比

  • INCR命令实现计数器,简单,但是无法去重,例如统计每天独立IP数量。
  • HyperLogLog实现计数器,支持海量数据统计,占用内存小,但是计数器是估算值,支持去重,例如统计每天独立IP数量。

2.简单计数器

> SET counter 0
OK
> INCR counter
(integer) 1
> INCR counter
(integer) 2

3.基于HyperLogLog的计数器

假设有一个在线游戏,需要统计每天登录游戏的独立用户数。

使用PFADD命令,用户登录的时候,添加用户ID即可,game:loginday1 是key

PFADD game:loginday1 user1 user2 user3 user4 user5

再添加一个

PFADD game:loginday1 user6

使用PFCOUNT统计数量

PFCOUNT game:loginday1

4.避免计数器出现负数

HyperLogLog计数器不会出现负数,DECR递减命令,可能会导致负数,避免负数的核心思路就是在递减之前做下条件检测。

Redis支持LUA脚本,下面是基于lua脚本避免负数的例子

-- 如果计数器 key 不存在,则初始化为 0
if redis.call('exists', KEYS[1]) == 0 then
    redis.call('set', KEYS[1], 0)
end

-- 增加计数器值,并检查是否超过最大值
local count = redis.call('incrby', KEYS[1], ARGV[1])
if count < 0 then
    redis.call('set', KEYS[1], 0)
    count = 0
end

return count

这是一段lua脚本避免计数器出现负数的代码,实际业务场景,会把这段代码封装下,执行计数器请求的时候一起执行。

提示:除了基于lua脚本,也可以在业务代码检测负数。