Skip to content

التخزين الكائني وشبكات CDN

💡 دليل التعلم: ستأخذك هذه المقالة في رحلة كاملة — من رفع الملفات إلى تنزيل المستخدم لها. سترى كيف يعمل التخزين الكائني مثل "مستودع ذكي" لإدارة كميات هائلة من الملفات، وكيف تعمل CDN مثل "نقاط توزيع البريد السريع" لإيصال المحتوى إلى باب المستخدم، وما هي "العقبات" التي تنتظرك. يُنصح بفهم أساسيات طلبات HTTP ومبادئ تحليل DNS مسبقًا.

قبل البدء، يُنصح ببناء بعض "الأساسيات":


0. المقدمة: لماذا يكون رفع وتنزيل الملفات "بطيئًا" جدًا؟

تخيل هذا السيناريو: قمت برفع صورة عالية الدقة بحجم 10 ميجابايت في مجتمع للصور، وانتظرت نصف دقيقة حتى اكتمل الرفع؛ بينما صديقك في بكين ينقر للتنزيل ويستغرق ثانيتين فقط. لماذا تختلف تجربة الرفع والتنزيل لنفس الملف بهذا القدر الكبير؟

أو فكر مرة أخرى: موقع التجارة الإلكترونية الخاص بك يجري عرضًا ترويجيًا في يوم العزاب (11.11)، وفجأة تتدفق ملايين الزيارات إلى صفحة تفاصيل المنتج، فيتعطل الخادم مباشرة. هل المشكلة في عرض النطاق الترددي؟ أم أن هناك خطأ في تصميم البنية؟

إجابات هذه الأسئلة مخبأة في الثنائي الذهبي: التخزين الكائني و CDN.


1. التخزين الكائني: "مستودعك السحابي الذكي"

1.1 ما هو التخزين الكائني؟

نظام الملفات التقليدي يشبه خزانة ملابسك: الملابس مرتبة حسب "قمصان/بناطيل/تنانير" في طبقات، للعثور على قميص، يجب أن تفتح الخزانة → منطقة القمصان → رف القمصان. هذا النموذج "الهرمي المتداخل" يصبح بطيئًا للغاية عندما ينفجر عدد الملفات.

أما التخزين الكائني فهو مثل اللوجستيات الحديثة: كل طرد له "رقم تتبع" فريد (مفتاح الكائن)، يكفي إعطاء الرقم، ويقوم روبوت المستودع باستخراجه بدقة من بين آلاف الطرود.

🗄️Object Storage ArchitectureUnderstand the relationship between Bucket, Object, and Metadata.
📦BucketsNamespace isolation and permission control
🖼️
myapp-images-prod
12543 objects
256 GB
🎬
myapp-videos-prod
892 objects
1.2 TB
💾
myapp-backups
3456 objects
500 GB
📄ObjectsFile data + metadata
Click a bucket above to view objects.
💡Core idea:Object storage uses a three-level structure: Account → Bucket → Object. Each object carries rich metadata for retrieval and management.

مقارنة الفروقات الأساسية:

البُعدنظام الملفات التقليديالتخزين الكائني
طريقة التنظيمشجرة أدلة هرميةأزواج مفتاح-قيمة مسطحة
بروتوكول الوصولPOSIX (عمليات الملفات المحلية)HTTP/REST API
قابلية التوسعسعة محدودة للجهاز الواحدتوسع أفقي غير محدود تقريبًا
البيانات الوصفيةخصائص أساسية (الحجم، الوقت)بيانات وصفية مخصصة غنية
السيناريوهات النموذجيةمستندات المكتب المحليةالصور/الفيديو/النسخ الاحتياطي/الموارد الثابتة

1.2 المفاهيم الأساسية للتخزين الكائني

الحاوية (Bucket): "قسم المستودع" الخاص بك

الحاوية هي الحاوية العليا في التخزين الكائني، وتعادل مساحة اسم مستقلة. يجب تخزين جميع الكائنات داخل حاوية معينة.

قواعد التسمية (باستخدام Alibaba Cloud OSS كمثال):

  • فريدة عالميًا: لا يمكن تكرارها بين جميع مستخدمي مزود السحابة بأكمله
  • يمكن أن تحتوي فقط على أحرف صغيرة وأرقام وشرطات
  • يجب أن تبدأ وتنتهي بحرف صغير أو رقم
  • الطول بين 3-63 حرفًا

درس عملي: أنشأ أحد الفرق عشرات الحاويات بناءً على خطوط الأعمال، وعندما جاءت الفاتورة في نهاية الشهر اندهشوا — كل حاوية لها حد أدنى من رسوم التخزين والطلبات. نصيحة: خطط الحاويات بناءً على "البيئة + الغرض"، مثل prod-static-assets و dev-backup-archive.

الكائن (Object): "طرد البيانات" الخاص بك

الكائن هو الوحدة الأساسية للتخزين، ويتكون من ثلاثة أجزاء:

  1. المفتاح (Key): المعرّف الفريد للكائن، يعادل "رقم التتبع"

    • مثال: images/avatar/2024/user123.jpg
    • على الرغم من أنه يبدو كمسار، إلا أنه في جوهره مجرد سلسلة نصية
  2. البيانات (Data): محتوى الكائن نفسه

    • يمكن أن تكون أي بيانات ثنائية
    • حد الحجم يعتمد على مزود السحابة (عادةً في حدود 5 تيرابايت للكائن الواحد)
  3. البيانات الوصفية (Metadata): معلومات إضافية تصف الكائن

    • بيانات وصفية للنظام: Content-Type و ETag و Last-Modified إلخ
    • بيانات وصفية مخصصة: مثل x-oss-meta-owner و x-oss-meta-project

التحكم في الوصول: من يستطيع الوصول إلى "مستودعي"؟

يوفر التخزين الكائني تحكمًا متعدد المستويات في الصلاحيات:

المستوىطريقة التحكمالسيناريو النموذجي
مستوى الحاويةBucket Policy (سياسة الموارد)منع كل الوصول الخارجي، السماح بعناوين IP محددة فقط
مستوى الكائنACL (قائمة التحكم في الوصول)صور عامة، مستندات خاصة
تفويض مؤقتSTS (خدمة الرمز الآمن)الرفع المباشر من الواجهة الأمامية، الرفع من الجوال

الخط الأحمر الأمني: لا تضع أبدًا AccessKey ID و AccessKey Secret في كود الواجهة الأمامية! الطريقة الصحيحة: تطلب الواجهة الأمامية بيانات اعتماد STS المؤقتة من الخادم الخلفي، ويتحقق الخادم الخلفي من الهوية ثم يعيد بيانات اعتماد مؤقتة مع وقت انتهاء صلاحية.


2. CDN: "شبكة التوصيل السريع العالمية" الخاصة بك

2.1 لماذا نحتاج إلى CDN؟

تخيل أنك فتحت متجرًا إلكترونيًا، والخادم موجود في شنتشن. الآن مستخدم في بكين يصل إلى صورك:

  • بدون CDN: ينتقل الطلب من بكين → خبي → خنان → خوبي → خونان → غوانغدونغ → شنتشن، عبر أكثر من 2000 كيلومتر، ذهابًا وإيابًا أكثر من 4000 كيلومتر. يستغرق النقل الشبكي وحده عشرات المللي ثانية، ويكون أسوأ في أوقات ازدحام الشبكة.

  • مع CDN: ينتقل الطلب من بكين مباشرة إلى عقدة CDN في بكين (قد تكون في غرفة خوادم China Unicom في بكين)، وتتقلص المسافة من 2000 كيلومتر إلى 20 كيلومترًا، والتأخير من 50 مللي ثانية إلى 5 مللي ثانية.

هذه هي القيمة الأساسية لـ CDN: جعل المحتوى أقرب إلى المستخدم.

🌐How CDN Acceleration WorksHow edge nodes, origin server, and origin fetch work together.
👥Global Users
👤
Beijing user
👤
Shanghai user
👤
Guangzhou user
👤
Chengdu user
👤
Overseas user
🌐CDN Edge Nodes
🌐
Beijing node
North China
Cache2.5 TB
Hit92%
🌐
Shanghai node
East China
Cache3.1 TB
Hit89%
🌐
Guangzhou node
South China
Cache1.8 TB
Hit87%
🌐
Chengdu node
Southwest China
Cache1.2 TB
Hit85%
🏢Origin Server
🗄️
Object storage origin
bucket.oss-cn-beijing.aliyuncs.com
Healthy
🎮 Simulation
📊 Access Stats
0
Cache hits
0
Cache misses
0%
Hit rate
0ms
Avg response
💡Core idea:CDN is like opening branches worldwide: users get resources from the nearest branch instead of always visiting the main store.

2.2 البنية الأساسية لـ CDN

العقد الطرفية: "محطة التوصيل" الأقرب للمستخدم

العقد الطرفية هي الطبقة الأقرب للمستخدم في شبكة CDN، وتُنشر عادةً في:

  • غرف خوادم مزودي الخدمة (China Unicom / China Telecom / China Mobile)
  • مراكز تبادل الإنترنت في المدن الكبرى
  • محاور النقل المهمة

توزيع عقد CDN الرئيسية في الصين:

  • مدن الدرجة الأولى: بكين، شنغهاي، غوانغتشو، شنتشن
  • مدن الدرجة الثانية: هانغتشو، نانجينغ، تشنغدو، ووهان، شيآن
  • خارج الصين: هونغ كونغ، سنغافورة، طوكيو، وادي السيليكون، فرانكفورت

Edge Node Distribution Demo

Shows global CDN edge-node distribution and scheduling strategy.

الخادم المصدر: "المستودع الرئيسي" للمحتوى

الخادم المصدر هو المكان الذي تعود إليه CDN للحصول على المحتوى، ويمكن أن يكون:

  • التخزين الكائني (OSS/COS/S3)
  • خادم ذاتي البناء (ECS/خادم مادي)
  • موازن تحميل (SLB/CLB)

الإعدادات الرئيسية:

  • مضيف العودة للمصدر (Origin HOST): اسم النطاق/IP المستخدم عند وصول عقدة CDN إلى الخادم المصدر
  • بروتوكول العودة للمصدر: HTTP أم HTTPS
  • منفذ العودة للمصدر: 80 أو 443 أو منفذ مخصص

العقد الوسيطة: "مركز الفرز الإقليمي"

بين العقد الطرفية والخادم المصدر، تمتلك CDN عادةً طبقة أو أكثر من العقد الوسيطة:

  • عقد التجميع: تجميع طلبات العودة للمصدر من عدة عقد طرفية، لتقليل الضغط على الخادم المصدر
  • المراكز الإقليمية: مسؤولة عن توزيع وجدولة المحتوى لمنطقة كبيرة

فوائد هذه البنية الطبقية:

  1. تقليل ضغط الخادم المصدر: 1000 طلب من العقد الطرفية، قد يحتاج فقط إلى 10 طلبات إلى الخادم المصدر
  2. تحسين معدل الإصاب��: يتم اعتراض المحتوى الشائع في الطبقة الوسيطة، دون الحاجة للعودة إلى المصدر
  3. عزل الأعطال: عند حدوث مشكلة في مسار معين، يمكن التبديل تلقائيًا إلى مسارات أخرى

2.3 العملية الكاملة لتسريع CDN

لنتتبع طلب مستخدم حقيقي:

⚙️Cache Policy DemoShows CDN and object-storage cache policy configuration, including TTL and refresh.
💡Core idea:Cache policy balances hit rate and freshness. A TTL that is too short causes frequent origin fetches; one that is too long can serve stale content.

الخطوة 1: تحليل DNS (الجدولة الذكية)

إدخال المستخدم: cdn.example.com/image.jpg

خادم DNS يعيد: عنوان IP لعقدة CDN في بكين China Unicom (1.2.3.4)

المفتاح هنا هو DNS الذكي: يعيد عنوان IP الأمثل لعقدة CDN بناءً على مزود خدمة المستخدم وموقعه الجغرافي وحمل العقدة.

الخطوة 2: بحث العقدة الطرفية (إصابة في الذاكرة المؤقتة؟)

يصل الطلب إلى عقدة CDN في بكين China Unicom (1.2.3.4)

تتحقق العقدة من الذاكرة المؤقتة المحلية:
├─ إصابة؟ تعيد المحتوى مباشرة ✓
└─ غير مصابة؟ تتابع إلى الخطوة التالية

الخطوة 3: العودة إلى المصدر (تصاعديًا طبقة تلو الأخرى)

العقدة الطرفية غير مصابة

تطلب من العقدة الأم (مثل: المركز الإقليمي لشمال الصين)
├─ العقدة الأم مصابة؟ تعيد المحتوى
└─ العقدة الأم غير مصابة؟ تستمر للأعلى

    تطلب من الخادم المصدر

    الخادم المصدر يعيد المحتوى

الخطوة 4: التخزين المؤقت والإرجاع (أسرع في المرة القادمة)

يعود المحتوى عبر المسار

كل طبقة من العقد تخزن نسخة مؤقتة

يصل أخيرًا إلى المستخدم

بهذه الطريقة، عندما يطلب مستخدم آخر نفس الملف، يمكن إعادته مباشرة من العقدة الطرفية، محققًا "فتحًا فوريًا".


3. من الرفع إلى الوصول: تحليل المسار الكامل

3.1 ثلاث طرق لرفع الملفات

📤File Upload FlowUnderstand direct upload, multipart upload, and resumable upload.
🚀
Direct upload
Upload small files to object storage in one request
Best for: < 100MB
🔪
Multipart upload
Split large files into parts and upload in parallel
Best for: > 100MB
💾
Resumable upload
Continue from the breakpoint after network interruption
Best for: Any size
🚀 Direct Upload Flow
1
User selects file
Browser selects a 5MB image file
2
Request upload credential
Frontend → backend → temporary STS credential
3
Upload directly to object storage
Browser → OSS/COS, 5MB in one request
4
Upload complete
Return URL; frontend asks backend to save record
💡Core idea:Multipart upload improves reliability for large files. If the network breaks, resumable upload avoids sending the whole file again.

الطريقة الأولى: العميل → الخادم → التخزين الكائني (النمط التقليدي)

المتصفح → خادمك الخلفي → التخزين الكائني

العملية:

  1. يختار المستخدم الملف، وينقر على رفع
  2. يُرفع الملف أولاً إلى خادمك الخلفي
  3. بعد أن يستلم الخادم الخلفي الملف بالكامل، يعيد رفعه إلى التخزين الكائني
  4. تُعاد نتيجة الرفع إلى المستخدم

المزايا:

  • تنفيذ بسيط، سهل التحكم فيه من الطرفين الأمامي والخلفي
  • يمكن إجراء التحقق من الملفات وتحويل التنسيق في الخادم الخلفي
  • يمكن تسجيل العمليات الحساسة وإجراء التحقق من الصلاحيات

العيوب:

  • استهلاك مضاعف لعرض النطاق: رفع المستخدم يستهلك عرض النطاق مرة، وإعادة رفع الخادم تستهلكه مرة أخرى
  • ضغط عالٍ على الخادم: الملفات الكبيرة تستهلك الكثير من الذاكرة ووحدة المعالجة المركزية
  • رفع بطيء: يعادل وجود محطة عبور إضافية، مما يجعل وقت الرفع المدرك من المستخدم أطول

السيناريوهات المناسبة: الملفات الصغيرة (< 10 ميجابايت)، الحاجة إلى معالجة خلفية (مثل ضغط الصور، إضافة العلامات المائية)، أنظمة الإدارة الداخلية.

الطريقة الثانية: الرفع المباشر من العميل إلى التخزين الكائني (موصى به حديثًا)

المتصفح ──────→ التخزين الكائني

        الخادم الخلفي يصدر فقط بيانات الاعتماد المؤقتة

العملية:

  1. يختار المستخدم الملف، وتطلب الواجهة الأمامية أولاً "بيانات اعتماد الرفع" من الخادم الخلفي
  2. يتحقق الخادم الخلفي من هوية المستخدم، ويطلب بيانات اعتماد STS المؤقتة (مع وقت انتهاء) من خدمة التخزين الكائني
  3. يعيد الخادم الخلفي بيانات الاعتماد المؤقتة إلى الواجهة الأمامية
  4. تستخدم الواجهة الأمامية بيانات الاعتماد لرفع الملف مباشرة إلى التخزين الكائني
  5. يعيد التخزين الكائني نتيجة الرفع، وتُعلم الواجهة الأمامية الخادم الخلفي "باكت���ال الرفع"

المزايا:

  • رفع سريع: إزالة خطوة العبور الوسيطة، أسرع سرعة يدركها المستخدم
  • ضغط منخفض على الخادم: يتعامل فقط مع إصدار بيانات الاعتماد، وليس مع تدفق الملفات
  • توفير عرض النطاق: تمر حركة الرفع مرة واحدة فقط
  • أمان عالٍ: بيانات الاعتماد المؤقتة لها وقت انتهاء، والضرر محدود حتى في حالة التسريب

العيوب:

  • تنفيذ أكثر تعقيدًا قليلاً، يتطلب فهم STS وآلية التوقيع
  • تحتاج الواجهة الأمامية إلى معالجة منطق الرفع المجزأ واستئناف التحميل بعد الانقطاع
  • يحتاج CORS (المشاركة عبر الأصول) إلى التكوين

السيناريوهات المناسبة: رفع الملفات الكبيرة، المحتوى الذي ينشئه المستخدم (UGC)، الأعمال التي تتطلب رفعًا عالي التزامن.

الطريقة الثالثة: الرفع المجزأ + استئناف التحميل بعد الانقطاع (ضروري للملفات الكبيرة)

ملف فيديو بحجم 10 جيجابايت

تقسيمه إلى 1000 جزء بحجم 10 ميجابايت لكل جزء

رفع متوازٍ (5 أجزاء في نفس الوقت)

انقطع الاتصال! تم رفع 600 جزء

استعادة الاتصال، الاستمرار من الجزء 601

بعد رفع كل الأجزاء، إرسال طلب "دمج"

لماذا نحتاج إلى التجزئة؟

السيناريوبدون تجزئةمع تجزئة
تقلب الشبكةبعد رفع 99%، انقطاع الشبكة، إعادة رفع كاملةإعادة رفع الأجزاء الفاشلة فقط
سرعة الرفعخيط واحد، بطيءخيوط متعددة متوازية، سريعة
استهلاك الذاكرةتحتاج إلى تخزين الملف بالكاملتحتاج فقط إلى تخزين الجزء الحالي
عرض التقدمفقط 0% و 100%تقدم دقيق لكل جزء

مواصفات التجزئة لمزودي السحابة الرئيسيين:

المزودحد حجم الجزءالحد الأقصى للأجزاءالحد الأدنى لحجم الجزء
Alibaba Cloud OSS100 ميجابايت10000100 كيلوبايت
Tencent Cloud COS5 جيجابايت100001 ميجابايت
AWS S35 جيجابايت100005 ميجابايت (موصى به)
Qiniu Cloud100 ميجابايت100004 ميجابايت

3.2 شرح تفصيلي لاستراتيجيات العودة إلى المصدر في CDN

⚙️Cache Policy DemoShows CDN and object-storage cache policy configuration, including TTL and refresh.
💡Core idea:Cache policy balances hit rate and freshness. A TTL that is too short causes frequent origin fetches; one that is too long can serve stale content.

ما هي "العودة إلى المصدر"؟

عقدة CDN الطرفية تخزن محتوى الخادم المصدر مؤقتًا، لكن عندما:

  • يُطلب المحتوى لأول مرة
  • المحتوى المخزن مؤقتًا انتهت صلاحيته (انتهاء TTL)
  • تم مسح/تسخين الذاكرة المؤقتة يدويًا

تحتاج عقدة CDN إلى طلب أحدث محتوى من الخادم المصدر، وتسمى هذه العملية "العودة إلى المصدر".

أنماط العودة إلى المصدر الثلاثة

النمطالمبدأالسيناريو المناسبالمزايا والعيوب
العودة المباشرةعقدة CDN → الخادم المصدرالخادم المصدر له IP عام، وحركة المرور ليست كبيرةبسيط ومباشر، لكن ضغط الخادم المصدر عالٍ
العودة عبر مصدر وسيطعقدة CDN → الطبقة الوسيطة → الخادم المصدرالمواقع الكبيرة، بنية تخزين مؤقت متعددة الطبقاتتوزيع ضغط الخادم المصدر، البنية معقدة
OSS/COS كمصدرعقدة CDN → التخزين الكائنيالموارد الثابتة، الصور، الفيديوأفضل ممارسة، تكلفة منخفضة، أداء جيد

تكوين العودة إلى المصدر عمليًا

السيناريو 1: التخزين الكائني كمصدر (موصى به)

وصول المستخدم: cdn.example.com/images/photo.jpg

            عقدة CDN الطرفية (بكين)

            غير مصابة، العودة إلى المصدر

            المصدر: bucket-name.oss-cn-beijing.aliyuncs.com

            إعادة الصورة، CDN تخزن مؤقتًا وتستجيب للمستخدم

عناصر التكوين الرئيسية:

  • نوع المصدر: نطاق OSS/COS أو مصدر مخصص
  • بروتوكول العودة: HTTP أم HTTPS (يُنصح بـ HTTPS)
  • مضيف العودة (Origin HOST): رأس Host المستخدم عند الوصول إلى المصدر
  • SNI العودة: مؤشر اسم الخادم عند العودة عبر HTTPS

السيناريو 2: موازنة تحميل بمصادر متعددة

عندما لا يتحمل مصدر واحد ضغط العودة، يمكن تكوين مصادر متعددة:

عقدة CDN الطرفية
    ├─ المصدر A (وزن 50%)
    ├─ المصدر B (وزن 30%)
    └─ المصدر C (وزن 20%)

نمط الرئيسي/الاحتياطي:

عقدة CDN الطرفية
    ├─ المصدر الرئيسي A (كل حركة المرور عندما يكون سليمًا)
    └─ المصدر الاحتياطي B (التبديل عند تعطل المصدر الرئيسي)

عرض النطاق للعودة مقابل عرض نطاق CDN

هناك مفهوم سهل الخلط بينه:

المؤشرالتعريفعلاقة الفوترة
عرض النطاق الصادر لـ CDNحركة المرور من عقدة CDN إلى المستخدمعادةً رسوم CDN حسب حركة المرور
عرض النطاق للعودةحركة المرور من المصدر إلى عقدة CDNعادةً رسوم حركة المرور الصادرة من التخزين الكائني أو المصدر

نصائح لتوفير التكاليف:

  • تحسين معدل إصابة CDN (جعل المزيد من الطلبات تصيب الذاكرة المؤقتة، لتقليل العودة)
  • تعيين وقت تخزين مؤقت معقول (TTL)
  • استخدام ميزة التسخين، لتخزين المحتوى الشائع مؤقتًا قبل وصول المستخدم
  • تفعيل "متابعة 301/302"، لتجنب قفزات العودة غير الضرورية

3.3 تكوين استراتيجية التخزين المؤقت

⚙️Cache Policy DemoShows CDN and object-storage cache policy configuration, including TTL and refresh.
💡Core idea:Cache policy balances hit rate and freshness. A TTL that is too short causes frequent origin fetches; one that is too long can serve stale content.

مفتاح التخزين المؤقت (Cache Key): تحديد ما يعتبر "نفس الملف"

كيف تحدد CDN ما إذا كان يجب إعادة نفس النسخة المخزنة مؤقتًا لطلبين؟ يعتمد ذلك على مفتاح التخزين المؤقت.

مفتاح التخزين المؤقت الافتراضي يشمل عادةً:

  • مسار URL (بدون معاملات الاستعلام)
  • مثال: /images/photo.jpg

سيناريو المشكلة:

طلب المستخدم A: /images/photo.jpg?w=100&h=100  (صورة مصغرة 100×100)
طلب المستخدم B: /images/photo.jpg?w=800&h=600  (صورة كبيرة 800×600)

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

الحل: قواعد مفتاح تخزين مؤقت مخصصة

القاعدةمثالالتأثير
الاحتفاظ بمعاملات استعلام محددةالاحتفاظ بـ w، hتخزين مؤقت منفصل لكل حجم
الاحتفاظ بجميع معاملات الاستعلامالاحتفاظ بالكلمطابقة دقيقة تمامًا
تجاهل معاملات استعلام محددةتجاهل token، timestampعناوين URL ذات الطوابع الزمنية يمكنها إصابة الذاكرة المؤقتة
تضمين رؤوس الطلبتضمين Accept-Languageإرجاع محتوى مختلف للغات المختلفة

مثال تكوين عملي (Alibaba Cloud CDN):

قواعد مفتاح التخزين المؤقت:
- مسار URL: /images/*
- معاملات الاستعلام المحتفظ بها: w, h, format
- معاملات الاستعلام المتجاهلة: token, timestamp, utm_source

وقت التخزين المؤقت (TTL): توازن "حداثة" المحتوى

TTL (Time To Live) يحدد مدة بقاء المحتوى مخزنًا مؤقتًا على عقدة CDN. إذا كان قصيرًا جدًا، تكثر العودة إلى المصدر وتزيد التكلفة؛ وإذا كان طويلاً جدًا، يرى المستخدم محتوى قديمًا بعد التحديث.

اقتراحات TTL حسب نوع الملف:

نوع الملفTTL المقترحالسبب
صفحات HTML0-5 دقائقتحديث متكرر للمحتوى، يحتاج إلى وقت حقيقي
ملفات JS/CSSسنة واحدة (مع hash في اسم الملف)المحتوى لا يتغير، يتغير اسم الملف عند تغير المحتوى
الصور/الفيديو7-30 يومًاتردد تحديث منخفض، يمكن تخزينه طويل المدى
ملفات الخطوطسنة واحدةلا تتغير تقريبًا
استجابات API0-5 دقائق (حسب العمل)متطلبات عالية لآنية البيانات

أفضل ممارسات هندسة الواجهة الأمامية بالتزامن مع CDN:

javascript
// إعدادات webpack/vite
output: {
  filename: 'js/[name]-[contenthash:8].js',
  chunkFilename: 'js/[name]-[contenthash:8].chunk.js',
}

اسم الملف المُنشأ: app-a3f2b1c9.js

  • تغير محتوى الملف → تغير hash → عنوان URL جديد → انتهاء صلاحية التخزين المؤقت طبيعيًا
  • عدم تغير محتوى الملف → عدم تغير hash → عدم تغير URL → إصابة تخزين مؤقت طويل المدى

مسح وتسخين الذاكرة المؤقتة

المسح اليدوي (سيناريوهات الطوارئ):

عندما تقوم بتحديث محتوى المصدر، لكن ذاكرة CDN المؤقتة لم تنته صلاحيتها بعد، يرى المستخدم المحتوى القديم:

نوع المسحالتأثيرالوقت المستغرقالسيناريو المناسب
مسح URLإبطال ذاكرة مؤقتة لـ URL محدد5-10 دقائقتحديث ملف واحد
مسح الدليلإبطال كل المحتوى تحت دليل محدد10-30 دقيقةتحديث دفعة
مسح كاملإبطال كل الذاكرة المؤقتة للنطاق بالكامل30 دقيقة فأكثرتراجع طارئ

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

التسخين (تحسين استباقي):

المسح سلبي (تم تحديث المحتوى)، بينما التسخين استباقي (تخزين مؤقت مسبق).

السيناريو: غدًا الساعة 10 صباحًا سيتم نشر مقال رائج

قدم طلب تسخين الليلة:
- URL: https://cdn.example.com/articles/رائج.html
- نطاق التسخين: جميع العقد الطرفية على مستوى الدولة

التأثير:
عند وصول المستخدمين غدًا الساعة 10، المحتوى موجود مسبقًا في العقد الطرفية
→ تأخير عودة إلى المصدر صفري، تجربة فتح فورية

4. جدولة حركة المرور: جعل المستخدم يصل إلى "أقرب" عقدة

🚦Traffic SchedulingUnderstand CDN intelligent scheduling and load balancing.
💡Core idea:Intelligent scheduling combines nearest access, load balancing, and failover to provide global acceleration and high availability.

4.1 جدولة DNS الذكية

تحليل DNS التقليدي:

يسأل المستخدم: ما هو عنوان IP لـ cdn.example.com؟
يجيب DNS: 1.2.3.4 (ثابت)

تحليل DNS الذكي:

يسأل المستخدم (بكين، China Unicom): ما هو عنوان IP لـ cdn.example.com؟
DNS الذكي: دعني أتحقق... عقدة CDN لـ Beijing Unicom هي 1.2.3.4

يسأل المستخدم (شنغهاي، China Telecom): ما هو عنوان IP لـ cdn.example.com؟
DNS الذكي: عقدة CDN لـ Shanghai Telecom هي 5.6.7.8

أبعاد الجدولة:

البعدالوصفالتأثير
الموقع الجغرافيالتوزيع حسب المقاطعة/المدينة/الدولةوصول قريب، تقليل التأخير
مزود الخدمةUnicom/Telecom/Mobile/BGPنقل داخل نفس المزود، تجنب عبور الشبكات
حمل العقدةCPU/عرض النطاق/QPS في الوقت الحقيقيتجنب العقد المثقلة
صحة العقدةفحص التوفراستبعاد العقد المعطلة تلقائيًا
عوامل التكلفةفروق أسعار عرض النطاقموازنة الأداء والتكلفة

4.2 HTTP DNS والاتصال المباشر بـ IP

مشكلة DNS التقليدي: اختطاف DNS وتأخير التحليل.

حل HTTP DNS:

العميل → تجاوز DNS النظام → يسأل خدمة HTTP DNS مباشرة (مثل 223.5.5.5:80)

    يعيد قائمة عناوين IP المثلى (مع أوزان)

    يختار العميل عنوان IP الأمثل بناءً على فحص جودة الشبكة

المزايا:

  • منع الاختطاف: لا يمر عبر DNS مزود الخدمة
  • أكثر دقة: يمكن اختيار IP بناءً على جودة شبكة العميل
  • آنية: تبديل أسرع عند الأعطال

نصائح عملية:

  • تطبيقات الجوال يُنصح بشدة باستخدام HTTP DNS
  • جانب الويب يمكنه استخدام جدولة CNAME المقدمة من CDN
  • الأعمال الحرجة يمكنها عمل تكرار بعناوين IP متعددة (نطاق واحد يعيد عدة عناوين IP)

5. تحسين HTTPS: توازن الأمان والأداء

🔒HTTPS OptimizationUnderstand CDN HTTPS protocol and certificate management.
💡Core idea:HTTPS encrypts traffic with TLS/SSL to prevent man-in-the-middle attacks and data leakage. It is a security baseline for modern web apps.

5.1 لماذا HTTPS مهم على CDN؟

مقارنة السيناريوهات:

بدون HTTPS:
وصول المستخدم إلى http://cdn.example.com/image.jpg

شريط عنوان المتصفح يعرض "غير آمن"

بعض المتصفحات/التطبيقات تمنع الوصول مباشرة

انخفاض ترتيب SEO
مع HTTPS:
وصول المستخدم إلى https://cdn.example.com/image.jpg

المتصفح يعرض أيقونة القفل الأخضر

تفعيل HTTP/2 تعدد الإرسال

تحسين مزدوج في الأداء + الأمان

5.2 نقاط تكوين HTTPS على CDN

إدارة الشهادات

الحلالوصفالتكلفةالسيناريو المناسب
شهادة مجانية من مزود السحابةمقدمة من Alibaba Cloud/Tencent Cloud إلخمجانيةنطاق واحد، بدء سريع
Let's Encryptشهادة مجتمعية مجانيةمجانيةنشر آلي
شهادة تجارية DV/OV/EVSymantec، GeoTrust إلخمئات إلى آلاف اليوانات/سنةمستوى المؤسسات، تحتاج إلى شريط أخضر
شهادة نطاق شامل*.example.comآلاف اليوانات/سنةنطاقات فرعية متعددة

نصائح عملية:

  • بيئة الاختبار: Let's Encrypt أو شهادة مجانية من مزود السحابة
  • بيئة الإنتاج: شهادة نطاق شامل (لتوفير الجهد) أو شهادة OV لنطاق واحد (لتوفير المال)
  • انتبه لوقت انتهاء الشهادة، وعيّن تذكيرات تجديد تلقائي

تكوين تحسين HTTPS

اختيار إصدار TLS:

التكوين الموصى به: TLS 1.2 و TLS 1.3 فقط
تكوين التوافق: TLS 1.1 + TLS 1.2 + TLS 1.3 (لتوافق المتصفحات القديمة)

مجموعات التشفير (Cipher Suites):

موصى به: تبادل مفاتيح ECDHE + تشفير AES-GCM
معطل: DES، RC4، MD5، SHA1

OCSP Stapling:

الوظيفة: عقدة CDN تجلب حالة إبطال الشهادة مسبقًا
التأثير: تقليل وقت تحقق العميل بمقدار 200-500 مللي ثانية
نصيحة: يجب تفعيله

إعادة استخدام جلسة TLS:

إعادة استخدام Session ID: العميل يحمل Session ID السابق، الخادم يستعيد الجلسة
إعادة استخدام Session Ticket: الخادم يرسل حالة الجلسة مشفرة للعميل، يعيدها في المرة القادمة
التأثير: تجنب مصافحة TLS الكاملة، تقليل 1-RTT

5.3 تطبيق HTTP/2 و HTTP/3 على CDN

تعدد إرسال HTTP/2:

HTTP/1.1:
طلب 1 (index.html) ────────────────→
استجابة 1 ←──────────────────────────────
طلب 2 (style.css) ─────────────────→
استجابة 2 ←──────────────────────────────
طلب 3 (script.js) ─────────────────→
استجابة 3 ←──────────────────────────────
(تسلسلي، واحد تلو الآخر)

HTTP/2:
طلب 1 ──→
طلب 2 ──→    مدمجة في اتصال TCP واحد، إطارات متداخلة
طلب 3 ──→
استجابة 1 ←──    إرجاع متدفق حسب الأولوية
استجابة 2 ←──
استجابة 3 ←──
(متوازٍ، اتصال واحد متعدد الإرسال)

دفع الخادم في HTTP/2 (Server Push):

السيناريو: يطلب المستخدم index.html، الذي يشير إلى style.css و script.js

الطريقة التقليدية:
1. ينزل المستخدم index.html
2. يكتشف التحليل الحاجة إلى style.css و script.js
3. يرسل طلبين آخرين للحصول عليهما

دفع HTTP/2:
1. يطلب المستخدم index.html
2. عقدة CDN تعيد index.html وتدفع بنشاط style.css و script.js
3. عندما يحلل المستخدم html، الموارد موجودة مسبقًا في الذاكرة المؤقتة

تنبيه: يجب توخي الحذر في الدفع، الدفع الزائد يهدر عرض النطاق، والقليل لا يحقق التأثير

HTTP/3 (QUIC):

مشكلة HTTP/2: مبني على TCP، حظر رأس الصف (Head-of-Line Blocking)
→ فقدان حزمة TCP واحدة، الاتصال بالكامل ينتظر إعادة الإرسال

حل HTTP/3: مبني على QUIC (نقل موثوق فوق UDP)
→ كل تدفق مستقل، حظر تدفق واحد لا يؤثر على التدفقات الأخرى
→ ترحيل الاتصال: التبديل من WiFi إلى 4G، الاتصال لا ينقطع
→ مصافحة 0-RTT: حتى أول زيارة يمكنها إنشاء اتصال بسرعة

الوضع الحالي: في 2024، CDN الرئيسية تدعم HTTP/3، يُنصح بتفعيله

6. تحليل الوصول: فهم تقارير CDN الخاصة بك

📊Access AnalyticsUnderstand CDN access statistics and log analytics.
💡Core idea:Log analytics shows who accessed which resources and when, helping detect unusual access patterns and security events.

6.1 شرح المؤشرات الأساسية

عرض النطاق (Bandwidth)

التعريف: كمية البيانات المنقولة في وحدة الزمن
الوحدة: bps (بت في الثانية)، Mbps، Gbps

عرض نطاق CDN = مجموع حركة المرور الصادرة من جميع العقد الطرفية

انتبه للتمييز بين:
- عرض النطاق المحتسب: عادةً يُحسب على ذروة 95% أو الذروة اليومية
- عرض النطاق الفعلي: معدل النقل في الوقت الحقيقي

العلاقة بين عرض النطاق وحجم المرور:

1 Mbps عرض نطاق يعمل باستمرار لمدة ساعة واحدة = 450 MB حركة مرور
(الحساب: 1,000,000 bps × 3600s ÷ 8 ÷ 1024 ÷ 1024 ≈ 429 MB)

QPS (Queries Per Second)

التعريف: عدد الاستعلامات/الطلبات في الثانية

CDN QPS = إجمالي طلبات HTTP التي تعالجها جميع العقد الطرفية في الثانية

انتبه: QPS مرتفع لا يعني عرض نطاق مرتفع
- سيناريو الملفات الصغيرة: QPS مرتفع جدًا، عرض النطاق ليس مرتفعًا
- سيناريو الملفات الكبيرة: QPS ليس مرتفعًا، عرض النطاق مرتفع جدًا

معدل الإصاب�� (Hit Ratio)

التعريف: نسبة الطلبات التي تصيب العقد الطرفية لـ CDN من إجمالي الطلبات

صيغة الحساب:
معدل الإصابة = (عدد الإصابات / إجمالي الطلبات) × 100%
أو
معدل الإصابة = (1 - حركة العودة / إجمالي حركة المرور الصادرة) × 100%

المعيار الصناعي:
- الصور/الفيديو/JS/CSS: > 95%
- صفحات HTML: 50-80% (حسب تردد التحديث)
- واجهات API: عادةً لا تُخزن مؤقتًا أو منخفضة جدًا

الأسباب الشائعة لانخفاض معدل الإصابة:

السببالظاهرةالحل
وقت تخزين قصير جدًاTTL بضع دقائق فقطضبط TTL حسب نوع الملف
تغير معاملات الاستعلامURL يحمل رقمًا عشوائيًاتكوين تجاهل معاملات محددة
إعداد مفتاح تخزين غير مناسبتم تمييز ما لا يجب تمييزهتحسين قواعد مفتاح التخزين
تحديث متكرر للمحتوىالملفات تُستبدل كثيرًااستخدام أرقام الإصدارات أو hash في أسماء الملفات
زيارات أولى كثيرةمحتوى جديد أو عقد جديدةتسخين مسبق

6.2 تحليل السجلات واستكشاف المشكلات

تحليل حقول سجل CDN

يحتوي سجل وصول CDN النموذجي على الحقول التالية:

الوقت | IP العميل | طريقة الطلب | URL | رمز حالة HTTP | حجم الاستجابة | حالة التخزين المؤقت | وقت الاستجابة | Referer | User-Agent

مثال:
2024-01-15 14:32:01 | 114.114.114.114 | GET | https://cdn.example.com/images/photo.jpg | 200 | 153600 | HIT | 23 | https://example.com/ | Mozilla/5.0...

شرح الحقول الرئيسية:

الحقلالوصفقيمة التحليل
cache_statusحالة التخزين المؤقتHIT (إصابة)، MISS (غير مصاب)، EXPIRED (منتهي الصلاحية)
response_timeوقت الاستجابة (ms)الحكم على تجربة المستخدم، > 500ms يحتاج تحسين
http_statusرمز حالة HTTPاستكشاف أخطاء 404/500
bytes_sentالبايتات المرسلةإحصاء عرض النطاق

استكشاف المشكلات الشائعة

المشكلة 1: المستخدم يبلغ عن بطء الوصول

خطوات الاستكشاف:

1. انظر إلى response_time في السجل
   - إذا كان كبيرًا (> 500ms): تحقق مما إذا كانت الذاكرة المؤقتة MISS أو المصدر بطيئًا

2. تحقق من cache_status
   - HIT: إصابة في الذاكرة المؤقتة، البطء قد يكون بسبب حجم الملف الكبير أو مشكلة في العقدة
   - MISS: غير مصاب، تحتاج إلى تحسين استراتيجية التخزين المؤقت أو معدل الإصابة

3. تحقق من توزيع IP العملاء
   - مناطق معينة بطيئة: قد تكون العقدة مثقلة أو التغطية غير كافية

المشكلة 2: التخزين المؤقت لا يعمل، العودة إلى المصدر في كل مرة

قائمة التحقق:

□ هل رأس استجابة المصدر يحتوي على Cache-Control: no-cache / private؟
□ هل URL يحمل معاملات عشوائية (مثل ?_=123456)؟
□ هل تكوين مفتاح التخزين المؤقت صحيح؟
□ هل إعداد TTL قصير جدًا؟
□ هل تمت الإصابة في الذاكرة المؤقتة المحلية للمتصفح بدلاً من CDN؟

المشكلة 3: ارتفاع مفاجئ في التكاليف

اتجاه الاستكشاف:

1. انظر إلى تفاصيل الفاتورة
   - رسوم حركة CDN مرتفعة: تحقق مما إذا كانت هناك ملفات كبيرة يتم الوصول إليها بشكل متكرر، أو سرقة روابط
   - رسوم حركة العودة مرتفعة: تحقق مما إذا انخفض معدل الإصابة فجأة
   - رسوم عدد الطلبات مرتفعة: تحقق من وجود هجوم CC أو برامج زحف

2. انظر إلى سجل الوصول
   - هل هناك عدد كبير من طلبات 404 (قد يكون فحصًا أو خطأ في التكوين)
   - هل Referer غير طبيعي (لتحديد سرقة الروابط)

3. إعدادات الأمان
   - تفعيل منع سرقة الروابط (القائمة البيضاء لـ Referer)
   - تفعيل القائمة السوداء/البيضاء لـ IP
   - تكوين حماية CC

7. حالة عملية: بناء حل تسريع الصور من الصفر

7.1 سيناريو العمل

افترض أنك المسؤول التقني لمجتمع صور، وتواجه التحديات التالية:

  • رفع المستخدم: يرفع المستخدمون مليون صورة يوميًا (متوسط 2 ميجابايت/صورة)
  • وصول المستخدم: 50 مليون طلب مشاهدة صور يوميًا
  • توزيع الوصول: المستخدمون منتشرون في جميع أنحاء البلاد، مع وصول محدود من الخارج
  • متطلبات الأداء: وقت تحميل الصورة < 500 مللي ثانية
  • ميزانية التكلفة: محاولة البقاء ضمن 50,000 يوان شهريًا

7.2 تصميم البنية

                         ┌──────────────────────────────────────┐
                         │           عملية رفع المستخدم                  │
                         └──────────────────────────────────────┘

   متصفح المستخدم                                     الخدمة الخلفية                      التخزين الكائني
       │                                            │                            │
       │  1. طلب بيانات اعتماد الرفع                            │                            │
       │───────────────────────────────────────────>│                            │
       │                                            │                            │
       │                                            │  2. طلب بيانات اعتماد STS المؤقتة        │
       │                                            │───────────────────────────>│
       │                                            │                            │
       │                                            │  3. إعادة بيانات اعتماد STS          │
       │                                            │<───────────────────────────│
       │                                            │                            │
       │  4. إعادة بيانات اعتماد الرفع (متضمنة STS)                  │
       │<───────────────────────────────────────────│                            │
       │                                            │                            │
       │  5. رفع الملف مباشرة (باستخدام توقيع STS)          │
       │──────────────────────────────────────────────────────────────────────>│
       │                                            │                            │
       │  6. إعادة نتيجة الرفع (URL، ETag إلخ)           │
       │<──────────────────────────────────────────────────────────────────────│
       │                                            │                            │
       │  7. إعلام الخادم الخلفي باكتمال الرفع (حفظ في قاعدة البيانات)        │
       │───────────────────────────────────────────>│                            │


                         ┌──────────────────────────────────────┐
                         │           عملية وصول المستخدم                  │
                         └──────────────────────────────────────┘

   متصفح المستخدم              تحليل DNS              عقدة CDN               التخزين الكائني (المصدر)
       │                     │                     │                     │
       │  1. طلب URL الصورة    │                     │                     │
       │────────────────────────────────────────>│                     │
       │                     │                     │                     │
       │                     │  2. استعلام DNS        │                     │
       │                     │────────────────────>│                     │
       │                     │                     │                     │
       │                     │  3. إعادة IP العقدة المثلى │                     │
       │                     │<────────────────────│                     │
       │                     │                     │                     │
       │  4. الاتصال بعقدة CDN   │                     │                     │
       │────────────────────────────────────────>│                     │
       │                     │                     │                     │
       │                     │  5. فحص الذاكرة المؤقتة        │                     │
       │                     │                     ├─ إصابة؟ إعادة مباشرة     │
       │                     │                     └─ غير مصابة؟ متابعة        │
       │                     │                     │                     │
       │                     │                     │  6. العودة للمصدر       │
       │                     │                     │──────────────────>│
       │                     │                     │                     │
       │                     │                     │  7. إعادة الملف       │
       │                     │                     │<──────────────────│
       │                     │                     │                     │
       │                     │  8. تخزين مؤقت واستجابة      │                     │
       │<────────────────────────────────────────│                     │

7.3 شرح التكوينات الرئيسية

تكوين التخزين الكائني

تخطيط حاوية التخزين:

 Bucket: myapp-images-prod
 ├─ هيكل الدليل:
 │   ├─ uploads/           # الصور الأصلية التي يرفعها المستخدم
 │   │   ├─ 2024/01/15/user123-abc.jpg
 │   │   └─ 2024/01/15/user456-def.png
 │   ├─ thumbnails/        # الصور المصغرة
 │   │   ├─ small/         # 100x100
 │   │   ├─ medium/        # 400x300
 │   │   └─ large/         # 800x600
 │   └─ processed/         # الصور المعالجة (مع علامة مائية إلخ)

 ├─ صلاحيات الوصول:
 │   ├─ دليل الصور الأصلية: خاص (يحتاج وصولًا موقعًا)
 │   ├─ دليل الصور المصغرة: قراءة عامة
 │   └─ CORS عبر النطاقات: السماح لـ *.myapp.com بالوصول

 └─ سياسة دورة الحياة:
     ├─ بعد 7 أيام من الرفع: تخزين منخفض التردد (توفير 40% من التكلفة)
     ├─ بعد 90 يومًا من الرفع: تخزين أرشيفي (توفير 70% من التكلفة)
     └─ بعد 3 سنوات من الرفع: حذف تلقائي (أو نقل إلى تخزين بارد أرخص)

تكوين CORS عبر النطاقات:

xml
<CORSConfiguration>
  <CORSRule>
    <AllowedOrigin>https://myapp.com</AllowedOrigin>
    <AllowedOrigin>https://www.myapp.com</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
    <ExposeHeader>ETag</ExposeHeader>
    <ExposeHeader>x-oss-request-id</ExposeHeader>
    <MaxAgeSeconds>3600</MaxAgeSeconds>
  </CORSRule>
</CORSConfiguration>

تكوين تسريع CDN

تكوين استراتيجية التخزين المؤقت:

القاعدة الافتراضية العامة:
├─ مفتاح التخزين المؤقت: مسار URL + الاحتفاظ بمعاملات w، h، format
├─ TTL الافتراضي: 7 أيام
└─ مضيف العودة: متابعة تلقائية

تقسيم حسب نوع الملف:
├─ *.html:
│   ├─ TTL: 5 دقائق
│   └─ أولوية القراءة من ذاكرة التخزين المؤقت

├─ *.js, *.css:
│   ├─ TTL: سنة واحدة
│   └─ تجاهل معاملات الاستعلام (لأن اسم الملف يحتوي على hash)

├─ *.jpg, *.png, *.gif, *.webp:
│   ├─ TTL: 30 يومًا
│   ├─ الاحتفاظ بمعاملات الاستعلام (w، h، format للقص الديناميكي)
│   └─ تفعيل تحسين ضغط الصور التلقائي

└─ /api/*:
    ├─ TTL: 0 (بدون تخزين مؤقت)
    └─ عودة مباشرة للمصدر

تكوين تحسين HTTPS:

تكوين الشهادة:
├─ نوع الشهادة: شهادة نطاق شامل *.myapp.com
├─ طريقة النشر: رفع في لوحة تحكم CDN، تجديد تلقائي
└─ شهادة احتياطية: شهادة EV للنطاق الرئيسي (عرض شريط العنوان الأخضر)

تكوين TLS:
├─ أدنى إصدار TLS: 1.2 (توازن التوافق والأمان)
├─ أعلى إصدار TLS: 1.3
├─ مجموعات التشفير: تفعيل مجموعات التشفير القوية فقط
├─ OCSP Stapling: مفعّل
├─ إعادة استخدام جلسة TLS: تفعيل Session Ticket
└─ HSTS: مفعّل (max-age=31536000)

HTTP/2 و HTTP/3:
├─ HTTP/2: مفعّل (تعدد الإرسال، ضغط الرؤوس)
├─ HTTP/2 Server Push: تفعيل حسب الحاجة (يُنصح باستخدام Preload كبديل)
└─ HTTP/3 (QUIC): مفعّل (ميزة تجريبية، زيادة تدريجية)

7.4 استراتيجية التحكم في التكاليف

تحليل مكونات التكلفة

مكونات التكلفة الشهرية لـ CDN + التخزين الكائني:

جزء CDN:
├─ رسوم حركة المرور الصادرة (الجزء الأكبر، حوالي 60%)
│   ├─ الصين الرئيسية: 0.15-0.30 يوان/جيجابايت
│   ├─ منطقة آسيا والمحيط الهادئ: 0.40-0.80 يوان/جيجابايت
│   └─ أوروبا وأمريكا: 0.30-0.60 يوان/جيجابايت

├─ رسوم عدد الطلبات (جزء صغير، حوالي 5%)
│   ├─ HTTP: 0.01-0.05 يوان/10,000 مرة
│   └─ HTTPS: 0.05-0.15 يوان/10,000 مرة (لأن مصافحة TLS تستهلك موارد)

├─ رسوم ذروة عرض النطاق (طريقة فوترة اختيارية)
│   └─ فوترة ذروة 95%: مناسبة للسيناريوهات ذات التقلب الكبير في حركة المرور

└─ رسوم الميزات الإضافية (حوالي 5%)
    ├─ إدارة شهادات HTTPS
    ├─ حماية WAF
    ├─ دفع السجلات في الوقت الحقيقي
    └─ النصوص/الدوال الطرفية

جزء التخزين الكائني:
├─ رسوم سعة التخزين (حوالي 15%)
│   ├─ تخزين قياسي: 0.12-0.15 يوان/جيجابايت/شهر
│   ├─ تخزين منخفض التردد: 0.08-0.10 يوان/جيجابايت/شهر
│   └─ تخزين أرشيفي: 0.03-0.05 يوان/جيجابايت/شهر

├─ رسوم الطلبات (حوالي 5%)
│   ├─ PUT: 0.01-0.05 يوان/10,000 مرة
│   └─ GET: 0.005-0.01 يوان/10,000 مرة

├─ رسوم استرجاع البيانات (منخفض التردد/الأرشيفي)
│   └─ رسوم إضافية على الحذف المبكر أو الاسترجاع

└─ رسوم حركة العودة للمصدر (حوالي 10%)
    └─ رسوم حركة مرور عودة CDN إلى التخزين الكائني

نصائح عملية لتوفير التكاليف

نصيحة 1: تقسيم التخزين، إدارة دورة الحياة التلقائية

yaml
# مثال قواعد دورة الحياة
rules:
  - id: image-lifecycle
    prefix: uploads/
    transitions:
      # بعد 7 أيام، نقل إلى تخزين منخفض التردد، توفير 30% من التكلفة
      - days: 7
        storageClass: IA
      # بعد 90 يومًا، نقل إلى تخزين أرشيفي، توفير 70% من التكلفة
      - days: 90
        storageClass: Archive
    # حذف تلقائي بعد 3 سنوات
    expiration:
      days: 1095

نصيحة 2: تحسين معدل إصابة CDN، تقليل العودة إلى المصدر

ماذا يعني رفع معدل الإصابة من 90% إلى 95%؟

بافتراض:
- حركة المرور اليومية: 10 TB
- معدل الإصابة 90%: العودة 1 TB
- معدل الإصابة 95%: العودة 0.5 TB

توفير حركة العودة: 0.5 TB/يوم × 0.15 يوان/GB × 30 يومًا = 2,250 يوان/شهر

نصيحة 3: الضغط وتحسين التنسيق

خطة تحسين الصور:
├─ تخزين الصور الأصلية في التخزين الكائني (غير متاحة مباشرة للخارج)
├─ تفعيل ميزة معالجة الصور في CDN:
│   ├─ تحويل تلقائي للتنسيق: JPEG → WebP/AVIF (توفير 30-50%)
│   ├─ ضغط تلقائي للجودة: ضغط بلا فقدان بصري (توفير 20-40%)
│   ├─ تكيف الأبعاد: إرجاع الحجم المناسب حسب الجهاز
│   └─ تحميل تدريجي: ضبابي ثم واضح
└─ التأثير: خفض تكلفة عرض النطاق بنسبة 50-70%

نصيحة 4: تحديد سقف عرض النطاق والتنبيهات

yaml
# تكوين سقف عرض النطاق
bandwidth_cap:
  daily_limit: 500 # Mbps، إذا تجاوزت الذروة اليومية، إيقاف CDN تلقائيًا
  monthly_limit: 10000 # GB، إذا تجاوزت حركة المرور الشهرية، إيقاف

  # عتبات التنبيه
  alerts:
    - threshold: 70% # عند الوصول إلى 70%، إرسال تنبيه
      channels: [sms, email]
    - threshold: 90% # عند الوصول إلى 90%، اتصال هاتفي
      channels: [phone]

8. الخلاصة: القواعد الذهبية للتخزين الكائني + CDN

8.1 مبادئ تصميم البنية

المبدأ 1: فصل المحتوى الديناميكي عن الثابت

المحتوى الديناميكي (API، HTML) → عبر الخادم المصدر أو الدوال الطرفية
المحتوى الثابت (الصور، JS، CSS، الفيديو) → عبر CDN + التخزين الكائني

المبدأ 2: الخدمة من أقرب نقطة

أينما كان المستخدم، يُخزن المحتوى مؤقتًا هناك
→ اختر مزود CDN بتغطية واسعة
→ فعّل جدولة DNS الذكية
→ سخّن المحتوى المهم مسبقًا

المبدأ 3: التخزين المؤقت متعدد الطبقات

ذاكرة المتصفح المحلية المؤقتة (الأقوى)

ذاكرة عقدة CDN الطرفية المؤقتة (ثاني أقوى)

طبقة CDN الوسيطة/العقد الإقليمية (شبكة أمان)

التخزين الكائني/الخادم المصدر (خط الدفاع الأخير)

المبدأ 4: توازن التكلفة والتجربة

تقسيم التخزين: البيانات الساخنة تخزين قياسي، البيانات الباردة تخزين أرشيفي
استراتيجية التخزين المؤقت: محتوى عالي التردد TTL طويل، محتوى منخفض التردد TTL قصير
تحسين الضغط: تنسيق WebP/AVIF، ضغط ذكي للجودة
المراقبة والتنبيه: تعيين سقف عرض النطاق، منع حركة المرور غير الطبيعية

8.2 قائمة تجنب العقبات

تسمية الحاويات والصلاحيات

  • [ ] اسم الحاوية فريد عالميًا، تجنب أن يكون محجوزًا
  • [ ] عدم تعيين الملفات الخاصة للقراءة العامة
  • [ ] عدم كتابة AccessKey في كود الواجهة الأمامية، استخدم بيانات اعتماد STS المؤقتة
  • [ ] تفعيل التشفير من جانب الخادم (SSE) لحماية البيانات الحساسة

تكوين ذاكرة CDN المؤقتة

  • [ ] TTL ملفات HTML لا يجب أن يكون طويلاً جدًا (يُنصح < 5 دقائق)
  • [ ] JS/CSS يُنصح باستخدام أسماء ملفات مع hash، وتعيين TTL لسنة واحدة
  • [ ] مفتاح التخزين المؤقت يجب أن يكون معقولاً، لا تضع معلومات المستخدم كمتغيرات فيه
  • [ ] بعد التحديثات المهمة، تذكر مسح الذاكرة المؤقتة أو التسخين

أمان HTTPS

  • [ ] عدم انتهاء صلاحية الشهادة، تعيين تجديد تلقائي
  • [ ] أدنى إصدار TLS موصى به هو 1.2
  • [ ] تفعيل HSTS لمنع هجمات التخفيض
  • [ ] تعيين Secure و HttpOnly لملفات تعريف الارتباط الحساسة

التحكم في التكاليف

  • [ ] تفعيل تنبيه سقف عرض النطاق، منع حركة المرور غير الطبيعية
  • [ ] التخزين منخفض التردد/الأرشيفي له حد أدنى لوقت التخزين ورسوم حذف مبكر، انتبه للقواعد
  • [ ] رسوم حركة العودة مرتفعة أيضًا، اجتهد في تحسين معدل إصابة CDN
  • [ ] تحليل سجلات الوصول بانتظام، تنظيف الموارد غير المستخدمة

9. قوالب كود عملية

9.1 الرفع المباشر من الواجهة الأمامية إلى التخزين الكائني (JavaScript)

javascript
/**
 * فئة أداة الرفع المباشر للتخزين الكائني
 * الدعم: Alibaba Cloud OSS، Tencent Cloud COS، AWS S3
 */
class DirectUploader {
  constructor(config) {
    this.provider = config.provider // 'oss' | 'cos' | 's3'
    this.region = config.region
    this.bucket = config.bucket
    this.getCredentials = config.getCredentials // دالة للحصول على بيانات الاعتماد المؤقتة
  }

  /**
   * الحصول على بيانات اعتماد STS المؤقتة
   */
  async fetchCredentials() {
    // طلب بيانات اعتماد مؤقتة من الخادم الخلفي
    const credentials = await this.getCredentials()
    return {
      accessKeyId: credentials.accessKeyId,
      accessKeySecret: credentials.accessKeySecret,
      sessionToken: credentials.securityToken || credentials.sessionToken,
      expiration: credentials.expiration
    }
  }

  /**
   * إنشاء توقيع الرفع (للحساب من جانب الواجهة الأمامية)
   */
  generateSignature(credentials, fileKey, fileType, options = {}) {
    const timestamp = new Date().toISOString()
    const date = timestamp.slice(0, 10).replace(/-/g, '')

    // خوارزميات التوقيع تختلف قليلاً بين المزودين
    switch (this.provider) {
      case 'oss':
        return this._ossSignature(credentials, fileKey, date, options)
      case 'cos':
        return this._cosSignature(credentials, fileKey, date, options)
      case 's3':
        return this._s3Signature(credentials, fileKey, date, options)
      default:
        throw new Error('Unknown provider')
    }
  }

  /**
   * رفع ملف واحد (ملفات صغيرة < 100 ميجابايت)
   */
  async upload(file, options = {}) {
    const credentials = await this.fetchCredentials()
    const fileKey = this._generateFileKey(file, options.directory)

    const formData = new FormData()

    // بناء حقول النموذج (تختلف أسماء الحقول بين المزودين)
    const formFields = this._buildFormFields(
      credentials,
      fileKey,
      file.type,
      options
    )
    Object.entries(formFields).forEach(([key, value]) => {
      formData.append(key, value)
    })

    formData.append('file', file)

    // إرسال طلب الرفع
    const uploadUrl = this._getUploadUrl()
    const response = await fetch(uploadUrl, {
      method: 'POST',
      body: formData,
      // إذا كان الملف كبيرًا، قد تحتاج إلى تعيين مهلة أطول
      signal: options.signal // دعم AbortController لإلغاء الرفع
    })

    if (!response.ok) {
      const errorText = await response.text()
      throw new Error(`Upload failed: ${response.status} ${errorText}`)
    }

    return {
      url: this._getFileUrl(fileKey),
      key: fileKey,
      etag: response.headers.get('ETag'),
      size: file.size
    }
  }

  /**
   * رفع مجزأ (ملفات كبيرة > 100 ميجابايت)
   */
  async multipartUpload(file, options = {}) {
    const partSize = options.partSize || 10 * 1024 * 1024 // افتراضي 10 ميجابايت/جزء
    const parallel = options.parallel || 3 // افتراضي 3 تزامن

    const credentials = await this.fetchCredentials()
    const fileKey = this._generateFileKey(file, options.directory)

    // 1. تهيئة الرفع المجزأ
    const uploadId = await this._initMultipartUpload(
      credentials,
      fileKey,
      file.type
    )

    // 2. حساب الأجزاء
    const parts = []
    const totalParts = Math.ceil(file.size / partSize)
    for (let i = 0; i < totalParts; i++) {
      const start = i * partSize
      const end = Math.min(start + partSize, file.size)
      parts.push({
        number: i + 1,
        start,
        end,
        blob: file.slice(start, end)
      })
    }

    // 3. رفع الأجزاء (مع تحكم في التزامن واستئناف بعد الانقطاع)
    const uploadedParts = []
    const failedParts = []

    // دعم استئناف الرفع: التحقق من الأجزاء التي تم رفعها
    if (options.resume) {
      const existingParts = await this._listParts(
        credentials,
        fileKey,
        uploadId
      )
      for (const part of existingParts) {
        uploadedParts.push(part)
      }
    }

    // تصفية الأجزاء غير المرفوعة
    const pendingParts = parts.filter(
      (p) => !uploadedParts.some((up) => up.partNumber === p.number)
    )

    // رفع متزامن
    const uploadPart = async (part) => {
      try {
        const etag = await this._uploadPart(
          credentials,
          fileKey,
          uploadId,
          part
        )
        return { partNumber: part.number, etag }
      } catch (error) {
        failedParts.push({ part, error })
        throw error
      }
    }

    // استخدام Promise.all للتحكم في التزامن
    const chunks = []
    for (let i = 0; i < pendingParts.length; i += parallel) {
      chunks.push(pendingParts.slice(i, i + parallel))
    }

    for (const chunk of chunks) {
      const results = await Promise.allSettled(chunk.map(uploadPart))
      for (const result of results) {
        if (result.status === 'fulfilled') {
          uploadedParts.push(result.value)
        }
      }
    }

    // التحقق من نجاح رفع جميع الأجزاء
    if (uploadedParts.length !== totalParts) {
      throw new Error(
        `Upload incomplete: ${uploadedParts.length}/${totalParts} parts uploaded`
      )
    }

    // 4. إكمال الرفع المجزأ (دمج الأجزاء)
    await this._completeMultipartUpload(
      credentials,
      fileKey,
      uploadId,
      uploadedParts
    )

    return {
      url: this._getFileUrl(fileKey),
      key: fileKey,
      size: file.size,
      parts: totalParts
    }
  }

  /**
   * إنشاء مسار تخزين الملف
   */
  _generateFileKey(file, directory = '') {
    const date = new Date()
    const datePath = `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')}`
    const random = Math.random().toString(36).substring(2, 10)
    const ext = file.name.split('.').pop() || 'bin'
    const key = directory
      ? `${directory}/${datePath}/${random}.${ext}`
      : `${datePath}/${random}.${ext}`
    return key
  }

  // ============ طرق خاصة بكل مزود ============

  _getUploadUrl() {
    switch (this.provider) {
      case 'oss':
        return `https://${this.bucket}.oss-${this.region}.aliyuncs.com`
      case 'cos':
        return `https://${this.bucket}.cos.${this.region}.myqcloud.com`
      case 's3':
        return `https://${this.bucket}.s3.${this.region}.amazonaws.com`
      default:
        throw new Error('Unknown provider')
    }
  }

  _getFileUrl(key) {
    return `https://${this.bucket}.${this.provider === 'oss' ? 'oss' : 'cos'}-${this.region}.${
      this.provider === 'oss'
        ? 'aliyuncs.com'
        : this.provider === 'cos'
          ? 'myqcloud.com'
          : 'amazonaws.com'
    }/${key}`
  }

  // طرق التوقيع والرفع المجزأ لكل مزود... (نفذ حسب الاحتياج الفعلي)
  _buildFormFields(credentials, fileKey, fileType, options) {
    // منطق بناء حقول النموذج لكل مزود
    // يحتاج إلى التنفيذ حسب توثيق المزود المحدد
    return {}
  }

  async _initMultipartUpload(credentials, fileKey, fileType) {
    // منطق تهيئة الرفع المجزأ لكل مزود
    return 'upload-id'
  }

  async _uploadPart(credentials, fileKey, uploadId, part) {
    // منطق رفع الأجزاء لكل مزود
    return 'etag'
  }

  async _completeMultipartUpload(credentials, fileKey, uploadId, parts) {
    // منطق إكمال الرفع المجزأ لكل مزود
  }

  async _listParts(credentials, fileKey, uploadId) {
    // منطق سرد الأجزاء المرفوعة لكل مزود
    return []
  }
}

// مثال استخدام
const uploader = new DirectUploader({
  provider: 'oss',
  region: 'cn-beijing',
  bucket: 'myapp-images-prod',
  getCredentials: async () => {
    // طلب بيانات اعتماد مؤقتة من الخادم الخلفي
    const res = await fetch('/api/upload/credentials')
    return res.json()
  }
})

// رفع ملف صغير
async function uploadAvatar(file) {
  try {
    const result = await uploader.upload(file, {
      directory: 'avatars',
      onProgress: (progress) => {
        console.log(`تقدم الرفع: ${progress.percent}%`)
      }
    })
    console.log('نجاح الرفع:', result.url)
    return result
  } catch (error) {
    console.error('فشل الرفع:', error)
    throw error
  }
}

// رفع ملف كبير مجزأ
async function uploadVideo(file) {
  try {
    const result = await uploader.multipartUpload(file, {
      directory: 'videos',
      partSize: 10 * 1024 * 1024, // 10 ميجابايت لكل جزء
      parallel: 3, // 3 تزامن
      resume: true, // دعم استئناف الرفع بعد الانقطاع
      onProgress: (progress) => {
        console.log(
          `تقدم الرفع: ${progress.percent}%, تم رفع ${progress.loaded}/${progress.total}`
        )
      },
      onPartComplete: (part) => {
        console.log(`اكتمل رفع الجزء ${part.number}`)
      }
    })
    console.log('نجاح الرفع:', result.url)
    return result
  } catch (error) {
    console.error('فشل الرفع:', error)
    // يمكن تنفيذ منطق إعادة المحاولة أو حفظ معلومات نقطة الانقطاع هنا
    throw error
  }
}

9.2 خدمة بيانات الاعتماد المؤقتة في الخادم الخلفي (Node.js/Express)

javascript
/**
 * خدمة بيانات اعتماد STS المؤقتة للتخزين الكائني
 * الدعم: Alibaba Cloud OSS، Tencent Cloud COS، AWS S3
 */
const express = require('express')
const STS = require('ali-oss').STS // Alibaba Cloud
// const COS = require('cos-nodejs-sdk-v5') // Tencent Cloud
const router = express.Router()

// التكوين
const config = {
  // تكوين Alibaba Cloud OSS
  oss: {
    accessKeyId: process.env.OSS_ACCESS_KEY_ID,
    accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
    region: 'oss-cn-beijing',
    bucket: 'myapp-images-prod',
    // دور STS ARN (يحتاج إلى إنشاء في لوحة تحكم RAM)
    roleArn: process.env.OSS_STS_ROLE_ARN
  }
}

/**
 * الحصول على بيانات اعتماد STS المؤقتة (Alibaba Cloud OSS)
 * POST /api/upload/credentials
 */
router.post('/credentials', async (req, res) => {
  try {
    // 1. التحقق من هوية المستخدم (نفذ حسب الحالة الفعلية)
    const userId = req.user?.id
    if (!userId) {
      return res.status(401).json({ error: 'Unauthorized' })
    }

    // 2. إنشاء بادئة فريدة لمسار الملف (لعزل الصلاحيات)
    const date = new Date()
    const prefix = `uploads/${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}/${userId}/`

    // 3. إنشاء عميل STS
    const sts = new STS({
      accessKeyId: config.oss.accessKeyId,
      accessKeySecret: config.oss.accessKeySecret
    })

    // 4. طلب بيانات اعتماد مؤقتة
    const result = await sts.assumeRole(
      config.oss.roleArn,
      {
        // تقييد نطاق الصلاحيات بالسياسة (مبدأ الحد الأدنى من الصلاحيات)
        Statement: [
          {
            Effect: 'Allow',
            Action: [
              'oss:PutObject',
              'oss:InitiateMultipartUpload',
              'oss:UploadPart',
              'oss:CompleteMultipartUpload',
              'oss:AbortMultipartUpload',
              'oss:ListParts'
            ],
            Resource: [`acs:oss:*:*:${config.oss.bucket}/${prefix}*`]
          }
        ],
        Version: '1'
      },
      3600, // صلاحية بيانات الاعتماد ساعة واحدة
      'web-upload-session-' + Date.now()
    )

    // 5. إعادة بيانات الاعتماد والتكوين
    res.json({
      success: true,
      data: {
        // بيانات اعتماد STS المؤقتة
        credentials: {
          accessKeyId: result.credentials.AccessKeyId,
          accessKeySecret: result.credentials.AccessKeySecret,
          sessionToken: result.credentials.SecurityToken,
          expiration: result.credentials.Expiration
        },
        // تكوين الرفع
        config: {
          provider: 'oss',
          region: config.oss.region,
          bucket: config.oss.bucket,
          endpoint: `https://${config.oss.bucket}.${config.oss.region}.aliyuncs.com`,
          prefix: prefix, // بادئة مسار الملف
          // قيود الأمان
          maxSize: 100 * 1024 * 1024, // الحد الأقصى 100 ميجابايت
          allowedTypes: [
            'image/jpeg',
            'image/png',
            'image/gif',
            'image/webp',
            'video/mp4'
          ]
        }
      }
    })
  } catch (error) {
    console.error('Get credentials failed:', error)
    res.status(500).json({
      success: false,
      error: 'Failed to get upload credentials',
      message: error.message
    })
  }
})

/**
 * إشعار معاودة الاتصال: بعد اكتمال رفع الواجهة الأمامية، إعلام الخادم الخلفي
 * POST /api/upload/callback
 */
router.post('/callback', async (req, res) => {
  try {
    const { key, etag, size, mimeType, originalName } = req.body
    const userId = req.user?.id

    // 1. التحقق من وجود الملف
    // 2. حفظ معلومات الملف في قاعدة البيانات
    const fileRecord = await db.files.create({
      userId,
      key,
      etag,
      size,
      mimeType,
      originalName,
      url: `https://cdn.example.com/${key}`,
      createdAt: new Date()
    })

    // 3. معالجة غير متزامنة: إنشاء صور مصغرة، استخراج البيانات الوصفية، مراجعة المحتوى إلخ
    await processFileAsync(fileRecord)

    res.json({
      success: true,
      data: {
        fileId: fileRecord.id,
        url: fileRecord.url,
        size: fileRecord.size
      }
    })
  } catch (error) {
    console.error('Upload callback failed:', error)
    res.status(500).json({
      success: false,
      error: 'Failed to process uploaded file'
    })
  }
})

module.exports = router

9.3 منع سرقة الروابط وتكوين الأمان

javascript
/**
 * مثال تكوين منع سرقة الروابط والأمان لـ CDN
 */

// 1. منع سرقة الروابط بـ Referer (منع المواقع الأخرى من الإشارة مباشرة إلى مواردك)
const refererConfig = {
  // نمط القائمة البيضاء: السماح فقط لـ Referer التالية بالوصول
  allowList: [
    '*.myapp.com', // الموقع الرئيسي
    '*.myapp.cn', // الموقع المحلي
    'localhost:*', // التطوير المحلي
    '127.0.0.1:*'
  ],

  // نمط القائمة السوداء (اختياري): حظر Referer التالية
  blockList: [
    '*.competitor.com', // المنافسون
    'spam-site.com'
  ],

  // معالجة Referer الفارغ: هل يُسمح بالوصول المباشر (كتابة URL في شريط عنوان المتصفح)
  allowEmptyReferer: false // بيئة الإنتاج يُنصح false، بيئة الاختبار يمكن true
}

// 2. توثيق URL (منع سرقة روابط أكثر أمانًا، مع طابع زمني وتوقيع)
class URLAuth {
  constructor(config) {
    this.key = config.key // مفتاح التوثيق، يُحفظ فقط في جانب الخادم
    this.expireTime = config.expireTime || 3600 // افتراضي ساعة واحدة صلاحية
  }

  /**
   * إنشاء URL مع توثيق
   * @param {string} url - URL الأصلي، مثل https://cdn.example.com/images/photo.jpg
   * @param {number} expireIn - مدة الصلاحية (بالثواني)
   * @returns {string} URL مع معاملات التوثيق
   */
  sign(url, expireIn = this.expireTime) {
    const urlObj = new URL(url)
    const pathname = urlObj.pathname
    const timestamp = Math.floor(Date.now() / 1000) + expireIn

    // بناء سلسلة التوقيع (تختلف الصيغة بين المزودين، هذا مثال عام)
    const signStr = `${pathname}-${timestamp}-${this.key}`
    const signature = this._md5(signStr)

    // إضافة معاملات التوثيق
    urlObj.searchParams.set('sign', signature)
    urlObj.searchParams.set('t', timestamp.toString())

    return urlObj.toString()
  }

  /**
   * التحقق من توقيع URL (للاستخدام في طرف CDN أو الخادم المصدر)
   */
  verify(url) {
    const urlObj = new URL(url)
    const signature = urlObj.searchParams.get('sign')
    const timestamp = parseInt(urlObj.searchParams.get('t'))
    const pathname = urlObj.pathname

    // التحقق من انتهاء الصلاحية
    if (timestamp < Math.floor(Date.now() / 1000)) {
      return { valid: false, error: 'URL expired' }
    }

    // التحقق من التوقيع
    const signStr = `${pathname}-${timestamp}-${this.key}`
    const expectedSign = this._md5(signStr)

    if (signature !== expectedSign) {
      return { valid: false, error: 'Invalid signature' }
    }

    return { valid: true }
  }

  _md5(str) {
    // في المشاريع الفعلية، استخدم crypto-js أو مكتبات MD5 أخرى
    // هذا مثال فقط
    return require('crypto').createHash('md5').update(str).digest('hex')
  }
}

// مثال استخدام
const auth = new URLAuth({
  key: 'your-secret-key-only-known-by-server',
  expireTime: 3600 // ساعة واحدة صلاحية
})

// جانب الخادم ينشئ URL موقعًا
const signedUrl = auth.sign(
  'https://cdn.example.com/private/document.pdf',
  7200
)
// النتيجة: https://cdn.example.com/private/document.pdf?sign=xxxxx&t=1699123456

// طرف CDN أو الخادم المصدر يتحقق
const result = auth.verify(signedUrl)
if (!result.valid) {
  // إرجاع 403 Forbidden
}

// 3. القائمة البيضاء/السوداء لعناوين IP
const ipConfig = {
  // السماح فقط لعناوين IP محددة (مناسب للأنظمة الداخلية)
  whiteList: [
    '192.168.1.0/24', // نطاق الشبكة الداخلية
    '10.0.0.0/8'
  ],

  // حظر عناوين IP محددة (حظر المهاجمين)
  blackList: ['1.2.3.4', '5.6.7.8']
}

// 4. القائمة البيضاء/السوداء لـ UA (User-Agent)
const uaConfig = {
  // حظر برامج الزحف/أدوات التنزيل
  blackList: [
    'Wget',
    'curl',
    'python-requests',
    'Scrapy',
    'AhrefsBot',
    'SemrushBot'
  ],

  // السماح فقط للمتصفحات (نمط صارم)
  whiteList: [
    'Mozilla/*', // المتصفحات الحديثة
    'AppleWebKit/*'
  ]
}

10. جدول المصطلحات

المصطلح الإنجليزيالمقابل العربيالشرح
Object Storageالتخزين الكائنيبنية تخزين بيانات تدير البيانات ككائنات، بدلاً من بنية نظام الملفات الهرمي. مناسبة لتخزين الصور والفيديو والنسخ الاحتياطي والبيانات غير المنظمة.
Bucketحاوية التخزينالحاوية العليا في التخزين الكائني، تُستخدم لتنظيم وعزل البيانات. لكل حاوية تحكم مستقل في الصلاحيات والتكوين.
Objectكائن/كائن ملفالوحدة الأساسية في التخزين الكائني، تحتوي على البيانات نفسها والبيانات الوصفية (Metadata) ومفتاح فريد عالمي (Key).
CDNشبكة توصيل المحتوىContent Delivery Network، من خلال نشر عقد طرفية حول العالم، تخزين محتوى الموقع مؤقتًا في أقرب موقع للمستخدم، لتسريع سرعة الوصول.
Edge Nodeعقدة طرفيةخوادم تخزين مؤقت منتشرة في مواقع مختلفة في شبكة CDN، تقدم خدمة وصول المحتوى مباشرة للمستخدمين.
Originالخادم المصدرالخادم الذي تعود إليه CDN للحصول على المحتوى، يمكن أن يكون تخزينًا كائنيًا أو ECS أو خادمًا ذاتي البناء.
Cache Hitإصابة الذاكرة المؤقتةالمحتوى المطلوب من المستخدم موجود مسبقًا في عقدة CDN الطرفية، يُعاد مباشرة دون حاجة للعودة إلى المصدر.
Cache Missعدم إصابة الذاكرة المؤقتةالعقدة الطرفية لا تحتوي على المحتوى المطلوب، تحتاج إلى العودة للمصدر للحصول عليه.
Hit Ratioمعدل الإصابةنسبة مرات إصابة الذاكرة المؤقتة إلى إجمالي عدد الطلبات. كلما ارتفع معدل الإصابة، قلت العودة للمصدر، وقلت التكلفة.
TTLمدة البقاء/وقت التخزين المؤقتTime To Live، مدة صلاحية المحتوى المخزن مؤقتًا على عقدة CDN. بعد انتهاء الصلاحية، يحتاج إلى العودة للمصدر مرة أخرى.
Back to Sourceالعودة إلى المصدرعملية طلب عقدة CDN الطرفية للمحتوى من الخادم المصدر.
Purge/Refreshمسح الذاكرة المؤقتةإجبار ذاكرة CDN المؤقتة على عدم الصلاحية، ليحصل الطلب التالي على أحدث محتوى من المصدر.
Preheatتسخينقبل النشر الرسمي، دفع المحتوى بنشاط إلى عقد CDN، ليتمكن المستخدم من إصابة الذاكرة المؤقتة في أول زيارة.
CORSمشاركة الموارد عبر الأصولCross-Origin Resource Sharing، آلية أمان في المتصفح، تتحكم في وصول الموارد بين النطاقات المختلفة.
Refererصفحة المصدرحقل في رأس طلب HTTP، يشير إلى الصفحة التي جاء منها الطلب. يُستخدم لمنع سرقة الروابط.
STSخدمة الرمز الآمنSecurity Token Service، خدمة تصدر بيانات اعتماد وصول مؤقتة، تُستخدم في سيناريوهات مثل الرفع المباشر من الواجهة الأمامية.
Multipart Uploadرفع مجزأتقسيم الملفات الكبيرة إلى أجزاء صغيرة متعددة للرفع المتوازي، مع دعم استئناف الرفع بعد الانقطاع، مما يحسن كفاءة وموثوقية الرفع.
ETagعلامة الكيانرأس استجابة HTTP، يُستخدم لتعريف إصدار محدد من المورد، شائع الاستخدام في التحقق من الذاكرة المؤقتة.
S3 APIواجهة S3 المتوافقةمواصفة API للتخزين الكائني من AWS S3، معظم مزودي السحابة للتخزين الكائني متوافقون مع هذه الواجهة.
Canonical Query Stringسلسلة استعلام موحدةجزء من سلسلة التوقيع، تُستخدم لحساب توقيع الطلب، لضمان عدم التلاعب بالطلب.

الخلاصة: القواعد الذهبية للتخزين الكائني + CDN

  1. الرفع عبر المسار المباشر: الملفات الكبيرة بالتجزئة، الأمان بـ STS
  2. التخزين المؤقت متعدد الطبقات: المتصفح -> CDN -> الخادم المصدر، طبقة تلو الأخرى
  3. خدمة المستخدم من أقرب نقطة: DNS ذكي + تغطية عقد عالمية
  4. الأمان بلا تهاون: HTTPS + منع سرقة الروابط + التحكم في الوصول
  5. مراقبة التكاليف: معدل الإصابة، عرض النطاق، تقسيم التخزين، تحسين مستمر

هذه البنية تدعم الغالبية العظمى من وصول الموارد الثابتة على الإنترنت، وفهمها يعني أنك فهمت حجر الأساس لتحسين أداء الويب الحديث.