Il y a moins de 24 heures, j'ai écrit un article sur la façon d'accélérer votre site Web à l'aide du cache Cloudflare. Cependant, j'ai depuis déplacé l'essentiel de la logique vers un middleware Fastify utilisant Redis. Voici pourquoi et comment vous pouvez le faire vous-même.
J'ai rencontré deux problèmes avec le cache Cloudflare :
J'ai rencontré quelques autres problèmes (comme ne pas pouvoir purger le cache à l'aide de la correspondance de modèles), mais ceux-ci n'étaient pas critiques pour mon cas d'utilisation.
Par conséquent, j'ai décidé de déplacer la logique vers un middleware Fastify utilisant Redis.
[!NOTER]
J'ai quitté le cache Cloudflare pour la mise en cache des images. Dans ce cas, le cache Cloudflare fonctionne effectivement comme un CDN.
Ce qui suit est une version annotée du middleware que j'ai écrit pour mettre en cache les réponses à l'aide de 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; });
Les commentaires parcourent le code, mais voici quelques points clés :
J'ai effectué des tests de latence à partir de plusieurs emplacements et capturé le temps de réponse le plus lent pour chaque URL. Les résultats sont ci-dessous :
URL | Pays | Temps de réponse d'origine | Temps de réponse mis en cache par Cloudflare | Fastifier le temps de réponse mis en cache |
---|---|---|---|---|
https://pillser.com/vitamins/vitamin-b1 | us-west1 | 240 ms | 16 ms | 40 ms |
https://pillser.com/vitamins/vitamin-b1 | europe-ouest3 | 320 ms | 10 ms | 110 ms |
https://pillser.com/vitamins/vitamin-b1 | Australie-sud-est1 | 362 ms | 16 ms | 192 ms |
https://pillser.com/supplements/vitamin-b1-3254 | us-west1 | 280 ms | 10 ms | 38 ms |
https://pillser.com/supplements/vitamin-b1-3254 | europe-ouest3 | 340 ms | 12 ms | 141 ms |
https://pillser.com/supplements/vitamin-b1-3254 | Australie-sud-est1 | 362 ms | 14 ms | 183 ms |
Par rapport au cache Cloudflare, le cache Fastify est plus lent. En effet, le contenu mis en cache est toujours servi depuis l'origine, tandis que le cache Cloudflare est servi depuis des emplacements périphériques régionaux. Cependant, j'ai trouvé que ces temps de réponse sont suffisants pour obtenir une bonne expérience utilisateur.
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3