dns-prefetch DNS 预解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: dns-prefetch DNS 预解析

Web 性能优化|了解 HTTP 协议后才能理解的预加载 https://mp.weixin.qq.com/s/2C7w4iL4DLa1QXqq-37SAw

CDN 动态加速

使用 CDN 动态加速时,CDN 通过在全球分布的边缘节点缓存和处理用户请求,显著缩短了从用户到服务器的物理距离,减少了传输延迟。同时 CDN 服务商会实时监控全球的网络状态,通过智能路由技术选择当前最优的路径传输数据,这避免了网络中的拥塞和瓶颈,确保数据以最快的速度传输到用户端。

如果使用了 CDN 提供的边缘计算能力,可以让用户直接从 CDN 边缘节点获取动态内容,进一步加速动态内容的访问。

dns-prefetch DNS 预解析

当浏览器需要访问特定域名时,必须先将先将域名解析为 IP 地址,这一步骤就是 DNS 解析。dns -prefetch 可以让浏览器提前在后台完成这一解析工作,避免用户在实际请求资源时等待 DNS 解析的时间。

在 HTML 顶部通过标签来指示浏览器对接下来要是用的静态资源、动态接口等域名提前进行 DNS 解析。

preconenct 域名预建连

当浏览器解析了域名后,接下来需要通过TCP协议和服务器建立连接,并在使用 HTTPS 的情况下进行 TLS 握手,这些步骤通常需要较多往返时间(RTT)。preconnect 通过提前完成这些连接步骤,可以减少用户真正需要请求资源时的等待时间。

可以在HTML中通过标签来指示浏览器进行预连接,使用 preconnect 之后浏览器不仅会解析域名的 DNS,还会提前与服务器建立 TCP 连接,并完成 TLS 握手。

preload 与 prefetch 预加载

除了对域名进行解析、建连,还可以通过 preload 和 prefetch 对页面将要使用的资源提前下载。

preload 是一种声明式资源引入方式,用来强制浏览器在合适的时机加载指定资源,通常用于关键资源(如字体、脚本、样式表等)的预加载,以确保这些资源能够尽快被使用。


prefetch 同样是一种声明式资源请求方式,用于提示浏览器在空闲时下载未来可能用到的资源,适合作为页面未来使用的资源或者当前页面下一跳页面要使用的资源预加载。


两个标签在优先级上有一定的区别:

preload:具有高优先级,浏览器会立即加载这些资源;
prefetch:具有较低优先级,只有在浏览器空闲时才会加载这些资源,确保不妨碍当前页面的正常加载;

两者在浏览器支持上各有千秋:

preload

prefetch

图片

图片

prerender 预渲染

使用prerender 可以将目标页面上近乎所有资源(HTML、CSS、JavaScript、图像等)和内容在后台提前下载并渲染,浏览器在用户首次访问该页面之前已经完全准备好了该页面的视图。这样当用户跳转到该页面时,使用户在实际跳转到这个页面时能够立即呈现,不需要再等待加载和渲染的时间。


听起来 prerender 是预加载的终极方案了,但在实际性能优化方案中却很少被使用,使用 preload 有几个弊端:

不能命中时候资源开销过大:因为 prerender 会对页面进行资源下载和渲染,当页面没有被用户访问时候造成的资源浪费过大;
影响页面数据统计:大部分页面在执行时候会对页面进行数据上报用作后续的页面效果分析,部分页面会有展示广告等行为,如果 prerender 后用户没有访问页面,会造成数据统计上的混乱;
浏览器兼容性问题:不同的浏览器对于 prerender 的实现细节可能有所不同。例如,一些浏览器可能出于性能或安全考虑,会对预渲染的资源类型进行某些限制;

图片

根据用户行为 prefetch 下一跳页面

无脑对页面进行 prefetch 会造成巨大的资源浪费,但很多时候我们可以根据用户行为更精准的预测用户接下来的动作,再进行 prefetch 可以很大程度上减少资源浪费。

举个例子,在 PC 页面当用户鼠标悬停在某个商品图片上时候,我们可以大胆预测用户及大概率要点击页面,这时候可以对页面进行 prefetch。如果希望进一步细化,用户点击鼠标的动作会依次触发 mousedown、mouseup、click事件,我们可以在 mousedown 事件中对页面进行预载,这样可以节省人点击鼠标的 200ms 左右。

function App() {
return (


Product List








);
}

const Product = ({ id, name, imageUrl, prefetchUrl, delay=200 }) => {
const [prefetchTimeout, setPrefetchTimeout] = useState(null);

const handleMouseOver = () => {
const timeout = setTimeout(() => {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = prefetchUrl;
link.credentials = 'include';
document.head.appendChild(link);
}, delay);

// 防止用户快速
setPrefetchTimeout(timeout);

};

const handleMouseOut = () => {
// 如果过度发 prefetch 请求
clearTimeout(prefetchTimeout);
};

return (


{name}

{name}



);
}

添加 credentials 属性,携带 cookie

安全原因 prefetch 请求默认不携带 cookie,为了让 prefetch 请求携带 cookie, 可以在 prefetch 的 link 标签中添加 credentials 属性,并将其设置为 "include"。

服务器设置缓存

因为大部分动态页面为了给用户传输动态内容是禁用客户端缓存的,所以即使发了 prefetch 请求也无法做到用户真实点击的时候复用 prefetch 请求,反而会重新发请求造成资源浪费。

因此需要在服务端识别 prefetch 请求,设置短时间的客户端缓存,当用户很快真实访问 prefetch 的页面后可以复用缓存。

浏览器发送的 prefetch 请求会携带 HTTP Header Sec-Purpose: prefetch或Purpose: prefetch,服务端根据这个属性识别 prefetch 请求。

app.get('/next-page', (req, res) => {
const purposeHeader = req.headers['purpose'] || req.headers['sec-purpose'];
if (purposeHeader === 'prefetch') {
res.set('Cache-Control', 'max-age=10'); // 设置缓存策略
console.log('Prefetch request detected, setting cache.');
} else {
console.log('Regular request detected, no cache.');
}
res.send(<h1>Next Page Content</h1> <p>This is the next page that was prefetched.</p>
);
});

页面与首屏请求并行加载

上述方案在 SSR 页面效果显著,但在 CSR 页面可能优化效果有限,主要原因是 CSR 页面内容存储在 CDN 甚至客户端本地缓存,本身加载很快,页面的渲染主要依赖动态接口的返回。

图片

如果我们可以知道页面首屏渲染需要发起的请求,其实可以利用和上面类似的原理,在用户点击页面的瞬间同时发起异步请求,当解析执行 JavaScript 脚本发送异步请求时可以判断本地已经有缓存,直接使用结果。

图片

原理非常类似,不再代码演示,核心还是请求:

设置credentials请求可以携带 cookie;
服务端识别 prefetch 请求,对接口设置短时间的缓存;

这样的方案最大程度利用了浏览器的特性实现起来比较简单,对 Service Worker 熟悉的话可以利用 Service 做更复杂的控制。

Speculation Rules API

Speculation Rules API 是一个新的 Web API,提供一种声明式的方法来指示浏览器应该对哪些链接进行预取操作,通过这个 API,开发者可以更精确地指示浏览器在何时和如何预取资源,从而显著提升网页性能和用户体验。

<!DOCTYPE html>









Go to Page 1
Go to Page 2
Go to Page 3
<script>
  function addPrefetchRule(url) {
    const speculationRulesScript = document.querySelector('script[type="speculationrules"]');
    const rules = JSON.parse(speculationRulesScript.textContent);

    // 检查 rule 是不是已经设置
    if (!rules.prefetch.some(rule => rule.urls.includes(url))) {
      rules.prefetch.push({
        "source": "list",
        "urls": [url]
      });

      // 更新 speculation rules
      speculationRulesScript.textContent = JSON.stringify(rules);
      console.log(`Prefetch rule added for: ${url}`);
    }
  }

  // 鼠标 hover 时候添加
  document.querySelectorAll('a[data-prefetch-url]').forEach(link => {
    link.addEventListener('mouseover', () => {
      const url = link.getAttribute('data-prefetch-url');
      addPrefetchRule(url);
    });
  });
</script>



Speculation Rules API 目前还处于早期阶段,未来可能会看到更多的浏览器开始支持这一 API,并且 API 本身也可能会引入更多的功能和配置选项。

页面部分内容提前返回

流式渲染简介

流式渲染(Streaming Rendering)是指在服务器上生成页面内容时,逐步将已准备好的部分内容立刻发送到客户端,而不是等待页面所有内容全部生成才开始发送,使客户端可以更快的接收数据渲染页面,而不必等待整个页面的内容完全下载,从而实现快速的页面加载和用户可视化体验。这个过程像是水管中的水一样流动起来源源不断,因此被称为流式渲染。

流式渲染实际上一个非常古老的技术,早在 HTTP 1.1 规范中就已经引入了 Transfer-Encoding: chunked 头字段,允许服务器将响应内容分批返回给客户端。服务器可以在生成响应内容的同时,将其分成小块,逐步传输给客户端,而不是等待所有内容生成完成后再返回。

在浏览器端,早期的浏览器(如 Netscape Navigator 和 IE)就已经支持对部分 HTML 内容进行解析和执行。当浏览器接收到服务器返回的部分 HTML 内容时,它可以立即开始解析和执行该内容,而不需要等待所有内容加载完成。

图片

const http = require('http');

http
.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/html',
'Transfer-Encoding': 'chunked',
});
function renderChunk(chunk) {
res.write(<div>${chunk}</div>);
}

renderChunk('Loading...');

setTimeout(() => {
  renderChunk('Chunk 1');
}, 1000);

setTimeout(() => {
  renderChunk('Chunk 2');
}, 2000);

setTimeout(() => {
  renderChunk('Chunk 3');
}, 3000);

setTimeout(() => {
  renderChunk('done!');
  res.write('</body></html>');
  res.end();
}, 4000);

})
.listen(3000, () => {
console.log('Server listening on port 3000');
});

开启流式渲染后的新思路

页面支持流式渲染之后我们可以利用等待服务器计算生成动态内容的空档,提前返回页面部分内容,在浏览器完成关键域名的预建连、核心资源的预加载,严格来讲下面讲的很多内容其实是 HTTP 协议实现的,但思路上和流式渲染原理一致,所以放在一块来讨论。

提前返回 preconnenct、preload 标签

页面可以对静态部分做缓存,接收到用户请求后流式渲染直接返回(其实这种最适合利用 CDN 边缘渲染)。

页面静态部分 public/static.html

<!DOCTYPE html>








如果服务器 RT 过长,甚至可以反直觉的在页面顶部预载 JavaScript 文件,但不执行。

server.js

const http = require('http');
const path = require('path');
const fs = require('fs');

const filePath = path.join(__dirname, 'public', 'static.html');

http
.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/html',
'Transfer-Encoding': 'chunked',
});

function renderChunk(chunk) {
  res.write(`<div>${chunk}</div>`);
}

fs.readFile(filePath, 'utf8', (err, firstFragment) => {
  // 返回静态部分,浏览器提前建连、加载
  renderChunk(firstFragment);
});

renderChunk('Loading...');

setTimeout(() => {
  // 复杂的服务端计算
  renderChunk('done!');
  res.write('</body></html>');
  res.end();
}, 4000);

})
.listen(3000, () => {
console.log('Server listening on port 3000');
});

[kod.hy2sc.cn)
[kod.abs-168.com)
[kod.51iyx.com)
[kod.ustore168.com)
[kod.szhair.net)
[kod.adk88.net)
[kod.hnhfhfs.com)
[kod.fxlantian.com)
[kod.cqmini.com)

根据数据生成 preload html 片段

其实我们还可以把服务器 RT 部分细分,🙋‍♀️🌰 页面取数部分实际非常复杂,而恰好首屏呈现的部分内容取数很快,后续取数或 SSR 很慢。

服务器首屏取数

服务器取数 2

服务器取数 3

调用页面 SSR

返回服务武器渲染部分

这种时候可以在调用 ssr 之前,解析首屏数据生成,如果包含图片,可以生成 preload 标签,提前返回到浏览器,甚至对首屏部分调用占位的 SSR。

const http = require('http');

http
.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/html',
'Transfer-Encoding': 'chunked',
});

function renderChunk(chunk) {
  res.write(`<div>${chunk}</div>`);
}

setTimeout(async () => {
const firstData = await getFirstScreenData();
renderChunk(<link rel="preload" href="${firstData.imgSrc}" as="image">);
}, 1000);

setTimeout(() => {
  // 复杂的服务端计算
  renderChunk('done!');
  res.write('</body></html>');
  res.end();
}, 4000);

})
.listen(3000, () => {
console.log('Server listening on port 3000');
});
用了此类技术的页面效果:

图片

http header 返回后续域名 preconenct

除了在 HTML 中通过 link 标签支持 preconnect,足够了解 HTTP 协议后我们还可以更快一些,在 HTTP header 中设置 preconenct,这就是 HTTP Link。

HTTP/2 200 OK
Content-Type: text/html
Link: https://example.com; rel=preconnect, https://fonts.googleapis.com; rel=preconnect
server.js

app.get('/', (req, res) => {
// Set Link headers for preconnect
res.set('Link', [
'https://example.com; rel=preconnect',
'https://fonts.googleapis.com; rel=preconnect'
].join(', '));

// Flush headers to send them immediately
res.flushHeaders();

// Stream the HTML file
const readStream = fs.createReadStream('content');

readStream.pipe(res);
});

HTTP/2 push

HTTP/2 Push 是 HTTP/2 协议中的一种功能,允许服务器在响应客户端请求时,主动将多个资源推送给客户端,而无需客户端明确请求这些资源。

图片

在 NGINX 中,http2_push 指令用于启用或禁用 HTTP/2 push。

server {
listen 443 ssl http2;
server_name localhost;

ssl_certificate /path/to/your/certificate.crt;
ssl_certificate_key /path/to/your/private.key;

location / {
    root /path/to/your/web/content;
    index index.html;

    # 启用 HTTP/2 推送
    http2_push_preload on;

    http2_push banner.jpg;
}

}
看起来怎么这么熟悉,没错,HTTP/2 push 在很多时候就是利用 HTTP Link 特性实现的。

server {
listen 443 ssl http2;
server_name localhost;

ssl_certificate /path/to/your/certificate.crt;
ssl_certificate_key /path/to/your/private.key;

location / {
    root /path/to/your/web/content;

    # 启用 HTTP/2 推送
    http2_push_preload on;

    # 发送 HTML 文档的同时,告诉客户端推送资源
    add_header Link "<banner.jpg.css>; as=image; rel=preload";
}

}
不过通过 Nginx 配置来完成这个工作过于不灵活,大部分时候是通过上面讲的在服务代码中实现。

Early Hints

https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103

在 HTTP 1xx 的状态码用来告示客户端继续进行请求或等待更详细的响应,比如在 WebSocket 交换协议期间返回的 101。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
还有一个专门用于服务器希望发送最终响应头部之前,提供一些消息头,客户端可以开始预加载资源的 103 —— Early Hints,其作用和前面提到的 http link 非常类似。

HTTP/1.1 103 Early Hints
Link: ; rel=preload; as=style
server.js

app.get('/', (req, res) => {
// Send 103 Early Hints response to client
res.status(103).set({
Link: [
'; rel=preload; as=style',
'; rel=preload; as=image',
].join(', ')
}).end();

// Set up final response
const readStream = fs.createReadStream('content');
res.set('Content-Type', 'text/html');

// Stream the final response content
readStream.pipe(res);
});
Early hints preconnect 已经在主流浏览器都得到普遍支持,preload Safari 还没有支持Web 。

图片

弹性公网IP满足资源灵活管理

相关文章
|
24天前
|
域名解析 存储 网络协议
深入解析网络通信关键要素:IP 协议、DNS 及相关技术
本文详细介绍了IP协议报头结构及其各字段的功能,包括版本、首部长度、服务类型、总长度、标识、片偏移、标志、生存时间(TTL)、协议、首部检验和等内容。此外,还探讨了IP地址的网段划分、特殊IP地址的应用场景,以及路由选择的大致流程。最后,文章简要介绍了DNS协议的作用及其发展历史,解释了域名解析系统的工作原理。
82 5
深入解析网络通信关键要素:IP 协议、DNS 及相关技术
|
2月前
|
网络协议 Linux Docker
在Linux中,如何指定dns服务器,来解析某个域名?
在Linux中,如何指定dns服务器,来解析某个域名?
|
5天前
|
域名解析 存储 缓存
域名解析 DNS:连接数字世界的关键枢纽
在数字世界中,DNS(域名解析系统)如同一位至关重要的引路人,将我们输入的域名与对应的IP地址相连,使我们可以轻松访问各种网站和服务。它通过多级服务器查询,将易于记忆的域名转换为复杂的IP地址,极大提升了互联网的易用性和普及度。尽管面临网络延迟和域名数量激增等挑战,通过分布式系统和缓存技术等创新方案,DNS 系统将持续发展,为用户提供更安全、高效的网络体验。
21 2
|
6天前
|
弹性计算 负载均衡 网络协议
内部名称解析设置阿里云私有 DNS 区域,针对于阿里云国际版经验教程
内部名称解析设置阿里云私有 DNS 区域,针对于阿里云国际版经验教程
|
7天前
|
域名解析 缓存 网络协议
【网络】DNS,域名解析系统
【网络】DNS,域名解析系统
40 1
|
1月前
|
网络协议
DNS正向解析实现
文章介绍了DNS正向解析的实现,包括资源记录的定义、配置区域解析记录的步骤,并通过实际操作展示了如何为"yinzhengjie.com"域名配置DNS解析记录。
37 2
DNS正向解析实现
|
2月前
|
域名解析 存储 缓存
在Linux中,DNS进行域名解析的过程是什么?
在Linux中,DNS进行域名解析的过程是什么?
|
2月前
|
域名解析 网络协议 Linux
在Linux中,如何配置DNS服务器和解析服务?
在Linux中,如何配置DNS服务器和解析服务?
|
2月前
|
域名解析 网络协议 数据中心
【应用服务 App Service】当遇见某些域名在Azure App Service中无法解析的错误,可以通过设置指定DNS解析服务器来解决
【应用服务 App Service】当遇见某些域名在Azure App Service中无法解析的错误,可以通过设置指定DNS解析服务器来解决
|
22小时前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)

推荐镜像

更多