使用 Cloudflare Workers 加速 Google Analytics
我一直以来都在使用 Google Analytics 统计自己的博客和几个网站访问情况。但是一个 gzip 以后都还有 45KB
大小的 analytics.js
、Cache-Control
还只有 7200 秒;Google 国内的数据中心会被抽风不说,www.google-analytics.com
域名早就上了各个广告屏蔽软件的黑名单。
异步 Google Analytics
避免用户直接给 Google Analytics 发起请求的思路已经有很多人提出并进行了实践。常见的思路有在 Web Server(一般是 Nginx)上实现统计请求并转发给 Google Analytics;一种是自己搭建一个后端程序,比如 Go 编写的 ga-proxy。
Cloudflare 最近推出了他们的 Serverless 平台 Cloudflare Workers,每天有 10 万次请求的免费额度;考虑到我的 PV 一时半会是达不到每天 10 万的,我决定把就用 Cloudflare Workers 实现一个 Google Analytics 异步转发。
首先丢 GitHub,食用指南写在 README 里了,欢迎大家丢 star~
本文完(并没有)
前端数据收集和发送
我并不是十分在意详细访问情况,但是基本的数据还是要收集的:
除此以外,我还通过 Performance Timing API 收集页面的加载性能:
plt
: 页面加载时间dns
: DNS 解析用时pdt
: 页面下载用时rrt
: 重定向用时tcp
: TCP 连接用时srt
: 服务器响应用时dit
: DOM Interactive 用时clt
: Content Load 用时
以上数据分为两类,一类是当前页面的 Page View 信息,一类是 Timing 信息。大部分数据都可以通过 JS 获取到,当前页面的 URL 和 User-Agent 还可以通过 Request Headers 获取到,Cloudflare Worker 还可以通过 Cloudflare 的 cf-connecting-ip
的回源请求头获取到用户的真实 IP 。只要将这些数据通过 GET 的方式发送即可。
Google 的 analytics.js
和 ga-proxy
都是让用户发送两个请求、一个请求绑定在 DOMContenLoaded 的 Event 上发送 Page View 信息,另一个请求绑定在 window.onload 事件上发送 Timing 信息。这样即使用户在页面加载完成之前就关闭了页面,只要 DOMContenLoaded 事件触发了,就可以提前发送 Page View 信息。考虑到 Cloudflare Workers 的免费额度是有限制的、我也不在乎数据的准确性,所以我只在 window.onload 事件上绑定了一个请求、将 Page View 和 Timing 信息通过这一个请求全部发送出去。
服务端逻辑
统计需要为每个用户生成唯一标识 UUID。虽然 Cloudflare 会为每个用户生成一个 _cfuid
的 cookie 可以直接使用,但是考虑到 Google Analytics 推荐使用 Version 4 的 UUID,所以我还是需要在远端实现一个 UUID 的逻辑。首先是检查 cookie 中有没有 uuid
,如果没有就需要生成一个 UUID 并通过 set-cookie
下发给浏览器。
使用 JavaScript 生成 UUID 有很多种方法,在 JSPerf 上做了测试后,性能最好的是这个:
const createUuid = () => {
let s = [];
const hexDigits = '0123456789abcdef';
for (let i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = '4'; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = '-';
return s.join('');
};
浏览器采集的数据是通过 GET 方法发送给服务端的,服务端只需要在 URL 中把参数提取出来再发送给 Google Analytics 就好了。为了不影响性能、降低客户端统计请求的 TTFB,回传数据给 Google Analytics 是异步的,需要先为浏览器生成一个 Response。Google Analytics 会给浏览器返回一个 1px 的 GIF 图片保证兼容性和性能的平衡;不过对于更现代的浏览器,content-length
为 0 的 204 状态码显然性能开销更小。
在 Cloudflare Workers 上生成一个 204 请求的方法很简单,只需要设置状态码为 204,并将 Response Body 设置为 null 即可:
response = new Response(null, { status: 204, statusText: 'No Content' });
Cloudflare 也兼容 Response Body 为 Empty String 的 204 请求,但是 Cloudflare 推荐使用 null,Empty String 的方法不保证向后兼容。
Cloudflare Workers 支持向其他 URL 发送请求,写法和 Fetch
API 类似,在 Cloudflare Workers 中被称为 Subrequest。Cloudflare Workers 在给浏览器返回 Response 后会停止运行、释放计算资源;对于需要异步的日志采集(比如回传数据给 Google Analytics),Cloudflare Workers 提供了一种 Service Worker 中的 ExtendableEvent.waitUntil()
写法,通过 waitUntil
调用的函数可以在 Response 发送给浏览器后继续持续运行。
async function senData(data, reqParameter) {
await fetch(data, reqParameter);
}
...
event.waitUntil(senData(data, parameter));
过滤非真实访客数据
在 Nginx 等 Web Server 中实现统计请求的问题在于,爬虫发送的 HTTP 请求都会被统计,因此会包含许多非真实访客的数据。在前端使用 JS 采集和发送数据可以避免简单的 GET 请求的数据。
Cloudflare Workers 工作在 Cloudflare Cache Layer 前面,因此即使设置了 cache-control
和 Edge Cache TTL,Cloudflare Workers 依然是不可缓存的;但是 Firewall Rules、Cloudflare WAF、Cloudflare Rate Limit 都工作在 Cloudflare Workers 前面,因此你还可以通过设置 Cloudflare Firewall 来拦截不必要的请求。