Maison > interface Web > js tutoriel > le corps du texte

Accélérer votre site Web à l'aide de Fastify et Redis Cache

WBOY
Libérer: 2024-08-26 21:46:32
original
372 Les gens l'ont consulté

Speeding Up Your Website Using Fastify and Redis Cache

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.

Problèmes de cache Cloudflare

J'ai rencontré deux problèmes avec le cache Cloudflare :

  • La navigation dans les pages s'est interrompue après avoir activé la mise en cache des réponses. J'ai soulevé un problème à ce sujet sur le forum Remix il y a quelque temps, mais au moment d'écrire ces lignes, il n'est toujours pas résolu. On ne sait pas pourquoi la mise en cache de la réponse entraîne une interruption de la navigation dans les pages, mais cela ne se produit que lorsque la réponse est mise en cache par Cloudflare.
  • Je n'ai pas réussi à faire en sorte que Cloudflare exécute le service de contenu obsolète lors de la revalidation comme décrit dans le message d'origine. On dirait que ce n'est pas une fonctionnalité disponible.

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.

[!NOTE]
J'ai quitté le cache Cloudflare pour la mise en cache des images. Dans ce cas, le cache Cloudflare fonctionne effectivement comme un CDN.

Fastifier le middleware

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<string, string>;
  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<string> => {
  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<string, string>;

  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;
});
Copier après la connexion

Les commentaires parcourent le code, mais voici quelques points clés :

  • Critères de mise en cache :
    • Demandes :
    • Ne mettez pas en cache les réponses des utilisateurs authentifiés.
    • Cache uniquement les requêtes GET.
    • Cache uniquement les réponses pour les URL qui incluent "/supplements/".
    • Contourner le cache si l'en-tête de la requête contient cache-control : no-cache.
    • Réponses :
    • Cache uniquement les réponses réussies (statusCode est 200).
    • Ne mettez pas en cache les réponses déjà servies depuis le cache (x-pillser-cache : HIT).
    • Cache uniquement les réponses avec le type de contenu : text/html.
  • Génération de clé de cache :
    • Utilisez le hachage SHA-256 d'une représentation JSON contenant la méthode de requête, l'URL et la largeur de la fenêtre.
    • Préfixez la clé de cache avec « request : » pour faciliter l'espacement des noms et la purge.
  • Traitement des demandes :
    • Accédez au cycle de vie onRequest pour vérifier si une requête a une réponse en cache.
    • Servez la réponse mise en cache si elle est disponible, en la marquant avec x-pillser-cache : HIT.
    • Démarrez une tâche en arrière-plan pour actualiser le cache après l'envoi d'une réponse mise en cache, en implémentant « Servir le contenu obsolète lors de la revalidation ».
  • Gestion des réponses :
    • Accédez au cycle de vie onSend pour traiter et mettre en cache les réponses.
    • Convertissez les flux lisibles en chaîne pour une mise en cache plus simple.
    • Exclure les en-têtes spécifiques (content-length, set-cookie, x-pillser-cache) du cache.
    • Marquer les réponses non mises en cache comme x-pillser-cache : DYNAMIC.
    • Cache les réponses avec un TTL (Time To Live) d'un jour, marquant les nouvelles entrées avec x-pillser-cache : MISS.

Résultats

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 Country Origin Response Time Cloudflare Cached Response Time Fastify Cached Response Time
https://pillser.com/vitamins/vitamin-b1 us-west1 240ms 16ms 40ms
https://pillser.com/vitamins/vitamin-b1 europe-west3 320ms 10ms 110ms
https://pillser.com/vitamins/vitamin-b1 australia-southeast1 362ms 16ms 192ms
https://pillser.com/supplements/vitamin-b1-3254 us-west1 280ms 10ms 38ms
https://pillser.com/supplements/vitamin-b1-3254 europe-west3 340ms 12ms 141ms
https://pillser.com/supplements/vitamin-b1-3254 australia-southeast1 362ms 14ms 183ms

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.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal