堆栈溢出、Redis和缓存失效

28
现在 Stack Overflow 使用 redis,他们处理缓存失效的方式是否相同?例如,将一组标识符散列为查询字符串+名称(我想名称是某种目的或对象类型名称)。然后,也许他们通过id直接检索缺失的单个项目(这可能会绕过一堆数据库索引并使用更有效的聚集索引)。那就很聪明了(Jeff提到的重新hydration机制吗?)。
现在,我正在努力找到一种简洁的方式来转换所有这些内容。在自己进行第一次切割之前,是否有任何此类示例可供我使用以帮助澄清我的思路?
另外,我想知道使用 .net 缓存(System.Runtime.Caching 或 System.Web.Caching)和使用 redis 之间的分界线在哪里。还是 Redis 更快更好?这是来自 2009 年的原始 SO 问题:

https://meta.stackexchange.com/questions/6435/how-does-stackoverflow-handle-cache-invalidation

另外还有几个链接:

https://meta.stackexchange.com/questions/69164/does-stackoverflow-use-caching-and-if-so-how/69172#69172

https://meta.stackexchange.com/questions/110320/stack-overflow-db-performance-and-redis-cache

1个回答

47
我实在无法确定这是个Stack Overflow问题还是Meta Stack Overflow问题,但是:
跑去另一个系统查询数据绝不会比查询本地内存更快(只要它是有键的);简单来说:我们两种都用!因此我们使用:
- 本地内存 - 否则检查Redis,并更新本地内存 - 否则从源获取,并更新Redis和本地内存
正如你所说,这就导致了缓存失效的问题 - 尽管在大多数地方这并不关键。但对于这个问题,Redis事件(发布/订阅)允许一种容易的方式将正在更改的键广播到所有节点,以便它们可以删除本地副本 - 这意味着:下次需要时,我们将从Redis中获取新副本。因此,我们针对一个单一事件通道名称广播正在更改的键名。
工具:Ubuntu服务器上的Redis;BookSleeve作为Redis包装器;protobuf-net和GZipStream(根据大小自动启用/禁用)用于打包数据。
因此:Redis发布/订阅事件用于立即(几乎)从一个节点(知道状态已更改的节点)向所有节点使给定键的缓存失效。
关于不同进程(来自评论,“您是否使用任何共享内存模型,以便多个不同的进程从相同的数据中提取?”):不,我们不这样做。每个Web层盒子实际上只托管一个进程(任何给定层的进程),具有多租户性质,因此在同一进程内我们可能有70个站点。基于传统原因(即“它可以工作,不需要修复”),我们主要使用HTTP缓存,并将站点标识作为关键部分。
对于系统中少量的大量数据密集部分,我们有机制将其持久化到磁盘,以便在Web自然回收(或重新部署)时可以在连续的应用程序域之间传递内存模型,但这与Redis无关。
以下是一个相关的例子,仅展示了此过程如何工作的“大致风味”,启动若干个实例,然后输入一些键名:
static class Program
{
    static void Main()
    {
        const string channelInvalidate = "cache/invalidate";
        using(var pub = new RedisConnection("127.0.0.1"))
        using(var sub = new RedisSubscriberConnection("127.0.0.1"))
        {
            pub.Open();
            sub.Open();

            sub.Subscribe(channelInvalidate, (channel, data) =>
            {
                string key = Encoding.UTF8.GetString(data);
                Console.WriteLine("Invalidated {0}", key);
            });
            Console.WriteLine(
                    "Enter a key to invalidate, or an empty line to exit");
            string line;
            do
            {
                line = Console.ReadLine();
                if(!string.IsNullOrEmpty(line))
                {
                    pub.Publish(channelInvalidate, line);
                }
            } while (!string.IsNullOrEmpty(line));
        }
    }
}

你应该会看到的是,当你输入一个键名时,它会立即显示在所有正在运行的实例中,这些实例将卸载它们本地的该键的副本。显然,在实际使用中,这两个连接需要放在某个地方并保持打开状态,因此不应该放在using语句中。我们使用了一个几乎是单例的对象来实现这一点。


RedisConnectionRedisSubscriberConnection类是什么?我需要添加哪个程序集才能访问这两个类? - Monojit Sarkar
@MonojitSarkar,这些在StackExchange.Redis中,可以在NuGet上获取。 - Marc Gravell
@MarcGravell 我很想了解你在这里提供的代码。上面的代码是做什么的?RedisConnection类是做什么的?RedisSubscriberConnection类又是做什么的? - Monojit Sarkar
@MarcGravell,我真的很想了解你的代码,那些行是在做什么。有一个发布者和一个订阅者。你能解释一下你的代码吗?谢谢。 - Monojit Sarkar
在Redis中,订阅连接与常规命令连接非常不同;“发布”通过常规命令连接进行;而“订阅”则通过其他连接进行。所以...这就是全部内容了... - Marc Gravell
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接