【问题标题】:What's the suggested way of storing a resource ETag?存储资源 ETag 的建议方法是什么?
【发布时间】:2012-08-16 11:46:22
【问题描述】:

我应该将给定资源的 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:在数据库级别存储

在使 INSERTUPDATE 语句变慢的同时节省一点 CPU 时间(我们使用触发器来更新 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 存储在一些缓存中间件中。

【问题讨论】:

  • 我不太明白,你说的是服务器端吧?为什么不在资源上有 PUT/POST/DELETE 并缓存它时计算它?您将始终拥有最新的 ETag。还是外部进程正在修改您的文件?
  • @Hugo 很抱歉让您感到困惑。不处理文件,而是处理资源/实体。我的问题是关于在哪里存储 etag:使用数据库 + 触发器或任何其他机制。

标签: http caching httpresponse http-caching etag


【解决方案1】:

我想这取决于将可用物品放入 ETag 本身的成本。

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

如果检索是简单的事情,例如获取文件,那么查询文件统计信息很快,并且不需要在任何地方存储任何东西:文件路径的 MD5 加上它的更新时间就足够了。

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

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

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

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

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

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

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

在一个半相关的注释中,我想到在某些情况下(对数据库的不频繁更新和对同一数据库的更频繁的查询),为 整个数据库。如果数据库没有改变,那么任何对数据库的查询都无法返回不同的资源,无论查询是什么。在这种情况下,只需将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() 会在涉及资源的情况下对数据库进行重大修改(即,在将访问统计信息记录到数据库时,您可以并且可能应该避免它,只要它们不影响资源的内容)。

【讨论】:

    【解决方案2】:

    在我看来,持久化 ETags 是一个坏主意,除非你的业务逻辑是关于持久化 ETags。就像您编写应用程序以基于 ETags 跟踪用户时一样,这是一项业务功能:)。

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

    根据规范,同一版本的Resource应根据已获得的端点给出不同的E-Tag。

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

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

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

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

    【讨论】:

      【解决方案3】:

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

      如果每个 ETag 在修改之间被读取 1 或 2 次,则只需即时计算它们。
      如果您的资源被读取的次数远多于更新的次数,那么您最好缓存它们,每次修改资源时计算 ETag(这样您就不必为过时的缓存 ETag 烦恼)。

      如果 ETag 的修改频率几乎与读取它们的频率一样,那么我仍然会缓存它们,尤其是因为您的资源似乎存储在数据库中。

      【讨论】:

        猜你喜欢
        • 2023-04-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-25
        • 1970-01-01
        • 1970-01-01
        • 2015-05-21
        相关资源
        最近更新 更多