Lingkaの宝藏之地
首页项目归档杂谈说说照片墙音乐友链关于

云端杂谈

“ 代码、学术与硬件设计的碎片记录 ”

cover
2026-07-04 21:04:47

Vivado 10G/25G Ethernet Subsystem Example Design 的 XDC 约束问题整理

本工程使用的是 AMD/Xilinx 的 `10G/25G Ethernet Subsystem` IP,主要配置如下: ``` Select Core = Ethernet MAC + PCS/PMA 64-bit Speed = 10.3125G Data Path Interface = AXI Stream Num of Cores = 2 Base R/KR Standard = BASE-R GT RefClk = 156.25 MHz GT Lane = X1Y4、X1Y6 Control Interface = Control and Status Vectors ``` 测试目标是让 FPGA 板卡上的两个 SFP+ 口互相通信: ``` SFP0 / GT X1Y4 <---- DAC/光纤 ----> SFP1 / GT X1Y6 ``` Example Design 顶层主要包括: ``` GT TX/RX 高速收发口 GT 156.25 MHz 参考时钟 INIT_CLK / dclk sys_reset pkt_gen_mon 发包检测模块 rx_gt_locked_led rx_block_lock_led completion_status ``` 本次问题主要集中在 Vivado XDC 约束文件中,尤其是 GT/MAC 派生时钟和 `dclk` 之间的异步时钟约束问题。 --- # 二、遇到的问题 1:XDC 中使用 `concat` 报错 ## 1. 报错信息 ``` [Designutils 20-1307] Command 'concat' is not supported in the xdc constraint file. ``` ## 2. 原因分析 一开始在 XDC 文件中写了类似下面的语句: ``` set gt_user_clks [get_clocks -quiet -of_objects [concat $gt_rx_userclk_pins $gt_tx_userclk_pins]] ``` 问题在于: ``` XDC 文件虽然看起来像 Tcl 文件, 但不能把它当成完整 Tcl 脚本使用。 ``` `concat` 属于普通 Tcl 命令,在 Vivado 的 XDC 约束文件中不能这样直接使用。 ## 3. 解决方法 XDC 文件中尽量不要写复杂 Tcl 逻辑,例如: ``` concat if foreach proc ``` XDC 中应尽量只写标准约束命令,例如: ``` create_clock create_generated_clock set_property set_false_path set_max_delay set_clock_groups ``` 如果确实需要复杂 Tcl 逻辑,应该单独写 `.tcl` 脚本,而不是直接写在 `.xdc` 文件中。 --- # 三、遇到的问题 2:XDC 中使用 `if` 报错 ## 1. 报错信息 ``` [Designutils 20-1307] Command 'if' is not supported in the xdc constraint file. ``` ## 2. 原因分析 当时为了判断 clock 是否存在,写了类似下面的判断: ``` if {[llength $gt_user_clks] > 0} { set_clock_groups -asynchronous \ -group [get_clocks dclk] \ -group $gt_user_clks } ``` 但是该写法不适合直接放在 XDC 约束文件中。 ## 3. 解决方法 不要在普通 XDC 文件中写 `if` 判断。 正确思路是: ``` 简单约束写在 XDC; 复杂判断逻辑写在单独 Tcl 脚本中。 ``` --- # 四、遇到的问题 3:`set_clock_groups` 找不到对象 ## 1. 报错信息 ``` [Vivado 12-4739] set_clock_groups: No valid object(s) found for '-group' ``` 或者: ``` [Vivado 12-4739] set_clock_groups: No valid object(s) found for '-group [get_clocks {rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1}]' ``` ## 2. 原来的约束写法 一开始写的是: ``` set_clock_groups -asynchronous \ -group [get_clocks dclk] \ -group [get_clocks {rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1}] ``` ## 3. 原因分析 这些时钟: ``` rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1 ``` 是 10G Ethernet / GT 相关的派生用户时钟。 在综合阶段,Vivado 可能还没有生成或识别这些时钟对象,所以: ``` get_clocks {rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1} ``` 返回的是空对象。 即使加了 `-quiet`: ``` get_clocks -quiet xxx ``` 也只能让 `get_clocks` 本身不打印 warning,不能解决外层命令的问题。 因为外层仍然相当于执行了: ``` set_clock_groups -group 空对象 ``` 所以还是会报错: ``` No valid object(s) found for '-group' ``` --- # 五、为什么 Tcl Console 能查到 clock,但 XDC 中仍然报错? ## 1. 在 Tcl Console 中查询 打开 implemented design 后执行: ``` open_run impl_1 get_clocks ``` 可以查到类似下面的时钟: ``` gt_refclk_p init_clk dclk qpll0clk_in[0] qpll0refclk_in[0] rxoutclk_out[0] rxoutclkpcs_out[0] txoutclk_out[0] rxoutclk_out[0]_1 rxoutclkpcs_out[0]_1 txoutclk_out[0]_1 rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1 ``` 并且执行: ``` get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1] ``` 能正常返回: ``` rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1 ``` ## 2. 关键区别 这里要注意: ``` Tcl Console 中能查到,是因为已经打开了 implemented design。 ``` 也就是说,这些时钟在实现后的设计中是存在的。 但是普通 XDC 文件在综合阶段也会被读取。 综合阶段这些时钟对象可能还没有建立,所以会报错。 因此,本质问题是: ``` XDC 约束读取阶段 和 clock 对象生成阶段 不一致。 ``` --- # 六、遇到的问题 4:Implementation 完成但 Timing Failed ## 1. 报错信息 ``` [Timing 38-282] The design failed to meet the timing requirements. ``` Timing Summary 中可以看到类似路径: ``` dclk -> rx_core_clk_0 dclk -> rx_core_clk_1 dclk -> tx_clk_out_0 dclk -> tx_clk_out_1 ``` 并且 WNS/TNS 很差。 ## 2. 原因分析 `dclk` 是初始化、控制、状态相关的 free-running clock。 而下面这些时钟: ``` rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1 ``` 是 10G Ethernet / GT 相关的用户时钟。 它们之间本来就不是同一个同步时钟域,而是异步时钟域。 如果没有正确添加异步时钟组约束,Vivado 就会尝试检查: ``` dclk -> rx_core_clk dclk -> tx_clk_out ``` 这类跨时钟域路径的 setup/hold timing。 于是就会出现 Timing Failed。 ## 3. 结论 这个 Timing Failed 不一定是 RTL 写错了,而是: ``` dclk 和 GT user clocks 之间的 CDC 约束没有正确设置。 ``` --- # 七、最终解决方案:单独建立 Implementation-only XDC ## 1. 核心思路 不要把下面这条约束放在普通 XDC 中: ``` set_clock_groups -asynchronous \ -group [get_clocks -quiet dclk] \ -group [get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]] ``` 原因是普通 XDC 会在综合阶段读取,而综合阶段这些 clock 可能还不存在。 正确做法是: ``` 把 dclk 与 GT user clocks 的异步时钟组约束, 单独放到一个只用于 Implementation 的 XDC 文件中。 ``` --- # 八、具体操作步骤 ## 第一步:删除或注释原 XDC 中的错误约束 在原来的: ``` xxv_ethernet_0_example_top.xdc ``` 中,把最后类似下面的约束删除或注释掉: ``` # set_clock_groups -asynchronous \ # -group [get_clocks -quiet dclk] \ # -group [get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]] ``` 注意: ``` 这条约束不能放在综合阶段也会读取的普通 XDC 文件里。 ``` --- ## 第二步:新建 Implementation-only XDC 文件 新建文件: ``` async_clk_impl.xdc ``` 文件内容如下: ``` ######################################## # async_clk_impl.xdc # Only used in Implementation # dclk vs GT user clocks ######################################## set_clock_groups -asynchronous \ -group [get_clocks -quiet dclk] \ -group [get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]] ``` --- ## 第三步:设置该 XDC 只用于 Implementation 在 Vivado 左侧 `Sources` 中选中: ``` async_clk_impl.xdc ``` 然后在 `Source File Properties` 中设置: ``` USED_IN_IMPLEMENTATION = 勾选 USED_IN_SYNTHESIS = 不勾选 ``` 也可以用 Tcl 命令设置: ``` set_property USED_IN {implementation} [get_files async_clk_impl.xdc] ``` 这样综合阶段不会读取这条约束,Implementation 阶段才会使用它。 --- # 九、检查真实 clock 名的方法 如果不确定真实时钟名,可以先打开 implemented design: ``` open_run impl_1 ``` 然后查询所有时钟: ``` get_clocks ``` 也可以模糊查询: ``` get_clocks *clk* get_clocks *dclk* get_clocks *rx* get_clocks *tx* ``` 本工程中查询到的关键时钟包括: ``` dclk rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1 ``` 进一步验证: ``` get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1] ``` 如果返回: ``` rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1 ``` 说明这些 clock 名是有效的。 --- # 十、重新运行流程 修改完成后,建议重新跑完整流程: ``` Reset Synthesis Run Run Synthesis Run Implementation Report Timing Summary ``` 如果 Timing 通过,再生成 bitstream: ``` Generate Bitstream ``` --- # 十一、本次问题总结 ## 问题 1:XDC 里用了 `concat` 错误原因: ``` XDC 中不适合直接使用普通 Tcl 的 concat 命令。 ``` 解决方法: ``` 不要在 XDC 里写复杂 Tcl 逻辑。 ``` --- ## 问题 2:XDC 里用了 `if` 错误原因: ``` XDC 中不适合直接写普通 Tcl 的 if 判断。 ``` 解决方法: ``` 不要在 XDC 中写 if 判断; 复杂判断逻辑应放到单独 Tcl 脚本中。 ``` --- ## 问题 3:`set_clock_groups` 找不到对象 错误原因: ``` 综合阶段相关 clock 对象还不存在,get_clocks 返回空。 ``` 解决方法: ``` 把相关 set_clock_groups 约束放到 Implementation-only XDC 中。 ``` --- ## 问题 4:Implementation Timing Failed 错误原因: ``` dclk 和 GT/MAC 用户时钟之间的异步路径没有正确切掉。 ``` 解决方法: ``` set_clock_groups -asynchronous \ -group [get_clocks -quiet dclk] \ -group [get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]] ``` 并且这条约束应该放在: ``` async_clk_impl.xdc ``` 中,同时设置: ``` USED_IN_IMPLEMENTATION = true USED_IN_SYNTHESIS = false ``` --- # 十二、经验教训 1. XDC 虽然长得像 Tcl,但不能完全当成普通 Tcl 脚本使用。 2. `get_clocks` 在综合阶段和实现阶段能查到的对象可能不同。 3. `-quiet` 不能解决空对象问题,只是隐藏部分 warning。 4. `set_clock_groups` 的 `-group` 不能为空。 5. 对 GT、Ethernet IP 这类复杂 IP,派生时钟经常在实现阶段才更完整。 6. 如果某条约束只适合实现阶段,应单独放在 Implementation-only XDC 中。 7. 对 10G Ethernet Example Design,`dclk` 和 GT user clocks 一般应按异步时钟域处理。 8. Timing Failed 不一定代表 RTL 写错,也可能是 CDC 约束没有正确设置。 9. 查时钟名时,最好在 implemented design 打开后查询。 10. 不要只看 Tcl Console 能不能查到 clock,还要注意该约束在哪个阶段被读取。 --- # 十三、最终推荐的 XDC 文件结构 建议把 XDC 文件分开管理: ``` constraints/ ├── xxv_ethernet_0_example_top.xdc │ ├── INIT_CLK_P/N 约束 │ ├── gt_refclk_p/n 约束 │ ├── 官方 example design 自带 set_false_path / set_max_delay / waiver │ ├── sfp.xdc │ ├── SFP0 GT TX/RX 管脚约束 │ ├── SFP1 GT TX/RX 管脚约束 │ ├── SFP TX_DISABLE / LED / Reset 等板级 GPIO 约束 │ └── async_clk_impl.xdc └── dclk 与 GT user clocks 的异步时钟组约束 仅用于 Implementation ``` --- # 十四、最终 `async_clk_impl.xdc` 内容 ``` ######################################## # async_clk_impl.xdc # Only used in Implementation # dclk vs GT user clocks ######################################## set_clock_groups -asynchronous \ -group [get_clocks -quiet dclk] \ -group [get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]] ``` 文件属性设置: ``` USED_IN_IMPLEMENTATION = true USED_IN_SYNTHESIS = false ``` --- # 十五、上板后优先检查的信号 bitstream 生成后,下载到 FPGA 板卡。 两个 SFP+ 口用 DAC 线或光纤互连。 优先观察下面这些信号: ``` rx_gt_locked_led_0 rx_gt_locked_led_1 rx_block_lock_led_0 rx_block_lock_led_1 completion_status[4:0] ``` 正常情况下希望看到: ``` rx_gt_locked_led_0 = 1 rx_gt_locked_led_1 = 1 rx_block_lock_led_0 = 1 rx_block_lock_led_1 = 1 ``` --- ## 1. `rx_gt_locked_led = 1` 的含义 ``` 说明 GT 接收侧已经锁定。 ``` 也就是 GT 物理层收发器本身基本起来了。 --- ## 2. `rx_block_lock_led = 1` 的含义 ``` 说明 10GBASE-R PCS 已经完成 block lock。 ``` 也就是 10GBASE-R 的 64b/66b 码流已经能被正确识别,链路基本起来。 --- ## 3. 如果 `rx_gt_locked_led = 1`,但 `rx_block_lock_led = 0` 这种情况说明: ``` GT 本身已经锁定, 但是 10GBASE-R 码流没有锁住。 ``` 此时需要重点检查: ``` SFP 线缆或光模块是否正常 TX_DISABLE 是否拉低 GT TX/RX 管脚是否约束正确 P/N 极性是否接反 GT Lane 是否对应 X1Y4 和 X1Y6 156.25 MHz GT 参考时钟是否正确 两端速率和编码方式是否一致 ``` --- # 十六、最终结论 本次 XDC 问题的核心不是 10G/25G Ethernet Subsystem IP 配置错误,而是: ``` XDC 约束的读取阶段 和 clock 对象生成阶段 不一致。 ``` 综合阶段找不到下面这些派生时钟: ``` rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1 ``` 所以不能把相关 `set_clock_groups` 约束放在普通 XDC 文件里。 最终解决方法是: ``` 单独创建 async_clk_impl.xdc; 只用于 Implementation; 在里面约束 dclk 与 GT user clocks 为异步时钟组。 ``` 这样可以同时解决两个问题: ``` 1. 避免综合阶段 clock 对象为空导致报错; 2. 解决 Implementation 阶段 dclk -> rx_core_clk / tx_clk_out 的 Timing Failed 问题。 ``` 最终推荐约束如下: ``` set_clock_groups -asynchronous \ -group [get_clocks -quiet dclk] \ -group [get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]] ``` 该约束应放在: ``` async_clk_impl.xdc ``` 并设置为: ``` USED_IN_IMPLEMENTATION = true USED_IN_SYNTHESIS = false ``` ‍
#FPGA#XDC
cover
2026-06-25 20:04:14

AXU9EGB 双 SFP+(XCZU9EG-FFVB1156-2-I)GT Quad / Lane 选择调试记录

为 AXU9EGB 开发板(黑金开发板)上的两个 SFP+ 光口配置 Vivado `10G/25G Ethernet Subsystem` IP,并确认其对应的 GTH Quad 和 Lane。 器件型号: ``` xczu9eg-ffvb1156-2-i ``` 板卡光口连接关系: 接口板卡信号FPGA 封装球位SFP1`228_TX2_P/N`、`228_RX2_P/N`TX:N4/N3;RX:M2/M1SFP2`228_TX0_P/N`、`228_RX0_P/N`TX:R4/R3;RX:T2/T1 --- ## 2. 容易混淆的概念 `228_TX0/RX0` 中的 `0` 是板卡原理图或 GT Bank 内的通道编号,不等于 Vivado GT Site 坐标中的 `X1Y0`。 最终应以 Vivado 查询出的实际 GT Site 为准,而不是仅依据 Bank 编号或名称推测。 --- ## 3. 初次查询遇到的问题 直接执行: ``` foreach p {R4 R3 T2 T1 N4 N3 M2 M1} { puts "$p -> [get_property SITE [get_package_pins $p]]" } ``` 报错: ``` ERROR: [Common 17-53] User Exception: No open design. Please open an elaborated, synthesized or implemented design before executing this command. ``` 原因:当前只打开了 IP 配置窗口,没有打开可查询的 design。 随后执行: ``` open_io_design -part xczu9eg-ffvb1156-2-i ``` 报错: ``` ERROR: [Common 17-69] Command failed: The design mode of fileset 'sources_1' must be 'PinPlanning' ``` 原因:`open_io_design` 要求 `sources_1` 的 `DESIGN_MODE` 设置为 `PinPlanning`。 --- ## 4. 正确的 Vivado Tcl 查询方法 在 Tcl Console 中执行: ``` set_property DESIGN_MODE PinPlanning [get_filesets sources_1] open_io_design -name pincheck -part xczu9eg-ffvb1156-2-i foreach p {R4 R3 T2 T1 N4 N3 M2 M1} { set pp [get_package_pins -quiet $p] puts [format "%-3s -> %s" $p [get_sites -of_objects $pp]] } ``` 查询输出如下: ``` R4 -> GTHE4_CHANNEL_X1Y4 R3 -> GTHE4_CHANNEL_X1Y4 T2 -> GTHE4_CHANNEL_X1Y4 T1 -> GTHE4_CHANNEL_X1Y4 N4 -> GTHE4_CHANNEL_X1Y6 N3 -> GTHE4_CHANNEL_X1Y6 M2 -> GTHE4_CHANNEL_X1Y6 M1 -> GTHE4_CHANNEL_X1Y6 ``` 结论: ``` SFP2:228_TX0/RX0 → GTHE4_CHANNEL_X1Y4 SFP1:228_TX2/RX2 → GTHE4_CHANNEL_X1Y6 ``` --- ## 5. Ethernet Subsystem IP 最终配置 两个通道均属于: ``` GT Selection = Quad X1Y1 GT Type = GTH ``` Lane 选择如下: 光口GT SelectionLane-00SFP2`Quad X1Y1X1Y4`SFP1`Quad X1Y1X1Y6` `Quad X1Y1` 中可用 Lane 的关系: ``` Quad X1Y1 ├─ X1Y4 → SFP2,228_TX0 / 228_RX0 ├─ X1Y5 → 未使用 ├─ X1Y6 → SFP1,228_TX2 / 228_RX2 └─ X1Y7 → 未使用 ``` --- ## 6. 建议的 IP 参数 单路 10G Ethernet 配置建议: ``` GT Type = GTH GT Selection = Quad X1Y1 Lane-00 = X1Y4(SFP2)或 X1Y6(SFP1) GT RefClk = 156.25 MHz GT DRP/Free-running Clk = 100 MHz GT Location = Include GT subcore in core ``` 注意: * `GT RefClk = 156.25 MHz` 的前提是板上实际连接到对应 GTH Quad 的专用 GT 参考时钟确实为 156.25 MHz。 * 不能把普通 PL 逻辑时钟直接当作 GT RefClk。 * SFP1 和 SFP2 若要分别作为独立 10G 接口,通常应分别实例化两个 Ethernet Subsystem IP: * SFP2 IP 绑定 `X1Y4` * SFP1 IP 绑定 `X1Y6` --- ## 7. 查询完成后的恢复操作 查询结束后可关闭 I/O design,并恢复工程默认模式: ``` close_design set_property DESIGN_MODE RTL [get_filesets sources_1] ``` --- ## 8. 最终结论 ``` SFP2(R4/R3/T2/T1,228_TX0/RX0) → GTHE4_CHANNEL_X1Y4 → Quad X1Y1,Lane-00 = X1Y4 SFP1(N4/N3/M2/M1,228_TX2/RX2) → GTHE4_CHANNEL_X1Y6 → Quad X1Y1,Lane-00 = X1Y6 ``` ‍
#FPGA#GT
2026-06-16 14:42:48

博客缓存与音乐播放问题总结

一、问题现象 问题 1:浏览器缓存旧页面 现象:更新博客源码并构建后,电脑端第一次打开博客显示旧页面,刷新后才显示新页面(**经过CDN加速的网址**) 手机端:正常,不会出现旧页面 问题 2:音乐播放中断 现象:首页播放音乐后,点击其他子页面音乐中断 特殊情况:所有页面都点过一遍后,再切换页面音乐不会中断 关键发现:直接访问源站音乐不会断,通过 CDN 访问才会断 二、问题分析 缓存策略冲突 Next.js 和服务器返回了两个冲突的缓存头,CDN 优先使用了缓存策略,导致 HTML 页面被缓存。 ESA 协商缓存 即使移除了缓存头,ESA 仍然返回了 ETag,导致浏览器使用本地缓存的页面,Next.js 客户端路由无法正常工作。 三、解决方案 方案 1:修改 Nginx 配置(服务器端) 关键配置: 使用 proxy\_hide\_header Cache-Control 移除上游的缓存头 添加 no-cache, no-store, must-revalidate 禁止缓存 静态资源单独配置长期缓存 方案 2:修改 Next.js 配置(源码端) 关键配置: 为所有页面路由添加禁止缓存的头 为静态资源添加长期缓存的头 方案 3:配置阿里云 ESA(CDN 端) 操作步骤: **进入 ESA 控制台的缓存配置** **添加规则匹配 .html 文件或首页路由** **设置缓存行为为「不缓存」** <https://bu.dusays.com/2026/06/16/6a30f03d037d3.png> ‍ ‍ 四、验证结果 修改前(通过 CDN 访问) Cache-Control: s-maxage=31536000 ← 被 CDN 缓存 Cache-Control: no-cache, no-store, must-revalidate 修改后(通过 CDN 访问) Cache-Control: no-cache, no-store, must-revalidate ← 只有这个 Pragma: no-cache Expires: 0 测试结果 ✅ 电脑端和手机端都能获取最新页面 ✅ 音乐播放不会因页面切换而中断 ✅ 静态资源长期缓存,提升加载速度 五、关键经验 1. 缓存头优先级 Nginx 的 proxy\_hide\_header 优先级高于上游返回的头 ESA 的缓存策略优先级高于源站缓存策略 1. Next.js 客户端路由 首次访问需要从服务器获取页面 后续访问使用客户端路由 HTML 页面不被缓存才能正常工作 1. 音乐播放器架构 使用 React Context 在根布局中管理状态 页面导航时不应卸载根布局组件 全页面刷新会导致所有组件重新挂载 1. CDN 配置要点 HTML 页面:不缓存(no-cache) 静态资源:长期缓存(max-age=2592000) 避免使用 s-maxage,除非明确知道 CDN 行为 六、常见问题 Q1:为什么直接访问源站音乐不会断? A1:直接访问时没有 CDN 缓存,每次都是从源站获取最新页面,Next.js 客户端路由正常工作。 Q2:为什么所有页面都点过一遍后音乐不会断? A2:Next.js 的预取机制会缓存已访问的页面,后续导航使用客户端路由,不会触发全页面刷新。 Q3:修改配置后需要重启吗? A3:Nginx 需要执行 sudo nginx -t && sudo systemctl reload nginx,Next.js 需要重新构建。 Q4:ESA 缓存刷新后多久生效? A4:通常 5-10 分钟,具体取决于边缘节点数量和刷新策略。 七、总结 这个问题涉及多个层面的缓存策略: 浏览器缓存:需要明确的 Cache-Control 头 服务器缓存:Nginx 需要正确配置代理头 CDN 缓存:需要在控制台配置不缓存 HTML 应用缓存:Next.js 的客户端路由依赖正确的缓存策略 解决这类问题需要逐层排查,找到真正的瓶颈所在。