بناء CDN Server خاص بكم: جلب مرة واحدة، وتسليم ألف مرة
يجلب CDN Server كل ملف مرة واحدة من الأصل، ثم يسلّمه بنفسه ألف مرة. وبذلك لا يرى خادم الأصل سوى جزء صغير من الطلبات، ويحصل الزوّار على أزمنة تحميل قصيرة. وهكذا تبنون واحداً.
كل ملف يحمّله زائر من موقعكم، يجلبه خادم الأصل عندكم من نظام الملفات، ويضعه في جواب، ويرسله. ومع عشرة زوّار في اليوم لا يلاحظ ذلك أحد. أما مع عشرة آلاف زائر في وقت واحد، فهذا بالضبط هو السبب في بطء الموقع، رغم أن التطبيق نفسه لا يفعل شيئاً.
ويجلس CDN Server أمام ذلك. فهو يجلب كل ملف مرة واحدة من الأصل، ويسلّمه بعد ذلك بنفسه، بقدر ما يُطلب. وبذلك لا يرى الأصل سوى جزء صغير من الطلبات، ويحصل الزوّار على ملفاتهم من خادم لا يفعل شيئاً آخر غير التسليم. يبيّن هذا المقال ما يجنيه ذلك فعلاً، وكيف تُعِدّون خادماً كهذا باستخدام nginx، وما المهم في الترافيك والموقع.
ما الذي يفعله CDN Server حقاً
يُحكى سير العمل في ثلاث جمل:
- يطلب زائر ملفاً من CDN Server.
- إن كان الملف موجوداً هناك، خرج فوراً. وهذه إصابة.
- وإن لم يكن موجوداً، جلبه CDN Server من الأصل، وخزّنه، وسلّمه. وهذا إخفاق، ويحدث لكل ملف مرة واحدة فقط في كل مدة صلاحية.
والمبدأ هو نفسه مبدأ Stream Server: جلب مرة واحدة، وتسليم متكرّر. والفرق الوحيد أن Stream Server يوزّع تدفّق بيانات جارياً، وأن CDN Server يوزّع ملفات جاهزة.
الحساب: ما مقدار التخفيف الذي تجلبه الذاكرة المؤقتة
الحاسم هو نسبة الإصابة، أي نسبة الطلبات التي تجيب عنها الذاكرة المؤقتة بنفسها. وعند مليون طلب في اليوم يبدو التخفيف كما يلي:
| نسبة الإصابة | الطلبات على الأصل | التخفيف |
|---|---|---|
| 50 % | 500,000 | معامل 2 |
| 80 % | 200,000 | معامل 5 |
| 95 % | 50,000 | معامل 20 |
| 99 % | 10,000 | معامل 100 |
وبالنسبة للملفات الثابتة فإن 95 بالمئة وأكثر هي الحالة الطبيعية، لأن الصور والسكربتات والخطوط تتغيّر نادراً. والقفزة من 95 إلى 99 بالمئة أقيم ممّا توحي به الأرقام: فهي تُنزل الحمل المتبقّي إلى خُمسه مرة أخرى.
وفي الوقت نفسه ينتقل الترافيك. فالأصل لا يكاد ينقل شيئاً، وCDN Server ينقل أكثر بكثير. فعند 500,000 استدعاء للصور في اليوم بحجم 400 KB لكل صورة، يكون ذلك 200 GB يومياً، أي نحو 6 TB في الشهر، للصور وحدها. ولهذا فإن الرقم المهم في عقدة CDN ليس المعالج، بل الوصلة والحجم.
متى يستحق CDN Server خاص الأمر
يحاسب مزوّدو CDN التجاريون على كل جيجابايت منقول. وهذا مريح ورخيص عند الحجم القليل. لكن النسبة تنقلب بدءاً من كمية معيّنة:
| الحالة | CDN Server خاص | المحاسبة على الجيجابايت |
|---|---|---|
| بضعة GB في الشهر | لا يكاد يستحق | أرخص |
| عدة TB في الشهر | سعر شهري ثابت | ينمو خطّياً |
| تنزيلات كبيرة وأرشيفات فيديو | متقدّم بوضوح | يصبح مكلفاً سريعاً |
| تحكّم كامل في القواعد والسجلات | كامل | محدود |
ويُضاف إلى ذلك أمر يصعب تقديره بالأرقام: فمع عقدة خاصة تقرّرون أنتم ما يُخزَّن مؤقتاً، وكم يبقى، ومتى يختفي. ولستم مقيّدين بقواعد مزوّد غريب، وتستطيعون كذلك تسليم أشياء لا تدخل في مخططات الآخرين.
البناء باستخدام nginx كذاكرة مؤقتة
التثبيت
apt update
apt install -y nginx
لا يحتاج الأمر أكثر من ذلك، فالذاكرة المؤقتة مبنية في nginx. أنشئوا المجلد الذي ستوضع فيه الملفات:
mkdir -p /var/cache/cdn
chown www-data:www-data /var/cache/cdn
الضبط
تُوصَف الذاكرة المؤقتة مرة واحدة في كتلة http، ثم تُستخدَم في المواضع المفردة:
proxy_cache_path /var/cache/cdn
levels=1:2
keys_zone=cdn:100m
max_size=200g
inactive=30d
use_temp_path=off;
proxy_cache_key "$scheme$request_method$host$request_uri";
وتعني القيم على التفصيل: keys_zone=cdn:100m يكفي لنحو 800,000 مدخل، وmax_size يحدّ المساحة على القرص، وinactive=30d يرمي الملفات التي لم يطلبها أحد خلال 30 يوماً، وuse_temp_path=off يوفّر نقلاً لا داعي له بين المجلدات.
وتحت ذلك الخادم نفسه:
server {
listen 443 ssl;
http2 on;
server_name cdn.example.com;
ssl_certificate /etc/letsencrypt/live/cdn.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cdn.example.com/privkey.pem;
location / {
proxy_pass https://origin.example.com;
proxy_set_header Host origin.example.com;
proxy_http_version 1.1;
proxy_cache cdn;
proxy_cache_valid 200 301 302 24h;
proxy_cache_valid 404 1m;
# تجميع عدة إخفاقات متزامنة في طلب واحد
proxy_cache_lock on;
proxy_cache_lock_timeout 10s;
# مواصلة التسليم عند حدوث مشاكل في الأصل
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
# كوكيز الجلسة لا يجوز أن تُفسد الذاكرة المؤقتة
proxy_ignore_headers Set-Cookie;
proxy_hide_header Set-Cookie;
add_header X-Cache-Status $upstream_cache_status always;
}
}
وثلاثة أسطر منها تستحق انتباهاً خاصاً. فـ proxy_cache_lock يضمن ألا يطلب مئة زائر الأصل في الوقت نفسه عند ملف جديد، بل واحد فقط وينتظر الباقون. وproxy_cache_use_stale يُبقي التسليم قائماً أثناء إعادة تشغيل الأصل أو بطء جوابه. وإخفاء Set-Cookie يمنع أن يُفسد جوابٌ واحد يحمل كوكي جلسة الذاكرة المؤقتة كلها.
التحقّق من أنه يعمل
nginx -t && systemctl reload nginx
curl -sI https://cdn.example.com/bild.jpg | grep -i x-cache-status
curl -sI https://cdn.example.com/bild.jpg | grep -i x-cache-status
في الاستدعاء الأول تظهر هناك MISS، وفي الثاني HIT. وإذا ظهرت MISS في المرة الثانية أيضاً، فالسبب يكون دائماً تقريباً أحد المتهمين الثلاثة المعتادين: Set-Cookie في الجواب، أو Cache-Control: no-store من الأصل، أو غياب بيان الطول.
ما يُخزَّن مؤقتاً وما لا يُخزَّن
الحلّ النظيف هو أن يقول الأصل ذلك بنفسه. فهو يعرف محتوياته، والذاكرة المؤقتة لا تعرفها. ولذلك يضبط الترويسات المناسبة:
| المحتوى | الترويسة على الأصل |
|---|---|
| ملف يحمل بصمة تحقّق في اسمه | Cache-Control: public, max-age=31536000, immutable |
| صورة أو تنزيل من دون ترقيم إصدار | Cache-Control: public, max-age=86400 |
| HTML لصفحة عامة | Cache-Control: public, max-age=300 |
| منطقة بعد تسجيل الدخول، سلة الشراء | Cache-Control: private, no-store |
وإن أردتم أن تقرّروا في الذاكرة المؤقتة نفسها، فذلك يجري عبر مواضع خاصة لكل نوع ملف:
location ~* \.(jpg|jpeg|png|gif|webp|avif|svg|ico|css|js|woff2|mp4|zip)$ {
proxy_pass https://origin.example.com;
proxy_cache cdn;
proxy_cache_valid 200 7d;
add_header Cache-Control "public, max-age=604800";
add_header X-Cache-Status $upstream_cache_status always;
}
التخلّص من المحتويات بشكل موجّه
أبسط طريق هو ألا تحتاجوا إلى الحذف أصلاً: فمن يمنح كل صورة مُعدَّلة اسم ملف جديد، مثل logo.a1b2c3.png، لا تواجهه هذه المشكلة. والملف القديم يسقط من الذاكرة المؤقتة من تلقاء نفسه في وقت ما.
وإن لم يكن ذلك ممكناً، فتساعد في nginx الوحدة الحرة ngx_cache_purge. وبلا وحدة إضافية يمكن حساب المسار من مفتاح الذاكرة المؤقتة، لأن nginx يستخدم مجموع MD5 الخاص به كاسم للملف:
KEY="httpsGETcdn.example.com/bild.jpg"
H=$(printf '%s' "$KEY" | md5sum | cut -d' ' -f1)
rm -f "/var/cache/cdn/${H: -1}/${H: -3:2}/$H"
وللاستخدام اليومي يكفي غالباً سكربت صغير ينفّذ هذه الأسطر على قائمة من المسارات.
الملفات الكبيرة والفيديو
عند الملفات التي تتجاوز بضع مئات من الميجابايت تحتاجون إلى التقسيم إلى كتل، وإلا حمّلت الذاكرة المؤقتة الملف كله من جديد عند كل قفزة في الفيديو:
location /video/ {
proxy_pass https://origin.example.com;
proxy_cache cdn;
proxy_cache_valid 200 206 7d;
slice 1m;
proxy_set_header Range $slice_range;
proxy_cache_key "$uri$slice_range";
}
وبذلك يجلب nginx الكتل المطلوبة فعلاً وحدها. فإذا قفز مشاهد إلى منتصف فيلم، مرّت بضعة ميجابايت عبر الخط بدلاً من عدة جيجابايت.
TLS وHTTP/2 وHTTP/3
تسلّم عقدة CDN كثيراً من الملفات الصغيرة إلى كثير من الاتصالات، وهناك بالضبط تثمر البروتوكولات الأحدث. وHTTP/2 ينتهي أمره بـ http2 on;. أما HTTP/3 فيجني شيئاً على اتصالات الهاتف المحمول خصوصاً، لأن تغيير الشبكة لا يقطع الاتصال:
listen 443 quic reuseport;
listen 443 ssl;
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400';
ولا تنسوا أثناء ذلك فتح UDP 443 في جدار الحماية. فهذا أكثر سبب يجعل HTTP/3 مضبوطاً ومع ذلك لا يستخدمه أحد.
عدة مواقع
عقدة واحدة تجلب أصلاً الجزء الأكبر من المكسب، أي تخفيف الحمل عن الأصل. أما المكسب الثاني، وهو قصر الطريق إلى الزائر، فلا يأتي إلا مع عدة مواقع.
ويجري التوزيع عبر DNS: فالاسم نفسه يشير إلى عقدة مختلفة تبعاً لمنشأ الزائر. وتحتاجون لذلك مزوّد DNS يدعم التوزيع الجغرافي. وتحصل كل العُقَد على الضبط نفسه والأصل نفسه، وهي لا تعرف شيئاً عن بعضها، وهذا بالضبط ما يجعل البنية متينة: فإذا تعطّلت عقدة، أخرجتموها من DNS، وتولّت الباقية الأمر.
وتوفّر KernelHost مواقع في ألمانيا، وعلى الساحل الغربي للولايات المتحدة، وعلى الساحل الشرقي للولايات المتحدة، وكذلك في سنغافورة وكوريا الجنوبية. وتجدون نظرة عامة مع قيم زمن الوصول في مواقع الخوادم. وكقاعدة عامة: عقدة واحدة لكل قارة يجلس فيها جزء يُعتدّ به من جمهوركم.
القياس بدلاً من التخمين
اكتبوا حالة الذاكرة المؤقتة في السجل، فتستطيعون عدّ نسبة الإصابة في أي وقت:
log_format cdn '$remote_addr $status $body_bytes_sent '
'$upstream_cache_status "$request"';
access_log /var/log/nginx/cdn.log cdn;
ويمكن تحليل ذلك بسطر واحد:
awk '{print $4}' /var/log/nginx/cdn.log | sort | uniq -c | sort -rn
وإذا كانت حصة HIT في الملفات الثابتة أقل من 90 بالمئة، فالبحث عن السبب يستحق الأمر دائماً تقريباً: فالسبب غالباً كوكيز في الأجوبة، أو مدد صلاحية قصيرة جداً، أو طلبات بمعاملات متغيّرة في آخر العنوان تبدو للذاكرة المؤقتة كل مرة كملف جديد.
أي خادم يناسب
عقدة CDN آلة تسليم. ولذلك يبدو ترتيب الخصائص المهمة مختلفاً عمّا هو في خادم تطبيقات:
- الترافيك والوصلة. هنا ينشأ الحمل كله. فعقدة واحدة تنقل في التشغيل العادي عدة تيرابايت في الشهر بانتظام.
- القرص. NVMe متقدّم بوضوح، لأن الذاكرة المؤقتة تتكوّن من كثير من الوصولات العشوائية الصغيرة. ويتحدّد الحجم بمقدار ما هو ساخن من محتواكم، لا بحجم المحتوى كله.
- الذاكرة. يستخدم نظام التشغيل الذاكرة الحرة كذاكرة مؤقتة ثانية أمام القرص. وهنا تعني الذاكرة الأكبر سرعة أكبر مباشرة.
- المعالج. الأقل أهمية، فهو لا يُستخدَم إلا لـ TLS، والمعالجات الحديثة تنجز ذلك على الهامش.
ولهذه الحالة بالضبط صُنعت الخوادم الجذرية بحركة مرور غير محدودة عندنا: 2 × 1 Gbit/s حقيقية لكل خادم بلا خفض للسرعة، وحجم غير محدود بسعر شهري ثابت، وأقراص NVMe من فئة مراكز البيانات في RAID، وحماية من الإغراق بسعة 3.2 Tbps من المصنع. ويستغرق التجهيز أقل من 30 ثانية، مع وصول Root كامل، وIPv4، وشبكة IPv6 بحجم /64. ولأن المحاسبة تجري على أساس الدفع المسبق مع إمكان الإلغاء شهرياً، يمكنكم البدء بعقدة واحدة وإضافة موقع ثانٍ في أي وقت، إذا توزّع جمهوركم.
الأسئلة الشائعة
ما الذي يفعله CDN Server بالضبط؟
ما مقدار التخفيف فعلياً؟
هل أحتاج لذلك إلى مزوّد تجاري؟
ما الملفات التي ينبغي تخزينها مؤقتاً وما التي لا ينبغي؟
كم ينبغي أن تحتفظ الذاكرة المؤقتة بالملفات؟
كيف أحذف ملفاً من الذاكرة المؤقتة؟
ما يحدث إذا تعطّل خادم الأصل؟
هل يعمل ذلك أيضاً مع ملفات الفيديو الكبيرة؟
كم موقعاً أحتاج؟
كيف أعرف أن الذاكرة المؤقتة تعمل؟
2026 KernelHost GmbH. جميع الحقوق محفوظة. هذا الشرح محمي بحقوق النشر. لا يُسمح بإعادة نشره على مواقع أخرى، كليًا أو جزئيًا أو بصيغة معدّلة، دون موافقتنا الخطية. أما الاقتباس مع ذكر المصدر ووضع رابط فهو مرحّب به تمامًا.

