存储资源ETag的建议方法是什么?

时间:2012-08-21 06:48:48

标签: http caching httpresponse http-caching http-etag

我应该在哪里存储给定资源的ETag?

方法A:动态计算

获取资源并在每次请求时动态计算ETag:

$resource = $repository->findByPK($id); // query

// Compute ETag
$etag = md5($resource->getUpdatedAt());

$response = new Response();
$response->setETag($etag);
$response->setLastModified($resource->getUpdatedAt());

if($response->isNotModified($this->getRequest())) {
    return $response; // 304
}

方法B:在数据库级别存储

节省一点CPU时间,同时使INSERTUPDATE语句慢一点(我们使用触发器来更新ETag):

$resource = $repository->findByPK($id); // query

$response = new Response();
$response->setETag($resource->getETag());
$response->setLastModified($resource->getUpdatedAt());

if ($response->isNotModified($this->getRequest())) {
    return $response;
}

方法C:缓存ETag

这就像方法B,但ETag存储在一些缓存中间件中。

3 个答案:

答案 0 :(得分:4)

我认为这取决于将物品送入ETag本身的成本。

我的意思是,用户发送给定资源的请求;这应该触发对数据库(或其他一些操作)的检索操作。

如果检索很简单,例如提取文件,那么查询文件统计信息的速度很快,并且不需要在任何地方存储任何内容:文件路径的MD5加上其更新时间就足够了。

如果检索意味着查询数据库,那么它取决于您是否可以在不损失性能的情况下分解查询(例如,用户通过ID请求文章。您可以仅从文章表中检索相关数据。所以缓存“命中“将需要主键上的单个SELECT。但缓存”未命中“意味着你必须再次查询数据库,浪费第一个查询 - 或不 - 根据你的模型。”

如果查询(或查询序列)可以很好地分解(并且生成的代码可维护),那么我将再次使用动态ETag。

如果不是,那么大多数取决于查询成本和存储ETag解决方案的维护总成本。如果查询成本高(或者输出很大)并且INSERT / UPDATE很少,那么(而且,我认为,只有这样),使用ETag存储辅助列(或表)将是有利的。

至于缓存中间件,我不知道。如果我有一个框架跟踪我的一切,我可能会说'为它' - 中间件应该关心和实现上面的要点。中间件应该是与实现无关的(不太可能,除非它是一个剪切和粘贴的打击......这是闻所未闻的),那么它将有“筛选”资源更新的风险,或者可能在更新时调用一些缓存清除API的过度尴尬。这两个因素都需要根据ETag支持提供的负载改进进行评估。

我不认为在这种情况下存在'银弹'。

编辑:在您的情况下,案例A和B之间几乎没有 - 甚至没有 - 为了能够实现getUpdatedAt(),您需要将更新时间存储在模型中

在这个特定情况下,我认为ETag的动态,显式计算会更简单,更易于维护(案例A)。在任何情况下都会产生检索成本,并且显式计算成本是MD5计算的成本,这非常快且完全受CPU限制。在我看来,可维护性和简单性的优势是压倒性的。

在一个半相关的说明中,我发现在某些情况下(对数据库的不频繁更新以及更频繁的查询)可能有利且几乎透明地实现全局Last-Modified时间对于整个数据库。如果数据库没有更改,那么无论查询是什么,对数据库的任何查询都无法返回不同的资源。在这种情况下,人们只需要将Last-Modified全局标志存储在一些简单快速的检索位置(不一定是数据库)。例如

function dbModified() {
    touch('.last-update'); // creates the file, or updates its modification time
}

在任何UPDATE/DELETE代码中。资源would then add a header

function sendModified() {
    $tsstring = gmdate('D, d M Y H:i:s ', filemtime('.last-update')) . 'GMT';
    Header("Last-Modified: " . $tsstring);
}

通知浏览器该资源的修改时间。

然后,对包含If-Modified-Since的资源的任何请求都可以使用304退回,而无需访问持久层(或者至少保存所有持久资源访问)。不需要记录级别的更新时间:

function ifNotModified() {
    // Check out timezone settings. The GMT helps but it's not always the ticket
    $ims = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
        ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])
        : -1; // This ensures the test will FAIL

   if (filemtime('.last-update') <= $ims) {
       // The database was never updated after the resource retrieval.
       // There's no way the resource may have changed.
       exit(Header('HTTP/1.1 304 Not Modified'));
   }
}

尽可能早地在资源供应路径中调用ifNotModified()调用,尽可能早地在资源输出代码中调用sendModified,并在数据库被修改的任何地方尽可能地调用dbModified() (即,在将访问统计信息记录到数据库时,您可以并且可能应该避免它,只要它们不影响资源的内容)。

答案 1 :(得分:1)

在我看来,除非你的业务逻辑是关于持久化ETag,否则持久化ETags是坏想法。就像你编写应用程序来跟踪基于ETag的用户一样,这是一个商业功能:)。

执行时间的潜在节省将很小或不存在。随着应用程序的增长,此解决方案的不良方面是肯定的并且在不断增长。

根据规范,相同版本的资源应根据已获得的终点给出不同的E-Tags。

来自http://en.wikipedia.org/wiki/HTTP_ETag

“比较ETag只对一个URL有意义 - 从不同URL获取的资源的ETag可能相同也可能不相同,因此从它们的比较中无法推断出任何意义。”

由此您可以得出结论,您不仅要坚持ETag,还要坚持其端点并存储尽可能多的ETag。听起来很疯狂?

即使您想忽略HTTP规范,只需为实体提供一个Etag,而不需要有关其端点的任何元数据。您仍然可以绑定至少2层(缓存和业务逻辑),理想情况下不应该混合使用。实体(与某些丢失数据相比)背后的想法是在其中分离并且不耦合业务逻辑,并且不会用关于网络,查看层数据或......缓存的东西污染它们。

答案 2 :(得分:0)

IHMO,这取决于资源更新的频率与资源的读取频率。

如果在修改之间读取每个ETag 1或2次,则只需动态计算它们 如果您的资源读取的次数远远超过它们的更新次数,那么您最好对它们进行缓存,每次修改资源时都要对ETag进行计算(因此您不必费心使用过时的缓存ETag)。

如果ETag的修改几乎和它们一样经常被修改,那么我仍然会对它们进行缓存,特别是因为看起来你的资源存储在数据库中。

相关问题