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