缓存

未匹配的标注

缓存

缓存是提升 Web 应用性能简便有效的方式。 通过将相对静态的数据存储到缓存并在收到请求时取回缓存, 应用程序便节省了每次重新生成这些数据所需的时间。

缓存可以应用在 Web 应用程序的任何层级任何位置。 在服务器端,在较的低层面,缓存可能用于存储基础数据,例如从数据库中取出的最新文章列表; 在较高的层面,缓存可能用于存储一段或整个 Web 页面, 例如最新文章的渲染结果。在客户端,HTTP 缓存可能用于 将最近访问的页面内容存储到浏览器缓存中。

Yii 支持如上所有缓存机制:

数据缓存

数据缓存是指将一些 PHP 变量存储到缓存中,使用时再从缓存中取回。 它也是更高级缓存特性的基础,例如查询缓存内容缓存

如下代码是一个典型的数据缓存使用模式。 其中 $cache 指向缓存组件

// 尝试从缓存中取回 $data 
$data = $cache->get($key);

if ($data === false) {

    // $data 在缓存中没有找到,则重新计算它的值

    // 将 $data 存放到缓存供下次使用
    $cache->set($key, $data);
}

// 这儿 $data 可以使用了。

从 2.0.11 版本开始, 缓存组件 提供了 [[yii\caching\Cache::getOrSet()|getOrSet()]] 方法来简化数据的取回、计算和存储。 下面的代码逻辑和上一个例子是完全一样的:

$data = $cache->getOrSet($key, function () {
    return $this->calculateSomething();
});

当缓存中有关联 $key 的数据时,将返回这个缓存的值。 否则就执行匿名函数来计算出将要缓存的数据并返回它。

如果匿名函数需要作用域外的数据时,可以使用 use 语句把这些数据传递到匿名函数中。 例如:

$user_id = 42;
$data = $cache->getOrSet($key, function () use ($user_id) {
    return $this->calculateSomething($user_id);
});

Note: [[yii\caching\Cache::getOrSet()|getOrSet()]] 方法也支持缓存持续性和缓存依赖。 请看缓存过期缓存依赖 来了解详细信息。

缓存组件

数据缓存需要缓存组件提供支持,它代表各种缓存存储器, 例如内存,文件,数据库。

缓存组件通常注册为应用程序组件,这样它们就可以 在全局进行配置与访问。如下代码演示了如何配置应用程序组件 cache 使用两个 memcached 服务器:

'components' => [
    'cache' => [
        'class' => 'yii\caching\MemCache',
        'servers' => [
            [
                'host' => 'server1',
                'port' => 11211,
                'weight' => 100,
            ],
            [
                'host' => 'server2',
                'port' => 11211,
                'weight' => 50,
            ],
        ],
    ],
],

然后就可以通过 Yii::$app->cache 访问上面的缓存组件了。

由于所有缓存组件都支持同样的一系列 API ,并不需要修改使用缓存的业务代码 就能直接替换为其他底层缓存组件,只需在应用配置中重新配置一下就可以。 例如,你可以将上述配置修改为使用 [[yii\caching\ApcCache|APC cache]]:

'components' => [
    'cache' => [
        'class' => 'yii\caching\ApcCache',
    ],
],

Tip: 你可以注册多个缓存组件,很多依赖缓存的类默认调用 名为 cache 的组件(例如 [[yii\web\UrlManager]])。

支持的缓存存储器

Yii 支持一系列缓存存储器,概况如下:

  • [[yii\caching\ApcCache]]:使用 PHP APC 扩展。 这个选项可以认为是集中式应用程序环境中 (例如:单一服务器,没有独立的负载均衡器等)最快的缓存方案。
  • [[yii\caching\DbCache]]:使用一个数据库的表存储缓存数据。要使用这个缓存, 你必须创建一个与 [[yii\caching\DbCache::cacheTable]] 对应的表。
  • [[yii\caching\ArrayCache]]: 仅通过将值存储在数组中来为当前请求提供缓存。 为了增强 ArrayCache 的性能,您可以通过将 [[yii\caching\ArrayCache::$serializer]] 设置为 false 来禁用已存储数据的序列化。
  • [[yii\caching\DummyCache]]:仅作为一个缓存占位符,不实现任何真正的缓存功能。 这个组件的目的是为了简化那些需要查询缓存有效性的代码。例如, 在开发中如果服务器没有实际的缓存支持,用它配置一个缓存组件。 一个真正的缓存服务启用后,可以再切换为使用相应的缓存组件。 两种条件下你都可以使用同样的代码 Yii::$app->cache->get($key) 尝试从缓存中取回数据而不用担心 Yii::$app->cache 可能是 null
  • [[yii\caching\FileCache]]:使用标准文件存储缓存数据。 这个特别适用于缓存大块数据,例如一个整页的内容。
  • [[yii\caching\MemCache]]:使用 PHP memcachememcached 扩展。 这个选项被看作分布式应用环境中(例如:多台服务器,有负载均衡等) 最快的缓存方案。
  • [[yii\redis\Cache]]:实现了一个基于 Redis 键值对存储器的缓存组件 (需要 redis 2.6.12 及以上版本的支持 )。
  • [[yii\caching\WinCache]]:使用 PHP WinCache另可参考)扩展.
  • [[yii\caching\XCache]]:使用 PHP XCache扩展。
  • [[yii\caching\ZendDataCache]]:使用 [Zend Data Cache](files.zend.com/help/Zend-Server-6/z... server.htm#data_cache_component.htm) 作为底层缓存媒介。

Tip: 你可以在同一个应用程序中使用不同的缓存存储器。一个常见的策略是使用基于内存的缓存存储器 存储小而常用的数据(例如:统计数据),使用基于文件或数据库的缓存存储器 存储大而不太常用的数据(例如:网页内容)。

缓存 API

所有缓存组件都有同样的基类 [[yii\caching\Cache]] ,因此都支持如下 API:

  • [[yii\caching\Cache::get()|get()]]:通过一个指定的键(key)从缓存中取回一项数据。 如果该项数据不存在于缓存中或者已经过期/失效,则返回值 false。
  • [[yii\caching\Cache::set()|set()]]:将一个由键指定的数据项存放到缓存中。
  • [[yii\caching\Cache::add()|add()]]:如果缓存中未找到该键,则将指定数据存放到缓存中。
  • [[yii\caching\Cache::getOrSet()|getOrSet()]]:返回由键指定的缓存项,或者执行回调函数,把函数的返回值用键来关联存储到缓存中, 最后返回这个函数的返回值。
  • [[yii\caching\Cache::multiGet()|multiGet()]]:由指定的键获取多个缓存数据项。
  • [[yii\caching\Cache::multiSet()|multiSet()]]:一次存储多个数据项到缓存中,每个数据都由一个键来指明。
  • [[yii\caching\Cache::multiAdd()|multiAdd()]]:一次存储多个数据项到缓存中,每个数据都由一个键来指明。 如果某个键已经存在,则略过该数据项不缓存。
  • [[yii\caching\Cache::exists()|exists()]]:返回一个值,指明某个键是否存在于缓存中。
  • [[yii\caching\Cache::delete()|delete()]]:通过一个键,删除缓存中对应的值。
  • [[yii\caching\Cache::flush()|flush()]]:删除缓存中的所有数据。

Note: 千万别直接用 false 布尔值当做数据项缓存,因为 [[yii\caching\Cache::get()|get()]] 方法用 false 作为返回值来表名对应的缓存项不存在。 你可以把 false 放到一个数组里然后缓存这个数组来避免上述的混淆问题。

有些缓存存储器如 MemCache,APC 支持以批量模式取回缓存值,这样可以节省取回缓存数据的开支。 [[yii\caching\Cache::multiGet()|multiGet()]] 和 [[yii\caching\Cache::multiAdd()|multiAdd()]] API提供对该特性的支持。 如果底层缓存存储器不支持该特性,Yii 也会模拟实现。

由于 [[yii\caching\Cache]] 实现了 PHP ArrayAccess 接口, 缓存组件也可以像数组那样使用,下面是几个例子:

$cache['var1'] = $value1;  // 等价于: $cache->set('var1', $value1);
$value2 = $cache['var2'];  // 等价于: $value2 = $cache->get('var2');

缓存键

存储在缓存中的每项数据都通过键作唯一识别。 当你在缓存中存储一项数据时,必须为它指定一个键, 稍后从缓存中取回数据时,也需要提供相应的键。

你可以使用一个字符串或者任意值作为一个缓存键。当键不是一个字符串时, 它将会自动被序列化为一个字符串。

定义一个缓存键常见的一个策略就是在一个数组中包含所有的决定性因素。 例如,[[yii\db\Schema]] 使用如下键存储一个数据表的结构信息。

[
    __CLASS__,              // 结构类名
    $this->db->dsn,         // 数据源名称
    $this->db->username,    // 数据库登录用户名
    $name,                  // 表名
];

如你所见,该键包含了可唯一指定一个数据库表所需的所有必要信息。

Note: 通过 [[yii\caching\Cache::multiSet()|multiSet()]] 或者 [[yii\caching\Cache::multiAdd()|multiAdd()]] 方法缓存的数据项的键,它的类型只能是字符串或整型, 如果你想使用较为复杂的键,可以通过 [[yii\caching\Cache::set()|set()]] 或者 [[yii\caching\Cache::add()|add()]] 方法来存储。

当同一个缓存存储器被用于多个不同的应用时,应该为每个应用指定一个唯一的缓存键前缀以避免缓存键冲突。 可以通过配置 [[yii\caching\Cache::keyPrefix]] 属性实现。 例如,在应用配置中可以编写如下代码:

'components' => [
    'cache' => [
        'class' => 'yii\caching\ApcCache',
        'keyPrefix' => 'myapp',       // 唯一键前缀
    ],
],

为了确保互通性,此处只能使用字母和数字。

缓存过期

默认情况下,缓存中的数据会永久存留,除非它被某些缓存策略强制移除(例如:缓存空间已满,最老的数据会被移除)。 要改变此特性,你可以在调用 [[yii\caching\Cache::set()|set()]] 存储一项数据时提供一个过期时间参数。 该参数代表这项数据在缓存中可保持有效多少秒。 当你调用 [[yii\caching\Cache::get()|get()]] 取回数据时, 如果它已经过了超时时间,该方法将返回 false,表明在缓存中找不到这项数据。 例如:

// 将数据在缓存中保留 45 秒
$cache->set($key, $data, 45);

sleep(50);

$data = $cache->get($key);
if ($data === false) {
    // $data 已过期,或者在缓存中找不到
}

从 2.0.11 开始,如果想自定义缓存的持续时间,你可以在缓存组件配置中设置 [[yii\caching\Cache::$defaultDuration|defaultDuration]] 成员属性的值。 这样设置会覆盖默认的缓存持续时间,且在使用 [[yii\caching\Cache::set()|set()]] 方法时不必每次都传递 $duration 参数。

缓存依赖

除了超时设置,缓存数据还可能受到缓存依赖的影响而失效。 例如,[[yii\caching\FileDependency]] 代表对一个文件修改时间的依赖。 这个依赖条件发生变化也就意味着相应的文件已经被修改。 因此,缓存中任何过期的文件内容都应该被置为失效状态, 对 [[yii\caching\Cache::get()|get()]] 的调用都应该返回 false。

缓存依赖用 [[yii\caching\Dependency]] 的派生类所表示。 当调用 [[yii\caching\Cache::set()|set()]] 在缓存中存储一项数据时, 可以同时传递一个关联的缓存依赖对象。例如:

// 创建一个对 example.txt 文件修改时间的缓存依赖
$dependency = new \yii\caching\FileDependency(['fileName' => 'example.txt']);

// 缓存数据将在30秒后超时
// 如果 example.txt 被修改,它也可能被更早地置为失效状态。
$cache->set($key, $data, 30, $dependency);

// 缓存会检查数据是否已超时。
// 它还会检查关联的依赖是否已变化。
// 符合任何一个条件时都会返回 false。
$data = $cache->get($key);

下面是可用的缓存依赖的概况:

  • [[yii\caching\ChainedDependency]]:如果依赖链上任何一个依赖产生变化,则依赖改变。
  • [[yii\caching\DbDependency]]:如果指定 SQL 语句的查询结果发生了变化,则依赖改变。
  • [[yii\caching\ExpressionDependency]]:如果指定的 PHP 表达式执行结果发生变化,则依赖改变。
  • [[yii\caching\FileDependency]]:如果文件的最后修改时间发生变化,则依赖改变。
  • [[yii\caching\TagDependency]]:将缓存的数据项与一个或多个标签相关联。 您可以通过调用 [[yii\caching\TagDependency::invalidate()]] 来检查指定标签的缓存数据项是否有效。

Note: 避免对带有缓存依赖的缓存项使用 [[yii\caching\Cache::exists()|exists()]] 方法, 因为它不检测缓存依赖(如果有的话)是否有效,所以调用 [[yii\caching\Cache::get()|get()]] 可能返回 false 而调用 [[yii\caching\Cache::exists()|exists()]] 却返回 true

查询缓存

查询缓存是一个建立在数据缓存之上的特殊缓存特性。 它用于缓存数据库查询的结果。

查询缓存需要一个 [[yii\db\Connection|数据库连接]] 和一个有效的 cache 应用组件。 查询缓存的基本用法如下,假设 $db 是一个 [[yii\db\Connection]] 实例:

$result = $db->cache(function ($db) {

    // SQL 查询的结果将从缓存中提供
    // 如果启用查询缓存并且在缓存中找到查询结果
    return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();

});

查询缓存可以用在DAOActiveRecord上:

$result = Customer::getDb()->cache(function ($db) {
    return Customer::find()->where(['id' => 1])->one();
});

Info: 有些 DBMS (例如:MySQL) 也支持数据库服务器端的查询缓存。 你可以选择使用任一查询缓存机制。 上文所述的查询缓存的好处在于你可以指定更灵活的缓存依赖因此可能更加高效。

自 2.0.14 以后,您可以使用以下快捷方法:

(new Query())->cache(7200)->all();
// and
User::find()->cache(7200)->all();

配置

查询缓存通过 [[yii\db\Connection]] 有三个全局可配置选项:

  • [[yii\db\Connection::enableQueryCache|enableQueryCache]]:是否打开或关闭查询缓存。 它默认为 true。 请注意,要有效打开查询缓存, 您还需要有一个由 [[yii\db\Connection::queryCache|queryCache]] 所指定的有效缓存。
  • [[yii\db\Connection::queryCacheDuration|queryCacheDuration]]:这表示查询结果在缓存中保持有效的秒数。 您可以使用 0 来表示查询结果永久保留在缓存中。 该属性是在未指定持续时间的情况下调用 [[yii\db\Connection::cache()]] 使用的默认值。
  • [[yii\db\Connection::queryCache|queryCache]]:缓存应用组件的 ID。默认为 'cache'。 只有在设置了一个有效的缓存应用组件时,查询缓存才会有效。

使用

如果您有多个需要利用查询缓存的 SQL 查询,则可以使用 [[yii\db\Connection::cache()]]。 用法如下,

$duration = 60;     // 缓存查询结果 60 秒。
$dependency = ...;  // 可选的依赖关系

$result = $db->cache(function ($db) {

    // ... 在这里执行 SQL 查询 ...

    return $result;

}, $duration, $dependency);

在匿名函数里的任何一个 SQL 查询都将使用指定的依赖项缓存指定的持续时间 如果一个 SQL 查询的结果在缓存中有效,那么这个 SQl 语句将会被跳过而它的查询结果会直接从缓存中读取。 如果你没有指明 $duration 参数, 那么使用 [[yii\db\Connection::queryCacheDuration|queryCacheDuration]] 属性。

有时在cache()里,你可能不想缓存某些特殊的查询, 这时你可以用[[yii\db\Connection::noCache()]]。

$result = $db->cache(function ($db) {

    // 使用查询缓存的 SQL 查询

    $db->noCache(function ($db) {

        // 不使用查询缓存的 SQL 查询

    });

    // ...

    return $result;
});

如果您只想为单个查询使用查询缓存,则可以在构建命令时调用 [[yii\db\Command::cache()]]。 例如,

// 使用查询缓存并将查询缓存持续时间设置为 60 秒
$customer = $db->createCommand('SELECT * FROM customer WHERE id=1')->cache(60)->queryOne();

您还可以使用 [[yii\db\Command::noCache()]] 禁用单个命令的查询缓存。例如,

$result = $db->cache(function ($db) {

    // 使用查询缓存的 SQL 查询

    // 对此命令不使用查询缓存
    $customer = $db->createCommand('SELECT * FROM customer WHERE id=1')->noCache()->queryOne();

    // ...

    return $result;
});

限制条件

当查询结果中含有资源句柄时,查询缓存无法使用。 例如,在有些 DBMS 中使用了 BLOB 列的时候, 缓存结果会为该数据列返回一个资源句柄。

有些缓存存储器有大小限制。例如,memcache 限制每条数据最大为 1MB。 因此,如果查询结果的大小超出了该限制, 则会导致缓存失败。

缓存冲刷

当你想让所有的缓存数据失效时,可以调用 [[yii\caching\Cache::flush()]]。

冲刷缓存数据,你还可以从控制台调用 yii cache/flush

  • yii cache:列出应用中可用的缓存组件
  • yii cache/flush cache1 cache2:刷新缓存组件cache1cache2 (可以传递多个用空格分开的缓存组件)
  • yii cache/flush-all:刷新应用中所有的缓存组件
  • yii cache/flush-schema db:清除给定连接组件的数据库表结构缓存

Info: 默认情况下,控制台应用使用独立的配置文件。 所以,为了上述命令发挥作用,请确保 Web 应用和控制台应用配置相同的缓存组件。

片段缓存

片段缓存指的是缓存页面内容中的某个片段。例如,一个页面显示了逐年销售额的摘要表格, 可以把表格缓存下来,以消除每次请求都要重新生成表格的耗时。 片段缓存是基于数据缓存实现的。

视图中使用以下结构启用片段缓存:

if ($this->beginCache($id)) {

    // ... 在此生成内容 ...

    $this->endCache();
}

调用 [[yii\base\View::beginCache()|beginCache()]] 和 [[yii\base\View::endCache()|endcache()]] 方法包裹内容生成逻辑。 如果缓存中存在该内容,[[yii\base\View::beginCache()|beginCache()]] 方法将渲染内容并返回 false, 因此将跳过内容生成逻辑。否则,内容生成逻辑被执行, 一直执行到[[yii\base\View::endCache()|endCache()]] 时, 生成的内容将被捕获并存储在缓存中。

数据缓存一样,每个片段缓存也需要全局唯一的 $id 标记。

缓存选项

如果要为片段缓存指定额外配置项, 请通过向 [[yii\base\View::beginCache()|beginCache()]] 方法第二个参数传递配置数组。在框架内部,该数组将被用来配置一个 [[yii\widget\FragmentCache]] 小部件用以实现片段缓存功能。

过期时间(duration)

或许片段缓存中最常用的一个配置选项就是 [[yii\widgets\FragmentCache::duration|duration]] 了。 它指定了内容被缓存的秒数。 以下代码缓存内容最多一小时:

if ($this->beginCache($id, ['duration' => 3600])) {

    // ... 在此生成内容 ...

    $this->endCache();
}

如果该选项未设置,则它将采用默认值 60,这意味着缓存的内容将在 60 秒后过期。

依赖

数据缓存一样,片段缓存的内容一样可以设置缓存依赖。 例如一段被缓存的文章,是否重新缓存取决于它是否被修改过。

通过设置 [[yii\widgets\FragmentCache::dependency|dependency]] 选项来指定依赖, 该选项的值可以是一个 [[yii\caching\Dependency]] 类的派生类,也可以是创建缓存对象的配置数组。 以下代码指定了一个片段缓存,它依赖于 update_at 字段是否被更改过的。

$dependency = [
    'class' => 'yii\caching\DbDependency',
    'sql' => 'SELECT MAX(updated_at) FROM post',
];

if ($this->beginCache($id, ['dependency' => $dependency])) {

    // ... 在此生成内容 ...

    $this->endCache();
}

变化

缓存的内容可能需要根据一些参数的更改而变化。 例如一个 Web 应用支持多语言,同一段视图代码也许需要生成多个语言的内容。 因此可以设置缓存根据应用当前语言而变化。

通过设置 [[yii\widgets\FragmentCache::variations|variations]] 选项来指定变化, 该选项的值应该是一个标量,每个标量代表不同的变化系数。 例如设置缓存根据当前语言而变化可以用以下代码:

if ($this->beginCache($id, ['variations' => [Yii::$app->language]])) {

    // ... 在此生成内容 ...

    $this->endCache();
}

开关

有时你可能只想在特定条件下开启片段缓存。例如,一个显示表单的页面,可能只需要在初次请求时缓存表单(通过 GET 请求)。 随后请求所显示(通过 POST 请求)的表单不该使用缓存,因为此时表单中可能包含用户输入内容。 鉴于此种情况,可以使用 [[yii\widgets\FragmentCache::enabled|enabled]] 选项来指定缓存开关, 如下所示:

if ($this->beginCache($id, ['enabled' => Yii::$app->request->isGet])) {

    // ... 在此生成内容 ...

    $this->endCache();
}

缓存嵌套

片段缓存可以被嵌套使用。一个片段缓存可以被另一个包裹。 例如,评论被缓存在里层,同时整个评论的片段又被缓存在外层的文章中。 以下代码展示了片段缓存的嵌套使用:

if ($this->beginCache($id1)) {

    // ...在此生成内容...

    if ($this->beginCache($id2, $options2)) {

        // ...在此生成内容...

        $this->endCache();
    }

    // ...在此生成内容...

    $this->endCache();
}

可以为嵌套的缓存设置不同的配置项。例如,内层缓存和外层缓存使用不同的过期时间。 甚至当外层缓存的数据过期失效了,内层缓存仍然可能提供有效的片段缓存数据。 但是,反之则不然。如果外层片段缓存没有过期而被视为有效, 此时即使内层片段缓存已经失效,它也将继续提供同样的缓存副本。 因此,你必须谨慎处理缓存嵌套中的过期时间和依赖, 否则外层的片段很有可能返回的是不符合你预期的失效数据。

译注:外层的失效时间应该短于内层,外层的依赖条件应该低于内层,以确保最小的片段,返回的是最新的数据。

动态内容

使用片段缓存时,可能会遇到一大段较为静态的内容中有少许动态内容的情况。 例如,一个显示着菜单栏和当前用户名的页面头部。 还有一种可能是缓存的内容可能包含每次请求 都需要执行的 PHP 代码(例如注册资源包的代码)。 这两个问题都可以使用动态内容功能解决。

动态内容的意思是这部分输出的内容不该被缓存,即便是它被包裹在片段缓存中。 为了使内容保持动态,每次请求都执行 PHP 代码生成, 即使这些代码已经被缓存了。

可以在片段缓存中调用 [[yii\base\View::renderDynamic()]] 去插入动态内容, 如下所示:

if ($this->beginCache($id1)) {

    // ...在此生成内容...

    echo $this->renderDynamic('return Yii::$app->user->identity->name;');

    // ...在此生成内容...

    $this->endCache();
}

[[yii\base\View::renderDynamic()|renderDynamic()]] 方法接受一段 PHP 代码作为参数。 代码的返回值被看作是动态内容。这段代码将在每次请求时都执行, 无论其外层的片段缓存是否被存储。

Note: 从版本 2.0.14 开始,动态内容 API 通过 [[yii\base\DynamicContentAwareInterface]] 接口及其 [[yii\base\DynamicContentAwareTrait]] 特质开放。 举个例子,你可以参考 [[yii\widgets\FragmentCache]] 类。

页面缓存

页面缓存指的是在服务器端缓存整个页面的内容。 随后当同一个页面被请求时,内容将从缓存中取出,而不是重新生成。

页面缓存由 [[yii\filters\PageCache]] 类提供支持,该类是一个过滤器。 它可以像这样在控制器类中使用:

public function behaviors()
{
    return [
        [
            'class' => 'yii\filters\PageCache',
            'only' => ['index'],
            'duration' => 60,
            'variations' => [
                \Yii::$app->language,
            ],
            'dependency' => [
                'class' => 'yii\caching\DbDependency',
                'sql' => 'SELECT COUNT(*) FROM post',
            ],
        ],
    ];
}

上述代码表示页面缓存只在 index 操作时启用,页面内容最多被缓存 60 秒, 会随着当前应用的语言更改而变化。 如果文章总数发生变化则缓存的页面会失效。

如你所见,页面缓存和片段缓存极其相似。 它们都支持 durationdependenciesvariationsenabled 配置选项。 它们的主要区别是页面缓存是由过滤器实现,而片段缓存则是一个小部件

你可以在使用页面缓存的同时, 使用片段缓存动态内容

HTTP 缓存

除了前面章节讲到的服务器端缓存外, Web 应用还可以利用客户端缓存 去节省相同页面内容的生成和传输时间。

通过配置 [[yii\filters\HttpCache]] 过滤器,控制器操作渲染的内容就能缓存在客户端。 [[yii\filters\HttpCache|HttpCache]] 过滤器仅对 GETHEAD 请求生效, 它能为这些请求设置三种与缓存有关的 HTTP 头。

  • [[yii\filters\HttpCache::lastModified|Last-Modified]]
  • [[yii\filters\HttpCache::etagSeed|Etag]]
  • [[yii\filters\HttpCache::cacheControlHeader|Cache-Control]]

Last-Modified

Last-Modified 头使用时间戳标明页面自上次客户端缓存后是否被修改过。

通过配置 [[yii\filters\HttpCache::lastModified]] 属性向客户端发送 Last-Modified 头。 该属性的值应该为 PHP callable 类型,返回的是页面修改时的 Unix 时间戳。 该 callable 的参数和返回值应该如下:

/**
 * @param Action $action 当前处理的动作对象
 * @param array $params “params” 属性的值
 * @return int 页面修改时的 Unix 时间戳
 */
function ($action, $params)

以下是使用 Last-Modified 头的示例:

public function behaviors()
{
    return [
        [
            'class' => 'yii\filters\HttpCache',
            'only' => ['index'],
            'lastModified' => function ($action, $params) {
                $q = new \yii\db\Query();
                return $q->from('post')->max('updated_at');
            },
        ],
    ];
}

上述代码表明 HTTP 缓存只在 index 操作时启用。 它会基于页面最后修改时间生成一个 Last-Modified HTTP 头。 当浏览器第一次访问 index 页时,服务器将会生成页面并发送至客户端浏览器。 之后客户端浏览器在页面没被修改期间访问该页, 服务器将不会重新生成页面,浏览器会使用之前客户端缓存下来的内容。 因此服务端渲染和内容传输都将省去。

ETag

“Entity Tag”(实体标签,简称 ETag)使用一个哈希值表示页面内容。如果页面被修改过, 哈希值也会随之改变。通过对比客户端的哈希值和服务器端生成的哈希值, 浏览器就能判断页面是否被修改过,进而决定是否应该重新传输内容。

通过配置 [[yii\filters\HttpCache::etagSeed]] 属性向客户端发送 ETag 头。 该属性的值应该为 PHP callable 类型,返回的是一段种子字符用来生成 ETag 哈希值。 该 callable 的参数和返回值应该如下:

/**
 * @param Action $action 当前处理的动作对象
 * @param array $params “params” 属性的值
 * @return string 一段种子字符用来生成 ETag 哈希值
 */
function ($action, $params)

以下是使用 ETag 头的示例:

public function behaviors()
{
    return [
        [
            'class' => 'yii\filters\HttpCache',
            'only' => ['view'],
            'etagSeed' => function ($action, $params) {
                $post = $this->findModel(\Yii::$app->request->get('id'));
                return serialize([$post->title, $post->content]);
            },
        ],
    ];
}

上述代码表明 HTTP 缓存只在 view 操作时启用。 它会基于用户请求的标题和内容生成一个 ETag HTTP 头。 当浏览器第一次访问 view 页时,服务器将会生成页面并发送至客户端浏览器。 之后客户端浏览器标题和内容没被修改在期间访问该页,服务器将不会重新生成页面, 浏览器会使用之前客户端缓存下来的内容。 因此服务端渲染和内容传输都将省去。

ETag 相比 Last-Modified 能实现更复杂和更精确的缓存策略。 例如,当站点切换到另一个主题时可以使 ETag 失效。

复杂的 Etag 生成种子可能会违背使用 HttpCache 的初衷而引起不必要的性能开销, 因为响应每一次请求都需要重新计算 Etag。 请试着找出一个最简单的表达式去触发 Etag 失效。

Note: 为了遵循 RFC 7232(HTTP 1.1 协议), 如果同时配置了 ETagLast-Modified 头,HttpCache 将会同时发送它们。 并且如果客户端同时发送 If-None-Match 头和 If-Modified-Since 头, 则只有前者会被接受。

Cache-Control

Cache-Control 头指定了页面的常规缓存策略。 可以通过配置 [[yii\filters\HttpCache::cacheControlHeader]] 属性发送相应的头信息。默认发送以下头:

Cache-Control: public, max-age=3600

会话缓存限制器

当页面使 session 时,PHP 将会按照 PHP.INI 中所设置的 session.cache_limiter 值自动发送一些缓存相关的 HTTP 头。 这些 HTTP 头有可能会干扰你原本设置的 HttpCache 或让其失效。 为了避免此问题,默认情况下 HttpCache 禁止自动发送这些头。 想改变这一行为,可以配置 [[yii\filters\HttpCache::sessionCacheLimiter]] 属性。 该属性接受一个字符串值,包括 publicprivateprivate_no_expire,和 nocache。 请参考 PHP 手册中的缓存限制器 了解这些值的含义。

SEO 影响

搜索引擎趋向于遵循站点的缓存头。因为一些爬虫的抓取频率有限制, 启用缓存头可以可以减少重复请求数量,增加爬虫抓取效率 (译者:大意如此,但搜索引擎的排名规则不了解,好的缓存策略应该是可以为用户体验加分的)。

💖喜欢本文档的,欢迎点赞、收藏、留言或转发,谢谢支持!
作者邮箱:zhuzixian520@126.com,github地址:github.com/zhuzixian520

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
zhuzixian520
讨论数量: 0
发起讨论 只看当前版本


暂无话题~