如何处理会话过期基于redis?

时间:2012-08-04 16:02:57

标签: session redis store

我想基于Redis实现会话存储。我想将会话数据放入Redis。但我不知道如何处理会话过期。我可以循环遍历所有redis密钥(sessionid)并评估上次访问时间和最大空闲时间,因此我需要将所有密钥加载到客户端,并且可能有1000m会话密钥并且可能导致非常差的I / O表演。
我想让Redis管理过期,但是当密钥到期时没有监听器或回调,所以不可能触发HttpSessionListener。有什么建议吗?

1 个答案:

答案 0 :(得分:36)

因此,当会话在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列表的密钥发布到#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 lightweight queuing system

每次在系统中添加会话时,应用程序都会将会话ID发布到beanstalk队列,其延迟对应于会话超时。守护程序正在侦听队列。当它可以使项目出列时,表示会话已过期。它只需要在Redis中清理会话,并通知应用程序。