„Wenn ein Arbeiter seine Arbeit gut machen will, muss er zuerst seine Werkzeuge schärfen.“ – Konfuzius, „Die Gespräche des Konfuzius. Lu Linggong“
Titelseite > Programmierung > Beschleunigen Sie Ihre Website mit Fastify und Redis Cache

Beschleunigen Sie Ihre Website mit Fastify und Redis Cache

Veröffentlicht am 27.08.2024
Durchsuche:676

Speeding Up Your Website Using Fastify and Redis Cache

Vor weniger als 24 Stunden habe ich einen Beitrag darüber geschrieben, wie Sie Ihre Website mithilfe des Cloudflare-Cache beschleunigen können. Seitdem habe ich jedoch den größten Teil der Logik mithilfe von Redis auf eine Fastify-Middleware verlagert. Hier erfahren Sie, warum und wie Sie es selbst tun können.

Probleme mit dem Cloudflare-Cache

Ich hatte zwei Probleme mit dem Cloudflare-Cache:

  • Die Seitennavigation brach ab, nachdem das Caching der Antworten aktiviert wurde. Ich habe vor einiger Zeit im Remix-Forum ein Problem zu diesem Thema angesprochen, aber zum Zeitpunkt des Verfassens dieses Artikels ist es immer noch ungelöst. Es ist nicht klar, warum das Zwischenspeichern der Antwort dazu führt, dass die Seitennavigation unterbrochen wird. Dies geschieht jedoch nur, wenn die Antwort von Cloudflare zwischengespeichert wird.
  • Ich konnte Cloudflare nicht dazu bringen, bei der erneuten Validierung veraltete Inhalte bereitzustellen, wie im ursprünglichen Beitrag beschrieben. Anscheinend ist diese Funktion nicht verfügbar.

Es gab noch ein paar andere Probleme, auf die ich gestoßen bin (z. B. dass ich den Cache nicht mithilfe des Mustervergleichs leeren konnte), aber diese waren für meinen Anwendungsfall nicht kritisch.

Daher habe ich beschlossen, die Logik mithilfe von Redis auf eine Fastify-Middleware zu verschieben.

[!NOTIZ]
Ich habe den Cloudflare-Cache für das Bild-Caching verlassen. In diesem Fall fungiert der Cloudflare-Cache effektiv als CDN.

Fastify-Middleware

Was folgt, ist eine kommentierte Version der Middleware, die ich geschrieben habe, um Antworten mit Fastify zwischenzuspeichern.

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;
});

Die Kommentare gehen durch den Code, aber hier sind einige wichtige Punkte:

  • Caching-Kriterien:
    • Anfragen:
    • Antworten für authentifizierte Benutzer nicht zwischenspeichern.
    • Nur GET-Anfragen zwischenspeichern.
    • Cache-Antworten nur für URLs, die „/supplements/“ enthalten.
    • Cache umgehen, wenn der Anforderungsheader Cache-Kontrolle enthält: no-cache.
    • Antworten:
    • Nur erfolgreiche Antworten zwischenspeichern (StatusCode ist 200).
    • Antworten, die bereits aus dem Cache bereitgestellt wurden, nicht zwischenspeichern (x-pillser-cache: HIT).
    • Nur Antworten mit dem Inhaltstyp „text/html“ zwischenspeichern.
  • Cache-Schlüsselgenerierung:
    • Verwenden Sie den SHA-256-Hash einer JSON-Darstellung, die die Anforderungsmethode, die URL und die Breite des Ansichtsfensters enthält.
    • Stellen Sie dem Cache-Schlüssel „request:“ voran, um den Namensraum zu vereinfachen und zu löschen.
  • Anfragebearbeitung:
    • Stecken Sie sich in den onRequest-Lebenszyklus ein, um zu prüfen, ob eine Anfrage eine zwischengespeicherte Antwort hat.
    • Stellen Sie die zwischengespeicherte Antwort bereit, falls verfügbar, und markieren Sie sie mit x-pillser-cache: HIT.
    • Starten Sie eine Hintergrundaufgabe, um den Cache nach dem Senden einer zwischengespeicherten Antwort zu aktualisieren, indem Sie „Veraltete Inhalte bei erneuter Validierung bereitstellen“ implementieren.
  • Antwortverarbeitung:
    • Einbinden in den onSend-Lebenszyklus, um Antworten zu verarbeiten und zwischenzuspeichern.
    • Konvertieren Sie lesbare Streams in einen String, um das Caching zu vereinfachen.
    • Bestimmte Header (content-length, set-cookie, x-pillser-cache) aus dem Cache ausschließen.
    • Markieren Sie nicht zwischenspeicherbare Antworten als x-pillser-cache: DYNAMISCH.
    • Cache-Antworten mit einer TTL (Time To Live) von einem Tag, markiert neue Einträge mit x-pillser-cache: MISS.

Ergebnisse

Ich habe Latenztests von mehreren Standorten aus durchgeführt und die langsamste Antwortzeit für jede URL ermittelt. Die Ergebnisse sind unten:

URL Land Origin-Reaktionszeit Cache-Antwortzeit von Cloudflare Cache-Antwortzeit verkürzen
https://pillser.com/vitamins/vitamin-b1 us-west1 240ms 16ms 40ms
https://pillser.com/vitamins/vitamin-b1 europa-west3 320ms 10ms 110ms
https://pillser.com/vitamins/vitamin-b1 Australien-Südosten1 362ms 16ms 192ms
https://pillser.com/supplements/vitamin-b1-3254 us-west1 280ms 10ms 38ms
https://pillser.com/supplements/vitamin-b1-3254 europa-west3 340ms 12ms 141ms
https://pillser.com/supplements/vitamin-b1-3254 Australien-Südosten1 362ms 14ms 183ms

Im Vergleich zum Cloudflare-Cache ist der Fastify-Cache langsamer. Das liegt daran, dass der zwischengespeicherte Inhalt weiterhin vom Ursprung bereitgestellt wird, während der Cloudflare-Cache von regionalen Edge-Standorten bereitgestellt wird. Ich habe jedoch festgestellt, dass diese Reaktionszeiten ausreichen, um eine gute Benutzererfahrung zu erzielen.

Freigabeerklärung Dieser Artikel ist nachgedruckt unter: https://dev.to/lilouarz/speeding-your-website-using-fastify--redis-cache-4ck6?1 Wenn es zu Verletzungen besteht, wenden Sie sich bitte an [email protected], um ihn zu löschen.
Neuestes Tutorial Mehr>

Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.

Copyright© 2022 湘ICP备2022001581号-3