Hace menos de 24 horas, escribí una publicación sobre cómo acelerar su sitio web usando el caché de Cloudflare. Sin embargo, desde entonces moví la mayor parte de la lógica a un middleware Fastify usando Redis. Aquí te explicamos por qué y cómo puedes hacerlo tú mismo.
Me encontré con dos problemas con el caché de Cloudflare:
Hubo algunos otros problemas con los que me encontré (como no poder purgar el caché usando la coincidencia de patrones), pero no fueron críticos para mi caso de uso.
Por lo tanto, decidí mover la lógica a un middleware Fastify usando Redis.
[!NOTA]
Dejé el caché de Cloudflare para el almacenamiento en caché de imágenes. En este caso, la caché de Cloudflare funciona efectivamente como una CDN.
Lo que sigue es una versión anotada del middleware que escribí para almacenar en caché las respuestas usando Fastify.
const isCacheableRequest = (request: FastifyRequest): boolean => { // Do not attempt to use cache for authenticated visitors. if (request.visitor?.userAccount) { return false; } if (request.method !== 'GET') { return false; } // We only want to cache responses under /supplements/. if (!request.url.includes('/supplements/')) { return false; } // We provide a mechanism to bypass the cache. // This is necessary for implementing the "Serve Stale Content While Revalidating" feature. if (request.headers['cache-control'] === 'no-cache') { return false; } return true; }; const isCacheableResponse = (reply: FastifyReply): boolean => { if (reply.statusCode !== 200) { return false; } // We don't want to cache responses that are served from the cache. if (reply.getHeader('x-pillser-cache') === 'HIT') { return false; } // We only want to cache responses that are HTML. if (!reply.getHeader('content-type')?.toString().includes('text/html')) { return false; } return true; }; const generateRequestCacheKey = (request: FastifyRequest): string => { // We need to namespace the cache key to allow an easy purging of all the cache entries. return 'request:' generateHash({ algorithm: 'sha256', buffer: stringifyJson({ method: request.method, url: request.url, // This is used to cache viewport specific responses. viewportWidth: request.viewportWidth, }), encoding: 'hex', }); }; type CachedResponse = { body: string; headers: Record; statusCode: number; }; const refreshRequestCache = async (request: FastifyRequest) => { await got({ headers: { 'cache-control': 'no-cache', 'sec-ch-viewport-width': String(request.viewportWidth), 'user-agent': request.headers['user-agent'], }, method: 'GET', url: pathToAbsoluteUrl(request.originalUrl), }); }; app.addHook('onRequest', async (request, reply) => { if (!isCacheableRequest(request)) { return; } const cachedResponse = await redis.get(generateRequestCacheKey(request)); if (!cachedResponse) { return; } reply.header('x-pillser-cache', 'HIT'); const response: CachedResponse = parseJson(cachedResponse); reply.status(response.statusCode); reply.headers(response.headers); reply.send(response.body); reply.hijack(); setImmediate(() => { // After the response is sent, we send a request to refresh the cache in the background. // This effectively serves stale content while revalidating. // Therefore, this cache does not reduce the number of requests to the origin; // The goal is to reduce the response time for the user. refreshRequestCache(request); }); }); const readableToString = (readable: Readable): Promise => { const chunks: Uint8Array[] = []; return new Promise((resolve, reject) => { readable.on('data', (chunk) => chunks.push(Buffer.from(chunk))); readable.on('error', (err) => reject(err)); readable.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); }); }; app.addHook('onSend', async (request, reply, payload) => { if (reply.hasHeader('x-pillser-cache')) { return payload; } if (!isCacheableRequest(request) || !isCacheableResponse(reply) || !(payload instanceof Readable)) { // Indicate that the response is not cacheable. reply.header('x-pillser-cache', 'DYNAMIC'); return payload; } const content = await readableToString(payload); const headers = omit(reply.getHeaders(), [ 'content-length', 'set-cookie', 'x-pillser-cache', ]) as Record ; reply.header('x-pillser-cache', 'MISS'); await redis.setex( generateRequestCacheKey(request), getDuration('1 day', 'seconds'), stringifyJson({ body: content, headers, statusCode: reply.statusCode, } satisfies CachedResponse), ); return content; });
Los comentarios recorren el código, pero aquí hay algunos puntos clave:
Realicé pruebas de latencia desde varias ubicaciones y capturé el tiempo de respuesta más lento para cada URL. Los resultados están a continuación:
URL | País | Tiempo de respuesta de origen | Tiempo de respuesta en caché de Cloudflare | Acelerar el tiempo de respuesta en caché |
---|---|---|---|---|
https://pillser.com/vitamins/vitamin-b1 | nosotros-oeste1 | 240 ms | 16 ms | 40 ms |
https://pillser.com/vitamins/vitamin-b1 | europa-oeste3 | 320 ms | 10 ms | 110 ms |
https://pillser.com/vitamins/vitamin-b1 | australia-sureste1 | 362 ms | 16 ms | 192ms |
https://pillser.com/supplements/vitamin-b1-3254 | nosotros-oeste1 | 280 ms | 10 ms | 38 ms |
https://pillser.com/supplements/vitamin-b1-3254 | europa-oeste3 | 340 ms | 12 ms | 141ms |
https://pillser.com/supplements/vitamin-b1-3254 | australia-sureste1 | 362 ms | 14 ms | 183ms |
En comparación con el caché de Cloudflare, el caché de Fastify es más lento. Esto se debe a que el contenido almacenado en caché todavía se sirve desde el origen, mientras que el caché de Cloudflare se sirve desde ubicaciones perimetrales regionales. Sin embargo, descubrí que estos tiempos de respuesta son suficientes para lograr una buena experiencia de usuario.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3