التخزين المؤقت لـ HTTP
التخزين المؤقت لـ HTTP
التخزين المؤقت لـ HTTP هو واحد من أقوى تقنيات تحسين الأداء وأقلها استخداماً. من خلال الاستفادة من ذاكرة التخزين المؤقت للمتصفح وCDN عبر رؤوس HTTP الصحيحة، يمكنك تقليل حمل الخادم بشكل كبير وتحسين تجربة المستخدم.
رأس Cache-Control
رأس Cache-Control هو الآلية الأساسية للتحكم في سلوك ذاكرة التخزين المؤقت:
app.get('/api/products', (req, res) => {
res.set('Cache-Control', 'public, max-age=3600');
res.json(products);
});
توجيهات Cache-Control:
public- يمكن تخزين الاستجابة مؤقتاً بواسطة أي ذاكرة تخزين مؤقت (متصفح، CDN، وكيل)private- يمكن تخزين الاستجابة مؤقتاً فقط بواسطة المتصفح (وليس CDN/وكيل)no-cache- يجب إعادة التحقق من الخادم قبل استخدام الاستجابة المخزنة مؤقتاًno-store- لا تخزن مؤقتاً على الإطلاق (بيانات حساسة)max-age=N- ذاكرة التخزين المؤقت صالحة لـ N ثانيةs-maxage=N- مثل max-age ولكن فقط لذاكرات التخزين المؤقت المشتركة (CDN)must-revalidate- يجب عدم استخدام ذاكرة تخزين مؤقت قديمة، يجب إعادة التحققimmutable- لن يتغير المورد أبداً (مثالي للأصول المُصنفة)
استراتيجيات التخزين المؤقت الشائعة
// هذه الملفات لا تتغير أبداً (إصدار جديد = اسم ملف جديد)
app.use('/static', express.static('public', {
maxAge: '1y', // سنة واحدة
immutable: true
}));
// استجابات API التي تتغير أحياناً
app.get('/api/config', (req, res) => {
res.set('Cache-Control', 'public, max-age=300'); // 5 دقائق
res.json(config);
});
// بيانات خاصة بالمستخدم
app.get('/api/user/profile', (req, res) => {
res.set('Cache-Control', 'private, max-age=60'); // دقيقة واحدة، متصفح فقط
res.json(userProfile);
});
// بيانات حساسة
app.get('/api/user/credit-card', (req, res) => {
res.set('Cache-Control', 'no-store'); // لا تخزن مؤقتاً أبداً
res.json(sensitiveData);
});
// محتوى ديناميكي يجب إعادة التحقق منه
app.get('/api/news', (req, res) => {
res.set('Cache-Control', 'public, max-age=60, must-revalidate');
res.json(news);
});
public, max-age=31536000, immutable للأصول الثابتة مع أسماء ملفات تعتمد على المحتوى (مثل، app.a3f2b1.js). هذا يوفر أقصى فوائد التخزين المؤقت.ETag (علامة الكيان)
تمكن ETags الطلبات الشرطية - يرسل الخادم معرفاً فريداً لكل إصدار من المورد:
app.get('/api/products', async (req, res) => {
const products = await db.products.findAll();
const content = JSON.stringify(products);
// إنشاء ETag من تجزئة المحتوى
const etag = crypto
.createHash('md5')
.update(content)
.digest('hex');
// تحقق مما إذا كان العميل لديه الإصدار الحالي
if (req.headers['if-none-match'] === etag) {
// لم يتغير المحتوى
return res.status(304).end(); // لم يتم التعديل
}
// تغير المحتوى، أرسل إصداراً جديداً
res.set({
'ETag': etag,
'Cache-Control': 'public, max-age=60'
});
res.json(products);
});
رأس Last-Modified
مماثل لـ ETag ولكن يستخدم الطوابع الزمنية بدلاً من تجزئات المحتوى:
const article = await db.articles.findById(req.params.id);
const lastModified = article.updated_at;
// تحليل رأس If-Modified-Since
const ifModifiedSince = req.headers['if-modified-since'];
if (ifModifiedSince) {
const clientDate = new Date(ifModifiedSince);
const serverDate = new Date(lastModified);
if (serverDate <= clientDate) {
return res.status(304).end(); // لم يتم التعديل
}
}
res.set({
'Last-Modified': lastModified.toUTCString(),
'Cache-Control': 'public, max-age=300'
});
res.json(article);
});
رأس Vary
يخبر رأس Vary ذاكرات التخزين المؤقت بأن الاستجابة تعتمد على رؤوس طلب معينة:
app.get('/api/messages', (req, res) => {
const lang = req.headers['accept-language']?.split(',')[0] || 'en';
const messages = getMessagesForLanguage(lang);
res.set({
'Cache-Control': 'public, max-age=3600',
'Vary': 'Accept-Language' // تخزين منفصل لكل لغة
});
res.json(messages);
});
// تختلف الاستجابة حسب Accept-Encoding
app.get('/api/large-data', (req, res) => {
res.set({
'Cache-Control': 'public, max-age=86400',
'Vary': 'Accept-Encoding' // تخزين مضغوط وغير مضغوط منفصلاً
});
res.json(largeData);
});
// رؤوس vary متعددة
app.get('/api/content', (req, res) => {
res.set({
'Cache-Control': 'public, max-age=1800',
'Vary': 'Accept-Language, Accept-Encoding'
});
res.json(content);
});
Vary: Cookie أو Vary: Authorization يمكن أن يجعل التخزين المؤقت لـ CDN غير فعال. استخدم هذه بشكل مقتصد وفكر في أساليب بديلة للمحتوى الشخصي.Express Middleware للتخزين المؤقت لـ HTTP
أنشئ middleware قابلة لإعادة الاستخدام لأنماط التخزين المؤقت الشائعة:
function cache(seconds, options = {}) {
return (req, res, next) => {
const { isPublic = true, mustRevalidate = false, vary } = options;
let cacheControl = isPublic ? 'public' : 'private';
cacheControl += `, max-age=${seconds}`;
if (mustRevalidate) cacheControl += ', must-revalidate';
res.set('Cache-Control', cacheControl);
if (vary) res.set('Vary', vary);
next();
};
}
// middleware ETag
function etag() {
return (req, res, next) => {
const originalJson = res.json.bind(res);
res.json = function(data) {
const content = JSON.stringify(data);
const hash = crypto
.createHash('md5')
.update(content)
.digest('hex');
if (req.headers['if-none-match'] === hash) {
return res.status(304).end();
}
res.set('ETag', hash);
return originalJson(data);
};
next();
};
}
// الاستخدام
app.get('/api/products',
cache(300), // 5 دقائق
etag(),
async (req, res) => {
const products = await db.products.findAll();
res.json(products);
}
);
app.get('/api/user/profile',
cache(60, { isPublic: false }), // دقيقة واحدة، خاصة
async (req, res) => {
const profile = await getUserProfile(req.userId);
res.json(profile);
}
);
تكامل CDN
تحسين رؤوس ذاكرة التخزين المؤقت لـ CDN (CloudFlare، Fastly، AWS CloudFront):
app.get('/api/static/config', (req, res) => {
res.set({
// المتصفحات تخزن مؤقتاً لمدة 5 دقائق
// CDN تخزن مؤقتاً لمدة ساعة واحدة
'Cache-Control': 'public, max-age=300, s-maxage=3600',
'CDN-Cache-Control': 'max-age=3600' // خاص بـ CloudFlare
});
res.json(config);
});
// تجاوز CDN للمحتوى الشخصي
app.get('/api/recommendations', (req, res) => {
res.set({
'Cache-Control': 'private, max-age=60',
'Surrogate-Control': 'no-store' // أخبر CDN بعدم التخزين المؤقت
});
res.json(recommendations);
});
Stale-While-Revalidate
تقديم محتوى قديم أثناء جلب بيانات جديدة في الخلفية:
res.set({
// تخزين مؤقت لمدة 30 ثانية
// السماح بتقديم قديم لمدة 60 ثانية أثناء إعادة التحقق
'Cache-Control': 'public, max-age=30, stale-while-revalidate=60'
});
res.json(feed);
});
stale-while-revalidate يوفر أفضل تجربة للمستخدم - يحصل المستخدمون دائماً على استجابات فورية بينما يتم تحديث ذاكرة التخزين المؤقت في الخلفية.تصحيح أخطاء ذاكرة التخزين المؤقت
أضف رؤوس تصحيح لفهم سلوك ذاكرة التخزين المؤقت:
return (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
res.set({
'X-Cache-Status': res.statusCode === 304 ? 'HIT' : 'MISS',
'X-Response-Time': `${duration}ms`
});
});
next();
};
}
// تمكين في التطوير
if (process.env.NODE_ENV === 'development') {
app.use(cacheDebug());
}