我们现在就是Redis
Instagram最近在他们的工程博客上发布了一篇文章,介绍了缓存值的概念。其思想是,在缓存未命中时,获取丢失的值需要一段时间,这可能会导致缓存应该保护的底层DBMS发生踩踏。踩踏包括多个并行请求,在缓存未命中时,这些请求会触发同一工作的多个实例来填充缓存(见下文)
Instagram的Nick Cooper在其帖子中展示了在缓存中存储一个虚拟值(即承诺)的想法,以向竞争对手的请求者发出信号,表示其他人正在准备数据,以便他们知道等待,而不是将DBMS击死。这是文章我指的是,它也收到了一些评论在黑客新闻。
这个想法并不新鲜(这是通过读/写缓存现成的特性之一,因为它们透明地处理数据库),但我认为它非常好,值得讨论。在这篇文章中,我将分享这种方法的一个简单实现,以及您如何从中获得比通常使用(r/w)-通过缓存提供的更多好处。
在深入我的实现工作的细节之前,这里是一个概述,在将这个用例映射到Redis操作之后,我能够实现什么。我的实现:
我的实现主要依赖于三个Redis特性:密钥过期(TTL)、原子操作(setnx)和Pub/Sub在以前的职位上:
共享状态导致协调,反之亦然。
Redis非常理解这一点,这就是为什么使用它可以很容易地构建这种抽象。在这种情况下,Pub/Sub消息传递功能帮助我们将一个锁定机制绑定在一起,从而轻松创建一个真正的跨网络承诺系统。
我将此模式的实现称为Redis MemoLock。在高层次上,它的工作原理如下:
实际上,该算法稍微复杂一些,因此我们可以获得正确的并发性并处理超时(在分布式环境中,无法过期的锁/承诺几乎毫无用处)。我们的命名方案也稍微复杂一些,因为我选择为每个资源提供一个名称空间,以便允许多个独立的服务使用同一个集群,而不冒密钥名冲突的风险。除此之外,解决这个问题并不需要太多的复杂性。
你可以在GitHub上找到我的代码。我已经发布了一个Go实现,很快我将结合我即将在NDC奥斯陆的演讲(未来可能会有更多的语言)。下面是Go版本的一个代码示例:
如果您在5秒内运行此程序的两个实例,“Cache miss!”将只显示一次,而不管您的第一次执行是否已结束(即值已缓存)或是否仍在计算(在本示例代码中,是休眠而不是执行有用的工作)。
两大原因:
这就引出了我将我的解决方案称为MemoLock的原因。如果您将承诺的缓存思想推广到不仅缓存查询,而且缓存(纯)函数调用的任何(可序列化的)输出,那么您讨论的是记忆(而不是记忆)。如果你从没听说过记忆,阅读更多相关信息-这是一个有趣的概念。
Instagram描述的模式并不新鲜,但值得讨论。更好的缓存可以帮助大量的用例,作为一个行业,我们并不总是完全熟悉承诺的概念,尤其是当它超出单个流程的范围时。在GitHub上试试代码和请让我知道如果你喜欢的话。