【Redis实战】存储24小时内的操作历史

分析与实现

一个消息应用有如下需求:

  1. 需要保留用户最近24小时内的所有操作历史
  2. 需要频繁的显示最近的10条操作历史给用户
  3. 需要频繁的显示24小时内总操作次数

这是一个典型的适合应用Redis的情形。现在需要考虑的是如何具体实现,首先想到的是采用字典(哈希表)列表的方式实现,其中每一个哈希表存储操作内容和操作时间,同时包含一个过期时间。但是考虑到Redis支持设置键的过期时间,所以哈希表本身可以不用存储过期时间,而是在创建的时候设置好过期时间即可。

我们给操作历史列表取名为history,之后每当有新的操作发生的时候我们需要在列表中新增一项,同时创建一个新的哈希表:

127.0.0.1:6379> RPUSH history operation:1
(integer) 1
127.0.0.1:6379> HMSET operation:1 time 12:23 desc o1
OK
127.0.0.1:6379> EXPIRE operation:1 30
(integer) 1

这样,当需要统计总操作数或者需要显示最近的10条记录的时候,我们需要先遍历history列表,然后删除掉其中已经过期的哈希表的键,最后再返回总条数或者最近10条记录。

为了减少网络传输时间,我们将删除过期键的功能使用Lua脚本实现并加载到Redis(>2.6.0)中。Lua脚本:

while (true)
do
if (redis.call('EXISTS', redis.call('LRANGE', KEYS[1], 0, 0)[1]) == 1) then
break
else
if redis.call('LLEN', KEYS[1]) == 0 then
break
end
redis.call('LPOP', KEYS[1])
end
end
local ret = { 'ok' }
ret['ok'] = 'OK'
return ret

查看本例完整的Python实现可访问Gist

时间复杂度

首先列出我们使用的各个操作的时间复杂度:

操作 时间复杂度
LPUSH O(1)
HMSET O(N), Nfield-value 对的数量。
EXPIRE O(1)
EXISTS O(1)
LRANGE O(S+N), S 为偏移量 startN 为指定区间内元素的数量。
LLEN O(1)
LPOP O(1)

具体到我们实现的功能上来说,各个功能的时间复杂度如下:

功能 复杂度
新增一条操作记录 O(N),N为hash中key的数量
删除过期的记录 O(N),N为本次过期条数
取最新的10条记录 O(1)

参考链接

避免误用 Redis

Redis 命令参考

Redis Lua 脚本使用

Lua: A Guide for Redis Users

How to Create and Expire List Items in Redis

分享到