我花了一些时间研究缓存(主要是redis和memcached),当数据不断变化时,我很难弄清楚在哪里使用缓存。
以推特为例(只需阅读《让推特速度提高10000%》)。当数据库记录的很大一部分不断变化时,您(或他们)将如何缓存数据?
假设Twitter有以下几种型号:User
、Tweet
、Follow
、Favorite
。
有人可能会发布一条一天转发一次的推文,另一条一天后转发一千次。对于1000倍的转发,由于每天大约有24 * 60 == 1440
分钟,这意味着推文几乎每分钟都会更新(比如说它也有440个收藏夹)。和关注某人一样,charlie sheen甚至在一天内就吸引了100万推特粉丝。在这种情况下,似乎不值得缓存,但可能只是因为我还没有达到那个水平。
还可以说,推特的普通粉丝每天至少发一次推特/关注/收藏夹。这意味着在天真的introrails模式中,用户表每天至少更新一次(tweet_count
等)。这种情况对于缓存用户配置文件是有意义的。
但对于上面的1000条推文和100万粉丝的例子,在缓存数据时,建议采取哪些做法?
具体来说(假设memcached或redi,并使用纯JSON API(无页面/片段缓存)):
- 你缓存个人推文/记录吗
- 还是通过分页来缓存记录块(例如,每个
20
的redis列表) - 还是将记录分别缓存在页面中(查看单个推文与JSON提要)
- 或者,你会为每个不同的场景缓存推文列表:主页时间线推文、用户推文、最喜欢的推文等等?还是以上全部
- 还是将数据分解为"最不稳定(最新)"、"最近几天"one_answers"旧"块,其中"旧"数据缓存的到期日期更长,或者分解为离散的分页列表或其他什么?而且最新的记录根本没有缓存。(即,如果数据像推特一样依赖时间,如果你的旧记录知道它不会有太大变化,你会区别对待它吗?)
我不明白的是,数据的变化量与是否应该缓存数据(并处理缓存到期的复杂性)的比例是多少。Twitter似乎可以缓存不同的用户推特订阅源,以及每个用户的主页推特,但每次一个收藏夹/推特/转发都会使缓存无效,这意味着更新所有这些缓存项(可能还有缓存的记录列表),这在某种程度上似乎意味着使缓存无效会适得其反。
缓存像这样变化很大的数据的建议策略是什么?
并不是说Twitter是这样做的(尽管我很确定这是相关的),而是:我最近熟悉了CQRS+事件来源。(http://martinfowler.com/bliki/CQRS.html+http://martinfowler.com/eaaDev/EventSourcing.html)。
基本上:读取和写入在应用程序和持久性级别(CQRS)上是完全分离的,对系统的每次写入都作为一个可以订阅的事件进行处理(事件源)。它还有更多功能(比如能够回放整个事件流,这对以后实现新功能非常有用),但这是相关的部分。
此后,通常的做法是,每当负责的Projector
(即:它将事件投影到新的读取模型)接收到它订阅的事件类型的新事件时,就会重新创建Read Model
(在mem缓存中思考)
在这种情况下,事件可以是TweetHandled,它将由所有订阅者处理,其中RecentTweetsPerUserProjector
、TimelinePerUserProjector
等更新它们各自的ReadModel。
结果是ReadModels的集合,它们最终是一致的,不需要任何失效,即:更新的写入和产生的事件是更新ReadModels开始的触发器。
我同意Charlie Sheen的Read Model最终会得到很多更新(尽管这种更新可能非常有效),所以缓存优势可能很低。然而,看看平均用户每个时间单位的平均帖子,情况就完全不同了。
DDD/CQRS/活动采购现场的一些有影响力的人:Greg Young、Udi Dahan。
这些概念相当"深刻",所以不要指望在一个小时内完全理解(至少我没有)。也许最近关于相关概念的思维导图也很有用:http://www.mindmeister.com/de/181195534/cqrs-ddd-links
是的,如果你还没有注意到的话,我对此非常感兴趣:)
我的2美分:Redis允许你对它的数据结构进行操作,这意味着你可以比每次接触关系数据库更快地在内存中进行操作。
因此,可以更改"缓存",这样它就不会像您预期的那样失效。
在我的项目中,我定期将500K条记录加载到排序的集合中,然后仅通过对它们进行范围查询来运行统计报告,这使报告执行时间平均不到2秒。