我想实现一个基于 Redis 的会话存储。我想将会话数据放入 Redis。但我不知道如何处理会话过期。我可以遍历所有 redis 键(sessionid)并评估最后一次访问时间和最大空闲时间,因此我需要将所有键加载到客户端,并且可能有 1000m 会话键并且可能导致非常糟糕的 I/O表演。
想让redis管理过期,但是key过期时没有监听和回调,所以无法触发HttpSessionListener。有什么建议吗?
1 回答
因此,您需要在 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 过期时间不准确。如果所有的key都过期了,但是只有一个即将过期,并且没有被访问,活跃的过期作业可能需要几分钟才能找到key并过期。如果您需要通知的准确性,这不是要走的路。
解决方案 2:使用 zset 模拟过期
这里的想法是不依赖 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 中的会话,并通知应用程序。