14

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

4

1 回答 1

41

因此,您需要在 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 中的会话,并通知应用程序。

于 2012-08-05T10:12:52.267 回答