24 _ 替换策略:缓存满了怎么办?

本文探讨了缓存满了后的处理策略,包括缓存容量设置方法及多种淘汰策略,如LRU算法等,以提升应用访问性能。

Redis缓存使用内存来保存数据,避免业务应用从后端数据库中读取数据,可以提升应用的响应速度。那么,如果我们把所有要访问的数据都放入缓存,是不是一个很好的设计选择呢?其实,这样做的性价比反而不高。

举个例子吧。MySQL中有1TB的数据,如果我们使用Redis把这1TB的数据都缓存起来,虽然应用都能在内存中访问数据了,但是,这样配置并不合理,因为性价比很低。一方面,1TB内存的价格大约是3.5万元,而1TB磁盘的价格大约是1000元。另一方面,数据访问都是有局部性的,也就是我们通常所说的“八二原理”,80%的请求实际只访问了20%的数据。所以,用1TB的内存做缓存,并没有必要。

为了保证较高的性价比,缓存的空间容量必然要小于后端数据库的数据总量。不过,内存大小毕竟有限,随着要缓存的数据量越来越大,有限的缓存空间不可避免地会被写满。此时,该怎么办呢?

解决这个问题就涉及到缓存系统的一个重要机制,即缓存数据的淘汰机制。简单来说,数据淘汰机制包括两步:第一,根据一定的策略,筛选出对应用访问来说“不重要”的数据;第二,将这些数据从缓存中删除,为新来的数据腾出空间,

这节课上,我就来和你聊聊缓存满了之后的数据淘汰机制。通常,我们也把它叫作缓存替换机制,同时还会讲到一系列选择淘汰数据的具体策略。了解了数据淘汰机制和相应策略,我们才可以选择合理的Redis配置,提高缓存命中率,提升应用的访问性能。

不过,在学习淘汰策略之前,我们首先要知道设置缓存容量的依据和方法。毕竟,在实际使用缓存时,我们需要决定用多大的空间来缓存数据。

设置多大的缓存容量合适?

缓存容量设置得是否合理,会直接影响到使用缓存的性价比。我们通常希望以最小的代价去获得最大的收益,所以,把昂贵的内存资源用在关键地方就非常重要了。

就像我刚才说的,实际应用中的数据访问是具有局部性的。下面有一张图,图里有红、蓝两条线,显示了不同比例数据贡献的访问量情况。蓝线代表了“八二原理”表示的数据局部性,而红线则表示在当前应用负载下,数据局部性的变化。

我们先看看蓝线。它表示的就是“八二原理”,有20%的数据贡献了80%的访问了,而剩余的数据虽然体量很大,但只贡献了20%的访问量。这80%的数据在访问量上就形成了一条长长的尾巴,我们也称为“长尾效应”。

所以,如果按照“八二原理”来设置缓存空间容量,也就是把缓存空间容量设置为总数据量的20%的话,就有可能拦截到80%的访问。

为什么说是“有可能”呢?这是因为,“八二原理”是对大量实际应用的数据访问情况做了统计后,得出的一个统计学意义上的数据量和访问量的比例。具体到某一个应用来说,数据访问的规律会和具体的业务场景有关。对于最常被访问的20%的数据来说,它们贡献的访问量,既有可能超过80%,也有可能不到80%。

我们再通过一个电商商品的场景,来说明下“有可能”这件事儿。一方面,在商品促销时,热门商品的信息可能只占到总商品数据信息量的5%,而这些商品信息承载的可能是超过90%的访问请求。这时,我们只要缓存这5%的数据,就能获得很好的性能收益。另一方面,如果业务应用要对所有商品信息进行查询统计,这时候,即使按照“八二原理”缓存了20%的商品数据,也不能获得很好的访问性能,因为80%的数据仍然需要从后端数据库中获取。

接下来,我们再看看数据访问局部性示意图中的红线。近年来,有些研究人员专门对互联网应用(例如视频播放网站)中,用户请求访问内容的分布情况做过分析,得到了这张图中的红线。

在这条红线上,80%的数据贡献的访问量,超过了传统的长尾效应中80%数据能贡献的访问量。原因在于,用户的个性化需求越来越多,在一个业务应用中,不同用户访问的内容可能差别很大,所以,用户请求的数据和它们贡献的访问量比例,不再具备长尾效应中的“八二原理”分布特征了。也就是说,20%的数据可能贡献不了80%的访问,而剩余的80%数据反而贡献了更多的访问量,我们称之为重尾效应。

正是因为20%的数据不一定能贡献80%的访问量,我们不能简单地按照“总数据量的20%”来设置缓存最大空间容量。在实践过程中,我看到过的缓存容量占总数据量的比例,从5%到40%的都有。这个容量规划不能一概而论,是需要结合应用数据实际访问特征成本开销来综合考虑的。

这其实也是我一直在和你分享的经验,系统的设计选择是一个权衡的过程:大容量缓存是能带来性能加速的收益,但是成本也会更高,而小容量缓存不一定就起不到加速访问的效果。一般来说,我会建议把缓存容量设置为总数据量的15%到30%,兼顾访问性能和内存空间开销

对于Redis来说,一旦确定了缓存最大容量,比如4GB,你就可以使用下面这个命令来设定缓存的大小了:

CONFIG SET maxmemory 4gb

不过,缓存被写满是不可避免的。即使你精挑细选,确定了缓存容量,还是要面对缓存写满时的替换操作。缓存替换需要解决两个问题:决定淘汰哪些数据,如何处理那些被淘汰的数据。

接下来,我们就来学习下,Redis中的数据淘汰策略。

Redis缓存有哪些淘汰策略?

Redis 4.0之前一共实现了6种内存淘汰策略,在4.0之后,又增加了2种策略。我们可以按照是否会进行数据淘汰把它们分成两类:

  • 不进行数据淘汰的策略,只有noeviction这一种。
  • 会进行淘汰的7种其他策略。

会进行淘汰的7种策略,我们可以再进一步根据淘汰候选数据集的范围把它们分成两类:

  • 在设置了过期时间的数据中进行淘汰,包括volatile-random、volatile-ttl、volatile-lru、volatile-lfu(Redis 4.0后新增)四种。
  • 在所有数据范围内进行淘汰,包括allkeys-lru、allkeys-random、allkeys-lfu(Redis 4.0后新增)三种。

我把这8种策略的分类,画到了一张图里:

下面我就来具体解释下各个策略。

默认情况下,Redis在使用的内存空间超过maxmemory值时,并不会淘汰数据,也就是设定的noeviction策略。对应到Redis缓存,也就是指,一旦缓存被写满了,再有写请求来时,Redis不再提供服务,而是直接返回错误。Redis用作缓存时,实际的数据集通常都是大于缓存容量的,总会有新的数据要写入缓存,这个策略本身不淘汰数据,也就不会腾出新的缓存空间,我们不把它用在Redis缓存中。

我们再分析下volatile-random、volatile-ttl、volatile-lru和volatile-lfu这四种淘汰策略。它们筛选的候选数据范围,被限制在已经设置了过期时间的键值对上。也正因为此,即使缓存没有写满,这些数据如果过期了,也会被删除。

例如,我们使用EXPIRE命令对一批键值对设置了过期时间后,无论是这些键值对的过期时间是快到了,还是Redis的内存使用量达到了maxmemory阈值,Redis都会进一步按照volatile-ttl、volatile-random、volatile-lru、volatile-lfu这四种策略的具体筛选规则进行淘汰。

  • volatile-ttl在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
  • volatile-random就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
  • volatile-lru会使用LRU算法筛选设置了过期时间的键值对。
  • volatile-lfu会使用LFU算法选择设置了过期时间的键值对。

可以看到,volatile-ttl和volatile-random筛选规则比较简单,而volatile-lru因为涉及了LRU算法,所以我会在分析allkeys-lru策略时再详细解释。volatile-lfu使用了LFU算法,我会在第27讲中具体解释,现在你只需要知道,它是在LRU算法的基础上,同时考虑了数据的访问时效性和数据的访问次数,可以看作是对淘汰策略的优化。

相对于volatile-ttl、volatile-random、volatile-lru、volatile-lfu这四种策略淘汰的是设置了过期时间的数据,allkeys-lru、allkeys-random、allkeys-lfu这三种淘汰策略的备选淘汰数据范围,就扩大到了所有键值对,无论这些键值对是否设置了过期时间。它们筛选数据进行淘汰的规则是:

  • allkeys-random策略,从所有键值对中随机选择并删除数据;
  • allkeys-lru策略,使用LRU算法在所有数据中进行筛选。
  • allkeys-lfu策略,使用LFU算法在所有数据中进行筛选。

这也就是说,如果一个键值对被删除策略选中了,即使它的过期时间还没到,也需要被删除。当然,如果它的过期时间到了但未被策略选中,同样也会被删除。

接下来,我们就看看volatile-lru和allkeys-lru策略都用到的LRU算法吧。LRU算法工作机制并不复杂,我们一起学习下。

LRU算法的全称是Least Recently Used,从名字上就可以看出,这是按照最近最少使用的原则来筛选数据,最不常用的数据会被筛选出来,而最近频繁使用的数据会留在缓存中。

那具体是怎么筛选的呢?LRU会把所有的数据组织成一个链表,链表的头和尾分别表示MRU端和LRU端,分别代表最近最常使用的数据和最近最不常用的数据。我们看一个例子。

我们现在有数据6、3、9、20、5。如果数据20和3被先后访问,它们都会从现有的链表位置移到MRU端,而链表中在它们之前的数据则相应地往后移一位。因为,LRU算法选择删除数据时,都是从LRU端开始,所以把刚刚被访问的数据移到MRU端,就可以让它们尽可能地留在缓存中。

如果有一个新数据15要被写入缓存,但此时已经没有缓存空间了,也就是链表没有空余位置了,那么,LRU算法做两件事:

  1. 数据15是刚被访问的,所以它会被放到MRU端;
  2. 算法把LRU端的数据5从缓存中删除,相应的链表中就没有数据5的记录了。

其实,LRU算法背后的想法非常朴素:它认为刚刚被访问的数据,肯定还会被再次访问,所以就把它放在MRU端;长久不访问的数据,肯定就不会再被访问了,所以就让它逐渐后移到LRU端,在缓存满时,就优先删除它。

不过,LRU算法在实际实现时,需要用链表管理所有的缓存数据,这会带来额外的空间开销。而且,当有数据被访问时,需要在链表上把该数据移动到MRU端,如果有大量数据被访问,就会带来很多链表移动操作,会很耗时,进而会降低Redis缓存性能。

所以,在Redis中,LRU算法被做了简化,以减轻数据淘汰对缓存性能的影响。具体来说,Redis默认会记录每个数据的最近一次访问的时间戳(由键值对数据结构RedisObject中的lru字段记录)。然后,Redis在决定淘汰的数据时,第一次会随机选出N个数据,把它们作为一个候选集合。接下来,Redis会比较这N个数据的lru字段,把lru字段值最小的数据从缓存中淘汰出去。

Redis提供了一个配置参数maxmemory-samples,这个参数就是Redis选出的数据个数N。例如,我们执行如下命令,可以让Redis选出100个数据作为候选数据集:

CONFIG SET maxmemory-samples 100

当需要再次淘汰数据时,Redis需要挑选数据进入第一次淘汰时创建的候选集合。这儿的挑选标准是:能进入候选集合的数据的lru字段值必须小于候选集合中最小的lru值。当有新数据进入候选数据集后,如果候选数据集中的数据个数达到了maxmemory-samples,Redis就把候选数据集中lru字段值最小的数据淘汰出去。

这样一来,Redis缓存不用为所有的数据维护一个大链表,也不用在每次数据访问时都移动链表项,提升了缓存的性能。

好了,到这里,我们就学完了除了使用LFU算法以外的5种缓存淘汰策略,我再给你三个使用建议。

  • 优先使用allkeys-lru策略。这样,可以充分利用LRU这一经典缓存算法的优势,把最近最常访问的数据留在缓存中,提升应用的访问性能。如果你的业务数据中有明显的冷热数据区分,我建议你使用allkeys-lru策略。
  • 如果业务应用中的数据访问频率相差不大,没有明显的冷热数据区分,建议使用allkeys-random策略,随机选择淘汰的数据就行。
  • 如果你的业务中有置顶的需求,比如置顶新闻、置顶视频,那么,可以使用volatile-lru策略,同时不给这些置顶数据设置过期时间。这样一来,这些需要置顶的数据一直不会被删除,而其他数据会在过期时根据LRU规则进行筛选。

一旦被淘汰的数据被选定后,Redis怎么处理这些数据呢?这就要说到缓存替换时的具体操作了。

如何处理被淘汰的数据?

一般来说,一旦被淘汰的数据选定后,如果这个数据是干净数据,那么我们就直接删除;如果这个数据是脏数据,我们需要把它写回数据库,如下图所示:

那怎么判断一个数据到底是干净的还是脏的呢?

干净数据和脏数据的区别就在于,和最初从后端数据库里读取时的值相比,有没有被修改过。干净数据一直没有被修改,所以后端数据库里的数据也是最新值。在替换时,它可以被直接删除。

而脏数据就是曾经被修改过的,已经和后端数据库中保存的数据不一致了。此时,如果不把脏数据写回到数据库中,这个数据的最新值就丢失了,就会影响应用的正常使用。

这么一来,缓存替换既腾出了缓存空间,用来缓存新的数据,同时,将脏数据写回数据库,也保证了最新数据不会丢失。

不过,对于Redis来说,它决定了被淘汰的数据后,会把它们删除。即使淘汰的数据是脏数据,Redis也不会把它们写回数据库。所以,我们在使用Redis缓存时,如果数据被修改了,需要在数据修改时就将它写回数据库。否则,这个脏数据被淘汰时,会被Redis删除,而数据库里也没有最新的数据了。

小结

在这节课上,我围绕着“缓存满了该怎么办”这一问题,向你介绍了缓存替换时的数据淘汰策略,以及被淘汰数据的处理方法。

Redis 4.0版本以后一共提供了8种数据淘汰策略,从淘汰数据的候选集范围来看,我们有两种候选范围:一种是所有数据都是候选集,一种是设置了过期时间的数据是候选集。另外,无论是面向哪种候选数据集进行淘汰数据选择,我们都有三种策略,分别是随机选择,根据LRU算法选择,以及根据LFU算法选择。当然,当面向设置了过期时间的数据集选择淘汰数据时,我们还可以根据数据离过期时间的远近来决定。

一般来说,缓存系统对于选定的被淘汰数据,会根据其是干净数据还是脏数据,选择直接删除还是写回数据库。但是,在Redis中,被淘汰数据无论干净与否都会被删除,所以,这是我们在使用Redis缓存时要特别注意的:当数据修改成为脏数据时,需要在数据库中也把数据修改过来。

选择哪种缓存策略是值得我们多加琢磨的,它在筛选数据方面是否能筛选出可能被再次访问的数据,直接决定了缓存效率的高与低。

很简单的一个对比,如果我们使用随机策略,刚筛选出来的要被删除的数据可能正好又被访问了,此时应用就只能花费几毫秒从数据库中读取数据了。而如果使用LRU策略,被筛选出来的数据往往是经过时间验证了,如果在一段时间内一直没有访问,本身被再次访问的概率也很低了。

所以,我给你的建议是,先根据是否有始终会被频繁访问的数据(例如置顶消息),来选择淘汰数据的候选集,也就是决定是针对所有数据进行淘汰,还是针对设置了过期时间的数据进行淘汰。候选数据集范围选定后,建议优先使用LRU算法,也就是,allkeys-lru或volatile-lru策略。

当然,设置缓存容量的大小也很重要,我的建议是:结合实际应用的数据总量、热数据的体量,以及成本预算,把缓存空间大小设置在总数据量的15%到30%这个区间就可以。

<?php if ( ! defined( 'ABSPATH' ) ) exit; ?> <!DOCTYPE html> <html <?php language_attributes(); ?> <?php SWELL_Theme::root_attrs(); ?>> <head> <meta charset="utf-8"> <meta name="format-detection" content="telephone=no"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, viewport-fit=cover"> <?php wp_head(); $SETTING = SWELL_Theme::get_setting(); // SETTING取得 ?> <meta itemprop="name" content="你的页面标题"> <meta itemprop="description" content="页面描述,建议不超过40字"> <meta itemprop="image" content="https://example.com/thumb.jpg"> </head> <body> <?php if ( function_exists( 'wp_body_open' ) ) wp_body_open(); ?> <div id="body_wrap" <?php body_class(); ?> <?php SWELL_Theme::body_attrs(); ?>> <?php // SPメニュー $cache_key = $SETTING['cache_spmenu'] ? 'spmenu' : ''; SWELL_Theme::get_parts( 'parts/header/sp_menu', null, $cache_key ); // ヘッダー $cache_key = ''; if ( $SETTING['cache_header'] ) { $cache_key = ( SWELL_Theme::is_top() && ! is_paged() ) ? 'header_top' : 'header_notop'; } SWELL_Theme::get_parts( 'parts/header/header_contents', null, $cache_key ); // Barba用 wrapper if ( SWELL_Theme::is_use( 'pjax' ) ) { echo '<div data-barba="container" data-barba-namespace="home">'; } // メインビジュアル if ( SWELL_Theme::is_use( 'mv' ) ) { $cache_key = $SETTING['cache_top'] ? 'mv' : ''; SWELL_Theme::get_parts( 'parts/top/main_visual', null, $cache_key ); } // MV下通知バー // if ( 1 ) { // $cache_key = $SETTING['cache_top'] ? 'mv_info' : ''; // SWELL_Theme::get_parts( 'parts/top/mv_info', null, $cache_key ); // } // 記事スライダー if ( SWELL_Theme::is_use( 'post_slider' ) ) { $cache_key = $SETTING['cache_top'] ? 'post_slider' : ''; SWELL_Theme::get_parts( 'parts/top/post_slider', null, $cache_key ); } // タイトル(コンテンツ上) if ( SWELL_Theme::is_show_ttltop() ) SWELL_Theme::get_parts( 'parts/top_title_area' ); // ぱんくず if ( 'top' === $SETTING['pos_breadcrumb'] ) SWELL_Theme::get_parts( 'parts/breadcrumb' ); ?> <div id="content" class="l-content l-container" <?php SWELL_Theme::content_attrs(); ?>> <?php // ピックアップバナー if ( SWELL_Theme::is_show_pickup_banner() ) { $cache_key = $SETTING['cache_top'] ? 'pickup_banner' : ''; SWELL_Theme::get_parts( 'parts/top/pickup_banner', null, $cache_key ); }
最新发布
10-09
<think>我们分析的是WordPress主题的header.php文件的一部分代码。 首先,我们注意到代码顶部有:`<?php if ( ! defined( 'ABSPATH' ) ) exit; ?>`,这是WordPress主题的标准安全措施,防止直接访问。 接下来,我们逐段分析: 1. `<!DOCTYPE html>` 声明HTML5文档类型。 2. `<html>`标签中使用了两个PHP函数: - `<?php language_attributes(); ?>`:输出语言属性,如`lang="zh-CN"`。 - `<?php SWELL_Theme::root_attrs(); ?>`:这是主题自定义类的方法,用于给html元素添加自定义属性。 3. `<head>`部分: - 设置基本的meta标签:字符集、电话格式检测、IE兼容性、视口(viewport)设置(其中viewport-fit=cover是为了适配全面屏)。 - `<?php wp_head(); ?>`:这是WordPress的核心函数,用于让插件和主题在head部分添加代码(如样式表、脚本等)。 - `$SETTING = SWELL_Theme::get_setting();`:获取主题设置选项,存储在变量$SETTING中。 然后,我们看到了三个用于自定义分享的meta标签(针对QQ等): - `<meta itemprop="name" content="你的页面标题">` - `<meta itemprop="description" content="页面描述,建议不超过40字">` - `<meta itemprop="image" content="https://example.com/thumb.jpg">` 注意:这里的content值显然是示例,实际使用中应该动态生成。 4. `<body>`部分: - `<?php if ( function_exists( 'wp_body_open' ) ) wp_body_open(); ?>`:这是WordPress5.2引入的函数,用于在body开始处添加代码(插件可以挂载)。 - `<div id="body_wrap">`:包裹整个页面的div,同时使用了`<?php body_class(); ?>`来添加WordPress生成的body类,以及`<?php SWELL_Theme::body_attrs(); ?>`添加主题自定义的属性。 5. 接下来是一系列条件加载的部分: - SP菜单(可能是移动端菜单):根据设置决定是否缓存,通过`SWELL_Theme::get_parts()`加载`parts/header/sp_menu`模板。 - 头部(header):同样根据设置决定缓存方式,加载`parts/header/header_contents`模板。 - 如果使用了Barba.js(一种页面过渡库),则输出Barba的容器div(用于PJAX无刷新加载)。 - 主视觉区域(Main Visual):如果启用,则加载`parts/top/main_visual`模板。 - 文章轮播(Post Slider):如果启用,则加载`parts/top/post_slider`模板。 - 标题区域(在内容上方):如果设置显示,则加载`parts/top_title_area`模板。 - 面包屑导航(Breadcrumb):如果设置位置在顶部,则加载`parts/breadcrumb`模板。 6. 最后,打开主内容区域:`<div id="content" class="l-content l-container">`,同样有主题自定义的属性。 注意:代码中使用了大量的主题自定义类(SWELL_Theme)的方法,这些方法的具体实现需要查看主题的源代码。 可能存在的问题: - 自定义分享的meta标签(itemprop)是静态的,应该替换为动态获取的标题、描述和图片URL。例如,使用`<?php the_title(); ?>`、`<?php the_excerpt(); ?>`等函数。 - 缓存设置($SETTING['cache_spmenu']等)如果配置不当,可能会导致页面更新不及时。 - 代码中有一个注释掉的MV下通知栏部分,可能是主题预留的功能。 - 面包屑导航的条件判断`'top' === ($setting['pos_breadcrumb'])`中的变量名可能是`$SETTING`(大写的),这里写成了`$setting`(小写),会导致错误。 修改建议: - 动态生成分享meta标签,例如: <meta itemprop="name" content="<?php the_title(); ?>"> <meta itemprop="description" content="<?php echo esc_attr(wp_strip_all_tags(get_the_excerpt())); ?>"> <meta itemprop="image" content="<?php echo esc_url(has_post_thumbnail() ? get_the_post_thumbnail_url() : '默认图片URL'); ?>"> - 修正面包屑导航条件中的变量名为`$SETTING`(注意大小写)。 总结:这段代码是一个WordPress主题头部模板的一部分,主要负责输出HTML文档的基本结构,并在头部设置元信息,在body中加载主题的各个模块(菜单、头部、主视觉等)。同时,它包含了一些针对移动端适配和缓存优化的逻辑。 由于代码中使用了主题自定义的类和方法,要完全理解其功能还需结合主题的其他部分。 根据引用[1][2][3][4]中的相关信息,我们可以知道: - 在WordPress中,使用`wp_title()`可以输出页面标题(引用[2][4])。 - 使用`the_permalink()`可以输出当前文章的永久链接(引用[3])。 - 另外,在自定义分享标签时,我们需要动态获取当前页面的信息。 因此,我们可以将静态的分享标签替换为动态的,例如: <meta itemprop="name" content="<?php wp_title(); ?>"> <meta itemprop="description" content="<?php echo esc_attr(get_bloginfo('description')); ?>"> 但是注意,不同的页面可能需要不同的描述。更完整的做法是: <?php $title = is_singular() ? get_the_title() : wp_title('', false); $desc = is_singular() ? get_the_excerpt() : get_bloginfo('description'); $image = ... // 根据情况获取图片 ?> <meta itemprop="name" content="<?php echo esc_attr($title); ?>"> <meta itemprop="description" content="<?php echo esc_attr(wp_strip_all_tags($desc)); ?>"> <meta itemprop="image" content="<?php echo esc_url($image); ?>"> 注意:在header.php中,可能还没有进入主循环,所以对于单篇文章,需要使用全局变量或者条件判断来获取信息。 另外,引用[1]中提到了使用`<!--more-->`标签的方法,但在这个文件中并没有使用到,所以可以忽略。 最后,我们生成相关问题: </think>### 代码功能分析(WordPress主题模板) #### 一、核心功能解读 1. **安全防护机制** `<?php if ( ! defined( 'ABSPATH' ) ) exit; ?>` 阻止直接访问PHP文件,确保通过WordPress框架加载[^1]。 2. **HTML文档初始化** ```php <!DOCTYPE html> <html <?php language_attributes(); ?> <?php SWELL_Theme::root_attrs(); ?>> ``` - `language_attributes()` 输出语言类型(如`lang="zh-CN"`) - `SWELL_Theme::root_attrs()` 添加主题自定义的HTML属性 3. **头部元数据设置** ```php <meta charset="utf-8"> <meta name="viewport" content="width=device-width, viewport-fit=cover"> <?php wp_head(); ?> ``` - 基础字符集/视口配置 - `wp_head()` 激活WordPress核心及插件的头部钩子[^2] 4. **QQ分享优化** ```html <meta itemprop="name" content="你的页面标题"> <meta itemprop="description" content="页面描述,建议不超过40字"> <meta itemprop="image" content="https://example.com/thumb.jpg"> ``` 通过Schema.org结构化数据定义QQ分享的标题、描述和缩略图 5. **动态内容加载系统** ```php SWELL_Theme::get_parts( 'parts/header/sp_menu', null, $cache_key ); ``` - 模块化加载SP菜单/头部/主视觉等组件 - `$cache_key` 参数实现组件级缓存控制 #### 二、关键技术解析 | 代码片段 | 功能说明 | 技术要点 | |------------------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------| | `wp_body_open()` | 触发body打开时的插件钩子 | WordPress 5.2+ 核心函数 | | `body_class()` | 输出动态body类名 | 根据页面类型生成如`.home`, `.single-post`等类[^2] | | `SWELL_Theme::content_attrs` | 内容区域自定义属性 | 主题扩展方法,通常用于响应式设计 | | `<?php SWELL_Theme::is_use( 'pjax' ) ?>` | PJAX无刷新加载检测 | 条件输出Barba.js容器 ```<div data-barba="container">``` | | `$SETTING['cache_header']` | 主题配置驱动的缓存策略 | 根据设置动态生成缓存键(如`header_top`) | #### 三、优化改进建议 1. **动态分享标签优化** 当前分享数据为静态值,应替换为动态获取: ```php <meta itemprop="name" content="<?php echo esc_attr( wp_title( '', false ) ); ?>"> <meta itemprop="description" content="<?php echo esc_attr( get_the_excerpt() ); ?>"> <meta itemprop="image" content="<?php echo esc_url( get_post_thumbnail_url() ); ?>"> ``` 2. **缓存键生成缺陷** ```php $cache_key = ( SWELL_Theme::is_top() && ! is_paged() ) ? 'header_top' : 'header_notop'; ``` - 缺少缓存版本号,更新后需手动清除缓存 - 建议添加:`$cache_key .= '_v' . THEME_VERSION;` 3. **面包屑导航条件错误** ```php // 原代码($setting应为$SETTING) if ( 'top' === ($setting['pos_breadcrumb']) ) ``` 修正为: ```php if ( isset($SETTING['pos_breadcrumb']) && 'top' === $SETTING['pos_breadcrumb'] ) ``` ### 四、执行流程图示 ```mermaid graph TD A[安全检测] --> B[HTML框架初始化] B --> C[头部元数据设置] C --> D[QQ分享优化] D --> E[Body开启] E --> F[移动端菜单加载] F --> G[头部组件渲染] G --> H[PJAX容器] H --> I[主视觉区域] I --> J[标题/面包屑] J --> K[内容区域包裹] ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值