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

Posted

技术标签:

【中文标题】如何处理基于redis的会话过期?【英文标题】:how to handle session expire basing redis? 【发布时间】:2012-08-02 08:18:30 【问题描述】:

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

【问题讨论】:

不在 Redis 中,但您可能想看看它在 Tarantool 中是如何完成的:github.com/mailru/tntlua/blob/master/expirationd.lua 简而言之,在 Tarantool 中,您可以在数据库中运行您自己的 Lua 脚本,并设置您的在其中拥有自己的过期策略。不需要外部守护进程。 【参考方案1】:

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

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

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

首先,人们正在考虑它:这仍在讨论中,但它可能会添加到 Redis 的未来版本中。请参阅以下问题:

https://github.com/antirez/redis/issues/83 https://github.com/antirez/redis/issues/594

现在,这里有一些您可以在当前 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 lightweight queuing system 是一个很好的可能性

每次在系统中添加会话时,应用程序都会将会话 ID 发布到 beanstalk 队列,延迟时间对应于会话超时。一个守护进程正在监听队列。当它可以使一个项目出队时,这意味着一个会话已经过期。它只需要清理 Redis 中的会话,并通知应用程序。

【讨论】:

惊人的答案 - 非常感谢!您能否澄清这句话:“守护程序应立即循环,否则可以引入 1 秒等待状态”。在这种情况下,循环意味着什么 - 为什么/在哪里引入了 1 秒等待? 守护程序是常驻程序,有时会醒来以在系统中进行一些活动。由于它们不断运行,因此大部分代码都包含在主循环中。现在守护进程也需要等待状态,以避免在循环时占用 100% CPU。没有与 Redis 的 zset 关联的阻塞命令(与列表的 BLPOP/BRPOP 不同),因此如果没有返回任何内容,则必须通过轮询和休眠来模拟它。 这已经在redis中实现了。这些问题已经结束。如果有人更新这个答案会很好。 @DidierSpezia 为什么我们在解决方案 2 中需要一个 multi? 将 SET 和 ZADD 命令捆绑在一起,并确保它们以原子方式执行。它还提高了时间戳的准确性,因为 zset 是在 SET 之后维护的。

以上是关于如何处理基于redis的会话过期?的主要内容,如果未能解决你的问题,请参考以下文章

面试突击 002 | Redis 是如何处理已过期元素的?

Redis 面试宝典之 Redis 如何处理已经过期的数据?

6. Redis在内存用完时会怎么办?以及Redis如何处理已过期的数据?

Redis 过期删除机制探究

Redis 缓存过期处理与内存淘汰机制

Redis缓存过期处理与内存淘汰机制