"إذا أراد العامل أن يؤدي عمله بشكل جيد، فعليه أولاً أن يشحذ أدواته." - كونفوشيوس، "مختارات كونفوشيوس. لو لينجونج"
الصفحة الأمامية > برمجة > تسريع موقع الويب الخاص بك باستخدام Fastify وRedis Cache

تسريع موقع الويب الخاص بك باستخدام Fastify وRedis Cache

تم النشر بتاريخ 2024-08-27
تصفح:210

Speeding Up Your Website Using Fastify and Redis Cache

منذ أقل من 24 ساعة، كتبت منشورًا حول كيفية تسريع موقع الويب الخاص بك باستخدام ذاكرة التخزين المؤقت Cloudflare. ومع ذلك، فقد قمت منذ ذلك الحين بنقل معظم المنطق إلى برنامج وسيط Fastify باستخدام Redis. إليك السبب وكيف يمكنك القيام بذلك بنفسك.

مشكلات ذاكرة التخزين المؤقت في Cloudflare

لقد واجهت مشكلتين مع ذاكرة التخزين المؤقت لـ Cloudflare:

  • تعطل التنقل في الصفحة بعد تمكين التخزين المؤقت للاستجابات. لقد أثرت مشكلة حول هذا الأمر في منتدى Remix منذ فترة، ولكن حتى كتابة هذا الموضوع، لا يزال الأمر دون حل. ليس من الواضح سبب تسبب التخزين المؤقت للاستجابة في انقطاع التنقل في الصفحة، ولكن هذا يحدث فقط عندما يتم تخزين الاستجابة مؤقتًا بواسطة Cloudflare.
  • لم أتمكن من جعل Cloudflare يقوم بخدمة المحتوى القديم أثناء إعادة التحقق كما هو موضح في المنشور الأصلي. يبدو أنها ليست ميزة متوفرة.

كانت هناك بعض المشكلات الأخرى التي واجهتها (مثل عدم القدرة على مسح ذاكرة التخزين المؤقت باستخدام مطابقة الأنماط)، ولكن هذه لم تكن حاسمة بالنسبة لحالة الاستخدام الخاصة بي.

لذلك، قررت نقل المنطق إلى برنامج وسيط Fastify باستخدام Redis.

[!ملحوظة]
لقد تركت ذاكرة التخزين المؤقت Cloudflare للتخزين المؤقت للصور. في هذه الحالة، تعمل ذاكرة التخزين المؤقت Cloudflare بشكل فعال كشبكة CDN.

Fastify الوسيطة

ما يلي هو نسخة مشروحة من البرنامج الوسيط الذي كتبته لتخزين الاستجابات مؤقتًا باستخدام 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;
});

تتنقل التعليقات عبر الكود، ولكن إليك بعض النقاط الأساسية:

  • معايير التخزين المؤقت:
    • الطلبات:
    • لا تقم بتخزين الاستجابات للمستخدمين المصادق عليهم.
    • طلبات GET للتخزين المؤقت فقط.
    • استجابات ذاكرة التخزين المؤقت فقط لعناوين URL التي تتضمن "/supplements/".
    • تجاوز ذاكرة التخزين المؤقت إذا كان رأس الطلب يحتوي على التحكم في ذاكرة التخزين المؤقت: no-cache.
    • الردود:
    • يتم تخزين الاستجابات الناجحة فقط في ذاكرة التخزين المؤقت (رمز الحالة هو 200).
    • لا تقم بتخزين الاستجابات التي تم تقديمها بالفعل من ذاكرة التخزين المؤقت (x-pillser-cache: HIT).
    • استجابات ذاكرة التخزين المؤقت فقط مع نوع المحتوى: نص/html.
  • إنشاء مفتاح ذاكرة التخزين المؤقت:
    • استخدم تجزئة SHA-256 لتمثيل JSON الذي يحتوي على طريقة الطلب وعنوان URL وعرض إطار العرض.
    • ابدأ مفتاح ذاكرة التخزين المؤقت بـ "الطلب:" لتسهيل مسافة الاسم والتطهير.
  • التعامل مع الطلب:
    • قم بالدخول إلى دورة حياة onRequest للتحقق مما إذا كان الطلب يحتوي على استجابة مخبأة.
    • قم بتقديم الاستجابة المخزنة مؤقتًا إذا كانت متوفرة، مع وضع علامة عليها باستخدام x-pillser-cache: HIT.
    • ابدأ مهمة خلفية لتحديث ذاكرة التخزين المؤقت بعد إرسال استجابة مخبأة، وتنفيذ "عرض المحتوى القديم أثناء إعادة التحقق".
  • التعامل مع الاستجابة:
    • قم بالربط بدورة حياة onSend لمعالجة الاستجابات وتخزينها مؤقتًا.
    • تحويل التدفقات القابلة للقراءة إلى سلسلة لتسهيل التخزين المؤقت.
    • استبعاد رؤوس محددة (طول المحتوى، مجموعة ملفات تعريف الارتباط، ذاكرة التخزين المؤقت لـ x-pillser) من ذاكرة التخزين المؤقت.
    • وضع علامة على الاستجابات غير القابلة للتخزين المؤقت كذاكرة تخزين مؤقت لـ x: DYNAMIC.
    • استجابات التخزين المؤقت باستخدام TTL (مدة البقاء) ليوم واحد، مع وضع علامة على الإدخالات الجديدة باستخدام ذاكرة التخزين المؤقت x-pillser: MISS.

نتائج

لقد أجريت اختبارات زمن الوصول من عدة مواقع وحصلت على أبطأ وقت استجابة لكل عنوان URL. النتائج أدناه:

عنوان URL دولة وقت استجابة المنشأ زمن الاستجابة المخزن مؤقتًا على Cloudflare تسريع وقت الاستجابة المخزنة مؤقتًا
https://pillser.com/vitamins/vitamin-b1 الولايات المتحدة والغرب1 240 مللي ثانية 16 مللي ثانية 40 مللي ثانية
https://pillser.com/vitamins/vitamin-b1 أوروبا الغربية3 320 مللي ثانية 10 مللي ثانية 110 مللي ثانية
https://pillser.com/vitamins/vitamin-b1 أستراليا-جنوب شرق 1 362 مللي ثانية 16 مللي ثانية 192 مللي ثانية
https://pillser.com/supplements/vitamin-b1-3254 الولايات المتحدة والغرب1 280 مللي ثانية 10 مللي ثانية 38 مللي ثانية
https://pillser.com/supplements/vitamin-b1-3254 أوروبا الغربية3 340 مللي ثانية 12 مللي ثانية 141 مللي ثانية
https://pillser.com/supplements/vitamin-b1-3254 أستراليا-جنوب شرق 1 362 مللي ثانية 14 مللي ثانية 183 مللي ثانية

بالمقارنة مع ذاكرة التخزين المؤقت Cloudflare، فإن ذاكرة التخزين المؤقت Fastify أبطأ. وذلك لأن المحتوى المخزن مؤقتًا لا يزال يتم تقديمه من الأصل، بينما يتم تقديم ذاكرة التخزين المؤقت لـ Cloudflare من مواقع الحافة الإقليمية. ومع ذلك، وجدت أن أوقات الاستجابة هذه كافية لتحقيق تجربة مستخدم جيدة.

بيان الافراج تم إعادة إنتاج هذه المقالة على: https://dev.to/lilouartz/speeding-up-your-website-using-fastify-and-redis-cache-4ck6?1 إذا كان هناك أي انتهاك، يرجى الاتصال بـ [email protected] لحذفه
أحدث البرنامج التعليمي أكثر>

تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.

Copyright© 2022 湘ICP备2022001581号-3