2R2L缓存

缓存是一个广为人知的主题。但是新的解决方案也可能出现在其中。特别是在高级产品领域(例如,在Web开发中)。面对经典方法的缺点,我试图为数据相关性不是很关键的情况下得出一种理想的缓存方案。然后,我试图找到类似方案的描述,或者更好的现成解决方案。没找到。因此,我自己给它命名-2R2L(2范围2位置)-两范围两“空间”缓存。尽管它可能已在某处使用。



一切都从一个简单的任务开始-考虑到用户的个人喜好向用户展示新产品。如果获取新产品没有问题,则将新产品与首选项相关联(分析统计数据)已经创建了明显的负载(例如,让我们在4秒内定义负载)。任务的特殊性是整个组织都可以充当用户。一次(2-3秒内)一次与一个用户有关的200-300个请求到达服务器的情况并不少见。那些。一次为多个用户生成了相同的块。



显而易见的解决方案是将其缓存在RAM中(不要让DBMS遭受暴力,迫使它处理大量的呼叫)。经典方案:



  1. 要求来了
  2. 检查缓存。如果其中有数据,并且它们没有过时,我们只需将其退还。
  3. 没有数据=>产生问题
  4. 我们发送给用户
  5. 此外,我们将其添加到缓存中,指示TTL


这种解决方案的缺点是:如果高速缓存中没有数据,则在第一代生成的所有请求都将生成它们,从而将服务器资源用于此(负载峰值)。当然,所有用户都将在“首次通话”中等待。



还要注意的是,使用单独的缓存值,记录数量可能会增加得太多,以致可用服务器RAM根本不够用。然后,将本地HDD服务器用作缓存存储似乎是合乎逻辑的。但是我们立即失去了速度。



怎样成为?



首先想到的是:最好将记录存储在2个地方-RAM(经常请求)和HDD(全部或很少请求)中。最纯粹形式的“冷热数据”概念。此方法有很多实现,因此我们不再赘述。让我们将此组件指定为2L。就我而言,它是基于Scylla DBMS成功实现的。



但是,当缓存已过期时,如何摆脱缩图?这里我们包含了2R的概念,其含义很简单:对于高速缓存记录,您需要指定的不是1 TTL值,而是2。TTL1是一个时间戳,表示“数据已过时,应该重新生成,但仍然可以使用它”;TTL2-“一切都过时了,无法再使用了。”



因此,我们得到了稍微不同的缓存方案:



  1. 要求来了
  2. 我们正在缓存中寻找数据。如果数据在那里并且没有过时(t <TTL1)-我们像往常一样将其返回给用户,则不执行其他任何操作。
  3. 数据在那里,已经过时,但是可以使用(TTL1 <t <TTL2)-我们将其提供给用户,并初始化更新缓存记录的过程
  4. 根本没有数据(TTL2过期后被杀死)-我们“照常”生成数据并将其写入缓存。
  5. 在向用户提供内容或以并行流形式提供内容之后,我们执行更新缓存记录的过程。


结果,我们有:



  • 如果足够频繁地使用缓存记录,则用户将永远不会陷入“等待缓存更新”的境地-他将始终收到现成的结果。
  • 如果正确组织了“更新”队列,则可以实现以下事实:在同时访问多个TTL1 <t <TTL2的记录时,队列中将只有1个更新任务,而没有几个相同的更新任务。


例如:对于新产品Feed,您可以指定TTL1 = 1小时(尽管新内容显示得不太密集),而TTL2-1周。



在最简单的情况下,实现2R的PHP代码如下所示:



$tmp = cache_get($key);
If (!$tmp){
	$items = generate_items();
	cache_set($items, 60*60, 60*60*24*7);
}else{
	$items = $tmp[‘items’];
	If (time()-$tmp[‘tm’] > 60*60){
		$need_rebuild[] = [‘to’=>$key, ‘method’=>’generate_items’];
}
}
//   
echo json_encode($items);
//     ,   
If (isset($need_rebuild) && count($need_rebuild)>0){
	foreach($need_rebuild as $k=>$v){
		$tmp = ['tm'=>time(), 'items'=>$$v[‘method’]];
		cache_set($tmp, 60*60, 60*60*24*7);
}
}


当然,实际上,实施起来可能会更困难。例如,缓存记录的生成器是作为服务启动的单独脚本;队列-通过Rabbit,通过Redis或Scylla,标有“此类密钥已在再生队列中”。



因此,如果我们将“双频带”方法和“热/冷”数据的概念结合起来,我们将得到-2R2L。



谢谢!



All Articles