Rumah  >  Artikel  >  hujung hadapan web  >  Mempercepatkan Tapak Web Anda Menggunakan Fastify dan Cache Redis

Mempercepatkan Tapak Web Anda Menggunakan Fastify dan Cache Redis

WBOY
WBOYasal
2024-08-26 21:46:32364semak imbas

Speeding Up Your Website Using Fastify and Redis Cache

Kurang daripada 24 jam yang lalu, saya menulis siaran tentang cara mempercepatkan tapak web anda menggunakan cache Cloudflare. Walau bagaimanapun, saya telah mengalihkan sebahagian besar logik ke perisian tengah Fastify menggunakan Redis. Inilah sebab dan cara anda boleh melakukannya sendiri.

Isu Cache Cloudflare

Saya menghadapi dua isu dengan cache Cloudflare:

  • Navigasi halaman rosak selepas mendayakan cache jawapan. Saya membangkitkan isu tentang perkara ini dalam forum Remix beberapa ketika dahulu, tetapi sehingga menulis ini, ia masih tidak dapat diselesaikan. Tidak jelas mengapa cache jawapan menyebabkan navigasi halaman rosak, tetapi ia hanya berlaku apabila respons dicache oleh Cloudflare.
  • Saya tidak boleh mendapatkan Cloudflare untuk melaksanakan Serve Stale Content Semasa Mengesahkan Semula seperti yang diterangkan dalam siaran asal. Nampaknya ia bukan ciri yang tersedia.

Terdapat beberapa isu lain yang saya hadapi (seperti tidak dapat membersihkan cache menggunakan padanan corak), tetapi itu tidak kritikal untuk kes penggunaan saya.

Oleh itu, saya memutuskan untuk mengalihkan logik ke perisian tengah Fastify menggunakan Redis.

[!NOTA]
Saya meninggalkan cache Cloudflare untuk cache imej. Dalam kes ini, cache Cloudflare berfungsi dengan berkesan sebagai CDN.

Fastify Middleware

Yang berikut ialah versi perisian tengah beranotasi yang saya tulis untuk cache respons menggunakan 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;
});

Komen berjalan melalui kod, tetapi berikut adalah beberapa perkara penting:

  • Kriteria Caching:
    • Permintaan:
    • Jangan cache respons untuk pengguna yang disahkan.
    • Hanya cache GET permintaan.
    • Hanya jawapan cache untuk URL yang mengandungi "/supplements/".
    • Pintas cache jika pengepala permintaan mengandungi kawalan cache: tiada cache.
    • Jawapan:
    • Hanya cache jawapan yang berjaya (Kod status ialah 200).
    • Jangan cache respons yang telah disediakan daripada cache (x-pillser-cache: HIT).
    • Hanya jawapan cache dengan jenis kandungan: teks/html.
  • Penjanaan Kunci Cache:
    • Gunakan cincang SHA-256 bagi perwakilan JSON yang mengandungi kaedah permintaan, URL dan lebar port pandangan.
    • Awalan kekunci cache dengan 'permintaan:' untuk ruang nama dan pembersihan yang mudah.
  • Pengendalian Permintaan:
    • Panggil ke dalam kitaran hayat onRequest untuk menyemak sama ada permintaan mempunyai respons yang dicache.
    • Layankan respons cache jika tersedia, tandakannya dengan x-pillser-cache: HIT.
    • Mulakan tugas latar belakang untuk menyegarkan cache selepas menghantar respons cache, melaksanakan "Layankan Kandungan Lapuk Semasa Mengesahkan Semula".
  • Pengendalian Respons:
    • Sangkut ke dalam kitaran hayat onSend untuk memproses dan cache respons.
    • Tukar strim boleh dibaca kepada rentetan untuk caching yang lebih mudah.
    • Kecualikan pengepala tertentu (panjang kandungan, set-kuki, x-pillser-cache) daripada cache.
    • Tandai respons tidak boleh cache sebagai x-pillser-cache: DINAMIK.
    • Cache respons dengan TTL (Time To Live) satu hari, menandakan entri baharu dengan x-pillser-cache: MISS.

Keputusan

Saya menjalankan ujian kependaman dari beberapa lokasi dan menangkap masa respons yang paling perlahan untuk setiap URL. Keputusan adalah di bawah:

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

Berbanding dengan cache Cloudflare, cache Fastify adalah lebih perlahan. Ini kerana kandungan cache masih disampaikan dari asal, manakala cache Cloudflare dihidangkan dari lokasi pinggir wilayah. Walau bagaimanapun, saya mendapati bahawa masa respons ini adalah banyak untuk mencapai pengalaman pengguna yang baik.

Atas ialah kandungan terperinci Mempercepatkan Tapak Web Anda Menggunakan Fastify dan Cache Redis. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn