如何处理基于Redis的会话过期?

14

我想实现基于Redis的会话存储。我想把会话数据放入Redis中,但是我不知道如何处理会话过期。我可以循环遍历所有Redis键(sessionid),并评估最后访问时间和最大空闲时间,因此需要将所有键加载到客户端中,可能存在1亿个会话密钥,这可能会导致I/O性能非常差。
我希望让Redis来管理过期时间,但是在键过期时没有监听器或回调函数,因此无法触发HttpSessionListener。有什么建议吗?


不是在Redis中,但您可能想看看Tarantool中的实现方式: https://github.com/mailru/tntlua/blob/master/expirationd.lua 简而言之,在Tarantool中,您可以在数据库中运行自己的Lua脚本,并在其中设置自己的过期策略。不需要外部守护程序。 - Kostja
1个回答

42

因此,您需要在Redis中的会话过期时通知应用程序。

虽然Redis不支持此功能,但有许多技巧可以用来实现它。

更新:从版本2.8.0开始,Redis支持此http://redis.io/topics/notifications

首先,有人正在思考这个问题:尽管这仍在讨论中,但它可能会被添加到Redis的未来版本中。请参见以下问题:

现在,这里是一些可以在当前Redis版本中使用的解决方案。

解决方案1:修补Redis

实际上,当Redis执行密钥过期时,添加简单的通知并不难。可以通过在Redis源代码的db.c文件中添加10行来实现。以下是一个示例:

https://gist.github.com/3258233

如果键已过期且以“@”字符(任意选择)开头,则此短补丁将在#expired列表中发布关键字。它可以轻松地适应您的需求。

然后,使用EXPIRE或SETEX命令为会话对象设置到期时间,并编写一个小型守护程序,该守护程序在BRPOP上循环以从“#expired”列表中出列,并在您的应用程序中传播通知。

重要的一点是了解Redis中过期机制的工作原理。实际上有两个不同的过期路径同时活动:

  • 惰性(被动)机制。每次访问键时,都可能发生过期。

  • 主动机制。内部作业定期(随机)对带有过期设置的多个键进行采样,尝试查找要过期的键。

请注意,上述补丁可与两条路径配合使用。

其结果是Redis到期时间不准确。如果所有键都有过期时间,但只有一个键即将过期,并且未被访问,则主动过期作业可能需要几分钟才能找到该键并使其过期。如果需要某些准确性的通知,则无法这样做。

解决方案2:使用zsets模拟过期

这里的想法是不依赖于Redis键过期机制,而是通过使用附加索引和轮询守护程序来模拟它。它可以在未修改的Redis 2.6版本中工作。

每次将会话添加到Redis时,可以运行:

MULTI
SET <session id> <session content>
ZADD to_be_expired <current timestamp + session timeout> <session id>
EXEC

to_be_expired有序集合仅是访问应该过期的第一个键的高效方式。 守护进程可以使用以下Lua服务器端脚本对to_be_expired进行轮询:

local res = redis.call('ZRANGEBYSCORE',KEYS[1], 0, ARGV[1], 'LIMIT', 0, 10 )
if #res > 0 then
   redis.call( 'ZREMRANGEBYRANK', KEYS[1], 0, #res-1 )
   return res
else
   return false
end

启动脚本的命令是:

EVAL <script> 1 to_be_expired <current timestamp>

守护进程最多获取10个项目。对于每个项目,它必须使用DEL命令删除会话,并通知应用程序。如果实际处理了一个项目(即Lua脚本的返回值不为空),则守护进程应立即循环,否则可以引入1秒等待状态。

由于Lua脚本的存在,可以并行启动多个轮询守护进程(该脚本保证给定会话仅被处理一次,因为键是由Lua脚本本身从to_be_expired中删除的)。

解决方案3:使用外部分布式定时器

另一种解决方案是依赖于外部分布式定时器。 beanstalk轻量级排队系统 是一个很好的选择。

每当系统添加会话时,应用程序将会话ID发布到beanstalk队列,并设置与会话超时相对应的延迟时间。守护进程正在监听队列。当它可以出列项目时,意味着会话已过期。它只需清除Redis中的会话并通知应用程序即可。


非常棒的答案 - 非常感谢!不过您能澄清一下这句话吗:“守护进程应立即循环,否则可以引入1秒等待状态”。在这种情况下,循环是什么意思 - 为什么/在哪里引入了这个1秒等待? - Alex Dean
守护进程是常驻程序,有时会在系统中执行某些活动。由于它们始终在运行,大部分代码都包含在主循环中。现在,守护进程还需要一个等待状态,以避免在循环时占用100%的CPU。Redis中没有与zset相关联的阻塞命令(不像list的BLPOP/BRPOP),因此必须通过轮询和睡眠来模拟,如果没有返回,则睡眠。 - Didier Spezia
2
这已经在Redis中实现了。那些问题已经关闭了。如果有人更新这个答案会很好。 - Farid Nouri Neshat
@DidierSpezia 为什么在解法2中我们需要一个multi(多)? - Ankit Gupta
将SET和ZADD命令捆绑在一起,并确保它们被原子地执行。这还可以提高时间戳的准确性,因为zset是在SET之后维护的。 - Didier Spezia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接