الشبكات والاتصال

مقارنة بين الاستطلاع الطويل وSSE وWebSockets

18 دقيقة الدرس 9 من 10

مقارنة بين الاستطلاع الطويل وSSE وWebSockets

بُني الويب على نموذج الطلب والاستجابة: يسأل العميل، يجيب الخادم، ثم تُغلق الاتصال. هذا النموذج يعمل بشكل ممتاز لتحميل الصفحات وإرسال النماذج، لكنه يفشل حين تحتاج إلى أن يبادر الخادم بدفع حدث إلى العميل — رسالة دردشة جديدة، أو سعر سهم لحظي، أو حالة خط أنابيب البناء، أو موقع صديق على الخريطة. ثلاثة أنماط معمارية تسد هذه الفجوة: الاستطلاع الطويل (Long Polling)، وأحداث الخادم المرسلة (SSE)، وWebSockets. كل منها يقدم مقايضات مختلفة بين البساطة وقابلية التوسع والقدرات.

لماذا يفشل الاستطلاع المعتاد؟

النهج الساذج هو الاستطلاع القصير (Short Polling): يرسل العميل طلباً كل N ثانية يسأل "هل من جديد؟" إذا كان N = 1 ثانية وعندك 100 000 مستخدم متصل، فهذا يعني 100 000 رحلة HTTP في الثانية — معظمها يعود باستجابات فارغة. الكمون محدود بالحد الأدنى N، وضياع كبير في طاقة المعالج، والـ CDN غير قادر على التخزين المؤقت للتغذيات الديناميكية. نادراً ما يكون الاستطلاع القصير مقبولاً خارج نطاق عدد ضئيل من المستخدمين أو تحمل انتظار يزيد عن 30 ثانية.

الاستطلاع الطويل (Long Polling)

الاستطلاع الطويل حيلة ذكية ضمن HTTP العادي: يرسل العميل طلباً، ويُبقي الخادم الاتصال مفتوحاً حتى يحدث حدث ما (أو تنتهي مهلة زمنية، عادة 20–30 ثانية). حين يكون للخادم شيء يقوله يستجيب فوراً، ويعالج العميل الاستجابة ثم يُطلق طلباً جديداً معلقاً فوراً، مما يعيد خلق تأثير "الانتظار الدائم".

  • الكمون: شبه صفر — يستجيب الخادم بمجرد اندلاع الحدث.
  • التكلفة: كل رسالة تتطلب مصافحة HTTP كاملة (رؤوس، سجل TLS، إلخ)، وهذا مكلف عند معدل أحداث مرتفع.
  • التوسع: كل طلب معلق يحتجز خيطاً أو واصف ملف على الخادم. مع 10 000 مستخدم متزامن تحتاج 10 000 اتصال مفتوح في آن واحد — قابل للإدارة مع الـ I/O غير المتزامن، لكنه مؤلم مع الخوادم ذات الخيط-لكل-اتصال.
  • البنية التحتية: يعمل في كل مكان — موازنات التحميل، البروكسيات، الـ CDN، وجدران الحماية تفهم HTTP ولا يلزم أي تهيئة خاصة.
متى تختار الاستطلاع الطويل: حين لا تستطيع ترقية البنية التحتية (بروكسيات قديمة، جدران حماية صارمة)، أو حين تردد الأحداث منخفض (بضعة في الدقيقة)، أو حين تحتاج لدعم متصفحات قديمة جداً أو بوابات API تقبل REST فقط. استخدمت Twilio وSlack المبكرة الاستطلاع الطويل كنقل احتياطي.
Long Polling sequence diagram Client Server GET /events (request 1) holding… event fires 200 OK {data} GET /events (request 2) holding… 204 No Content (timeout)
الاستطلاع الطويل: يُبقي الخادم كل طلب معلقاً حتى يندلع حدث أو تنتهي المهلة، ثم يُطلق العميل الطلب التالي فوراً.

أحداث الخادم المرسلة (SSE)

SSE معيار رسمي من W3C يُبنى فوق استجابة HTTP/1.1 عادية بنوع المحتوى Content-Type: text/event-stream. يفتح العميل اتصالاً HTTP دائماً واحداً؛ يبث الخادم عبره أحداثاً نصية مفصولة بأسطر جديدة إلى أجل غير مسمى. تتولى واجهة EventSource المدمجة في المتصفح إعادة الاتصال تلقائياً.

  • الاتجاه: من الخادم إلى العميل حصراً (أحادي الاتجاه). لا يستطيع العميل إرسال بيانات عبر نفس القناة — يستخدم XHR/fetch منفصلاً لذلك.
  • الكمون: مماثل لـ WebSockets — تصل الأحداث بمجرد كتابتها في التيار، عادة أقل من 10 ms على الشبكة المحلية، و30–100 ms عبر الإنترنت.
  • تكلفة البروتوكول: منخفضة جداً بعد المصافحة الأولى. كل حدث مجرد نص (عشرات البايتات). تعدد إرسال HTTP/2 يعني أن تيارات SSE لا تعوق الطلبات الأخرى على نفس الاتصال.
  • إعادة الاتصال ومعرفات الأحداث: يرسل الخادم حقل id:؛ يخزنه المتصفح ويرسل Last-Event-ID عند إعادة الاتصال مما يتيح الاستئناف دون فجوات.
  • دعم المتصفحات: جميع المتصفحات الحديثة.
HTTP/2 وSSE: تحت HTTP/1.1 تحد المتصفحات الاتصالات لكل أصل إلى ~6، مما يعني أن أكثر من 6 تيارات SSE من نفس الصفحة ستُحجب. HTTP/2 يضغد التيارات فوق اتصال TCP واحد مما يزيل هذا الحد. تأكد دائماً من تقديم نقطة نهاية SSE عبر HTTP/2 في الإنتاج.

WebSockets

تُنشئ WebSockets قناة TCP ثنائية الاتجاه دائمة. يبدأ الاتصال كطلب HTTP/1.1 Upgrade (مع Connection: Upgrade وUpgrade: websocket). حين يستجيب الخادم بـ 101 Switching Protocols تُتجاهل طبقة HTTP ويتواصل الطرفان بـإطارات ثنائية خفيفة — دون إعادة إرسال الرؤوس مع كل رسالة.

  • الاتجاه: ثنائي. كل من العميل والخادم يدفع في أي وقت.
  • تكلفة كل رسالة: 2–14 بايت من رأس الإطار (مقارنة بمئات البايتات لرؤوس HTTP). عند 1000 رسالة/ثانية هذا الفرق مهم.
  • الكمون: أدنى ما يمكن تحقيقه — عادة 1–5 ms على الشبكة المحلية، و30–80 ms عبر الإنترنت.
  • الحالة: الاتصال ذو حالة — يجب أن يتتبع الخادم أي عملية/خيط يحمل كل مقبس. هذا يعقد التوسع الأفقي.
  • احتكاك البنية التحتية: بعض موازنات التحميل والبروكسيات وجدران الحماية لا تدعم ترقيات WebSocket. قد تحتاج إلى إعداد صريح.
SSE vs WebSocket connection model comparison Server-Sent Events (SSE) Client Server GET /stream (HTTP) 200 text/event-stream data: event1 data: event2 data: event3 … client sends data via separate fetch() WebSockets Client Server Upgrade: websocket 101 Switching Protocols frame: server→client frame: client→server frame: server→client frame: client→server Full-duplex over single TCP
SSE (يسار) يوفر تيار أحادي الاتجاه من الخادم إلى العميل عبر HTTP العادي؛ WebSockets (يمين) توفر اتصالاً ثنائي الاتجاه عبر اتصال TCP مُرقَّى واحد.

مقارنة جانبية

البعد الاستطلاع الطويل SSE WebSockets
الاتجاه خادم → عميل (محاكى) خادم → عميل (أصلي) ثنائي الاتجاه
البروتوكول HTTP/1.1+ HTTP/1.1+ (يُنصح بـ H2) ws:// / wss://
تكلفة كل رسالة مرتفعة (رؤوس HTTP كاملة) منخفضة (أسطر نصية فقط) ضئيلة جداً (2–14 بايت)
إعادة الاتصال يدوي (العميل يعيد الاستطلاع) تلقائي (EventSource) يدوي (مكتبة التطبيق)
توافق الـ Proxy ممتاز جيد (HTTP عادي) متوسط (يحتاج دعم الترقية)
التوسع الأفقي سهل (عديم الحالة بين الاستطلاعات) متوسط (اتصالات طويلة) صعب (ذو حالة، sticky sessions)
الأمثل لـ تحديثات نادرة، بنية قديمة لوحات مباشرة، إشعارات الدردشة، الألعاب، التحرير التعاوني

اعتبارات التوسع

كل من هذه الأنماط يُنشئ اتصالاً مفتوحاً دائماً (الاستطلاع الطويل يُقارب ذلك)، مما يعني أن خادمك يحتفظ بحالة لكل مستخدم. مع 500 000 مستخدم متزامن:

  • الخادم الساذج ذو خيط-لكل-اتصال يحتاج 500 000 خيط — مستحيل. استخدم I/O المبني على الأحداث (Node.js، Go، Nginx، Netty، أو Python غير المتزامن) الذي يستطيع إدارة مئات الآلاف من المقابس المفتوحة في عملية واحدة باستخدام epoll/kqueue على مستوى نظام التشغيل.
  • للـ WebSockets تحتاج sticky sessions (تجزئة متسقة عند موازن التحميل) أو حافلة pub/sub (Redis Pub/Sub أو Kafka) لتوزيع رسالة عبر مقابس موزعة على عُقد مختلفة.
  • تتفادى SSE مشكلة sticky sessions إذا مررت الأحداث عبر قائمة انتظار مشتركة — أي عُقدة تستطيع التقاط الحدث وكتابته لعملاء SSE المحليين.
قطيع الرعد عند إعادة الاتصال: إذا انقطع 200 000 عميل SSE أو WebSocket في وقت واحد (إعادة تشغيل الخادم، اضطراب شبكي) وحاولوا إعادة الاتصال فوراً، يمكن للارتفاع المفاجئ أن يُغرق خدمتك. أضف تراجعاً أسياً مع تشويش عشوائي في منطق إعادة الاتصال بالعميل (مثال: انتظر 1–16 ثانية + تشويش عشوائي). المتصفح الأصلي EventSource لا يضيف هذا التراجع — يجب تطبيقه يدوياً إذا لففت SSE.

استخدامات حقيقية على نطاق واسع

  • سجلات GitHub Actions المباشرة — SSE. تيار بسيط أحادي الاتجاه؛ HTTP عادي يعمل عبر بروكسيات الشركات.
  • Slack — بدأ بالاستطلاع الطويل، انتقل إلى WebSockets للمراسلة الفورية. يستخدم الآن بروتوكولاً مخصصاً فوق WebSockets مع نبضات قلب وتأكيدات رسائل.
  • Figma / Google Docs للتحرير التعاوني — WebSockets مع Operational Transform أو CRDT عبر القناة الثنائية الاتجاه.
  • لوحات البيانات المالية (Bloomberg، Robinhood) — SSE لتغذيات بيانات السوق (يبادر بها الخادم، عالية التردد، لا حاجة لإرسال العميل على قناة البيانات).
  • تتبع موقع السائق في Uber — WebSockets (ثنائي الاتجاه: السائق يرسل GPS، الراكب يستقبل التحديثات).
WebSocket horizontal scaling with pub/sub bus Clients A Clients B Load Balancer (sticky sessions) WS Node 1 WS Node 2 Pub/Sub Bus (Redis / Kafka) Backend / DB publish/subscribe fan-out to sockets
التوسع الأفقي لـ WebSockets: حافلة pub/sub (Redis أو Kafka) تسمح لأي عُقدة خلفية بتوزيع رسالة على مقابس موزعة عبر عُقد WS مختلفة، مما يزيل الحاجة لوصول كل رسالة لنفس العملية.

إطار القرار

اسأل نفسك سؤالين لاختيار النقل المناسب:

  1. هل يحتاج العميل إلى إرسال بيانات في الوقت الفعلي عبر نفس القناة؟ إذا نعم، تحتاج WebSockets. إذا لا، يمكن لـ SSE أو الاستطلاع الطويل أن يعمل.
  2. كم تردد وصول الأحداث؟ تردد منخفض (بضعة في الدقيقة) وبنية تحتية قديمة → استطلاع طويل. تردد متوسط إلى مرتفع، عملاء متصفح، اتجاه واحد فقط من الخادم → SSE. تردد مرتفع، ثنائي الاتجاه، حرج الكمون → WebSockets.
النهج الهجين: كثير من أنظمة الإنتاج تستخدم SSE لـاستقبال البيانات الفورية (تكلفة بنية تحتية أقل، يعمل في كل مكان) وـfetch() عادياً للأوامر من العميل إلى الخادم. هذا أبسط في التوسع والتشخيص من WebSockets لمعظم حالات الاستخدام. الجأ إلى WebSockets فقط حين تحتاج فعلاً إلى الثنائية بكمون منخفض.