Streaming في gRPC: بناء اتصالات ثنائية الاتجاه بكفاءة عالية
عندما تنتقل من بناء REST APIs تقليدية إلى تصميم أنظمة موزعة حديثة، ستصطدم سريعًا بسؤال: كيف أنقل البيانات بشكل مستمر وتفاعلي بين الخدمات بدون تعقيد كبير وبأداء عالٍ؟ هنا يأتي gRPC streaming كأحد أقوى الأدوات في عالم الـ RPC الحديث.
في هذه المقالة من سلسلة شروحات RPC في افهم صح سنشرح:
- ما هو gRPC بشكل سريع
- ما المقصود بالـ Streaming في gRPC ولماذا هو مختلف عن REST
- شرح واضح للأنواع الثلاثة: client streaming وserver streaming وbidirectional streaming
- متى تستخدم كل نوع مع أمثلة عملية
- أفضل ممارسات لبناء اتصالات streaming فعّالة في الأنظمة الموزعة
لمحة سريعة عن gRPC وعلاقته بـ RPC
gRPC هو إطار عمل من جوجل مبني على بروتوكول HTTP/2، ويعتمد على فكرة Remote Procedure Call – RPC، أي أنك تستدعي دوال على خادم بعيد كما لو كانت دوال عادية في الكود.
إذا لم تكن لديك خلفية عن RPC، يمكنك الرجوع إلى:
ما يميز gRPC عن REST التقليدي ليس فقط الأداء، بل أيضًا دعم أنماط Streaming بشكل مدمج في البروتوكول، باستخدام قدرات HTTP/2 في الـ multiplexing والاتصالات طويلة الأمد.
ما هو gRPC streaming؟
في REST، النموذج الشائع هو:
- العميل يرسل طلب HTTP واحد
- الخادم يرد برد واحد
لكن في gRPC streaming يصبح الأمر أكثر مرونة. يمكن:
- إرسال عدة رسائل في نفس الطلب أو الرد
- الحفاظ على قناة اتصال مفتوحة بين العميل والخادم
- إرسال واستقبال البيانات بشكل متدرّج وتدريجي بدلاً من الانتظار حتى اكتمال كل شيء
تقنيًا، gRPC يدعم أربعة أنواع من الـ RPC:
- Unary (طلب واحد – رد واحد)
- Server Streaming
- Client Streaming
- Bidirectional Streaming
سنركز في هذا المقال على الأنواع الثلاثة الخاصة بـ Streaming لأنها الأهم لبناء اتصالات تفاعلية وثنائية الاتجاه بكفاءة عالية في الأنظمة الموزعة.
النوع الأول: Server Streaming – عندما تحتاج لردود متتالية من الخادم
ما هو Server Streaming؟
في server streaming:
- العميل يرسل طلب واحد للخادم
- الخادم يرد بسيل من الرسائل المتتابعة عبر نفس الاتصال
- الاتصال يبقى مفتوحًا حتى ينتهي الخادم من إرسال كل الرسائل
تعريفه في ملف .proto يكون مثلًا:
مثال:
rpc ListOrders(ListOrdersRequest) returns (stream Order) {}
هنا الخادم سيرسل أكثر من Order واحد للعميل من خلال نفس الاستدعاء.
متى تستخدم Server Streaming؟
- نقل مجموعات بيانات كبيرة بدلًا من إرسالها دفعة واحدة (مثل سجلات أو نتائج بحث متعددة الصفحات)
- تحديثات مستمرة من الخادم مثل:
- مراقبة حالة خدمة أو سيرفر
- Streaming للوغز أو أحداث (logs, events)
- تقليل زمن الانتظار عبر إرسال أول جزء من البيانات فور توفره بدلًا من الانتظار لاكتمال المعالجة الكاملة
فوائد Server Streaming
- أفضل في استهلاك الذاكرة من الطرفين؛ لا حاجة لتحميل كل الرد دفعة واحدة
- يوفر تجربة قريبة من real-time للعميل عند التعامل مع بيانات متتالية
- يناسب نمط pub/sub بسيط عندما يكون اتجاه البيانات غالبًا من الخادم للعميل
النوع الثاني: Client Streaming – عندما يرسل العميل سيل من البيانات
ما هو Client Streaming؟
في client streaming:
- العميل يرسل عدة رسائل للخادم عبر نفس الاتصال
- الخادم يستقبل هذه الرسائل بشكل متدفق
- بعد انتهاء العميل، يرسل الخادم ردًا واحدًا نهائيًا
تعريفه في ملف .proto يكون مثلًا:
rpc UploadMetrics(stream Metric) returns (UploadSummary) {}
هنا العميل يرسل العديد من Metric، وعندما ينتهي، يعيد الخادم كائن UploadSummary واحد يلخص ما تم استقباله.
متى تستخدم Client Streaming؟
- رفع بيانات كثيرة على دفعات:
- رفع ملفات أو أجزاء من ملف كبير
- إرسال دفعات من القياسات (metrics) من Agent إلى خادم مركزية
- تقليل overhead الاتصالات:
- بدل إرسال مئات الطلبات المنفصلة، تستخدم اتصال واحد وتبث فيه الرسائل
- حالات batch processing حيث العميل يجمع بيانات ثم يرسلها كسيل متتابع
فوائد Client Streaming
- تقليل ضغط الشبكة عبر تجميع الرسائل في اتصال واحد
- إمكانية معالجة الرسائل تدريجيًا على الخادم أثناء الاستقبال
- تحسين الأداء في الخدمات التي تتلقى الكثير من البيانات من عدة عملاء
النوع الثالث: Bidirectional Streaming – اتجاهين في نفس الوقت
ما هو Bidirectional Streaming؟
هذا هو النمط الأقوى بين أنماط gRPC streaming.
- العميل يمكنه إرسال سلسلة رسائل للخادم
- الخادم يمكنه أيضًا إرسال سلسلة رسائل للعميل
- الاتجاهان يعملان بشكل مستقل ومتزامن (full duplex)
التعريف في .proto:
rpc Chat(stream ChatMessage) returns (stream ChatMessage) {}
هنا العميل والخادم يمكنهما الاستمرار في تبادل رسائل ChatMessage طوال مدة الاتصال.
كيف يختلف عن WebSockets؟
من ناحية الفكرة، bidirectional streaming يشبه ما تفعله WebSockets في عالم HTTP/1.1، لكن:
- يعتمد على HTTP/2 و
protobuf، ما يجعل الرسائل أصغر وأسرع - يتكامل مباشرة مع عالم RPC وأنواع الاستدعاءات الأخرى
- يوفر توثيقًا وتعريفًا واضحًا للـ API من خلال ملفات
.proto
إذا كنت مهتمًا بالمقارنة العملية مع WebSockets في REST APIs، راجع: التعامل مع WebSockets في FastAPI: تطبيق عملي.
متى تستخدم Bidirectional Streaming؟
- تطبيقات الدردشة بين الخدمات أو بين العميل والخادم
- Streaming للبيانات الحية مثل:
- أسعار الأسهم
- حالة اللاعبين في الألعاب (game state)
- تتبع المواقع في الزمن الحقيقي
- أنظمة التحكم حيث يرسل العميل أوامر ويستجيب الخادم بحالة فورية مستمرة
- التكامل مع أجهزة IoT التي تحتاج قنوات اتصال ثنائية الاتجاه مستمرة
ثلاثة أنماط للاستخدام العملي
داخل bidirectional streaming يمكن تصميم سلوك البروتوكول بين العميل والخادم بعدة طرق:
- طلب/رد متداخل (Interleaved Request/Response):
- كل رسالة من العميل تقابلها رسالة رد من الخادم
- يشبه RPC متسلسل لكن عبر نفس قناة الـ stream
- Streaming حر في الاتجاهين (Full Duplex):
- الطرفان يرسلان الرسائل بشكل مستقل
- مفيد في الدردشة أو الإشعارات ثنائية الاتجاه
- حالة مشتركة (Shared State):
- العميل يرسل تحديثات للخادم
- الخادم يبث نسخة محدثة من الحالة للعميل في كل مرة تتغير فيها
لماذا gRPC Streaming مناسب للأنظمة الموزعة؟
في الأنظمة الموزعة Microservices، التحدي ليس فقط في تبادل الطلبات، بل في:
- مزامنة الحالات بين الخدمات
- نقل كميات كبيرة من الأحداث (events)
- تحديث الـ clients بحالة النظام لحظيًا
هنا تظهر قوة gRPC streaming مقارنة بالـ REST التقليدي:
- تقليل Latency:
- لا حاجة لفتح اتصال جديد لكل طلب
- الاتصال المستمر يقلل TCP handshake و
HTTP overhead
- استهلاك أقل للموارد:
- الرسائل تكون ثنائية (binary) عبر
protobuf وليست JSON كبيرة الحجم - يدعم HTTP/2 multiplexing على نفس الاتصال
- نموذج برمجي موحد:
- تكتب تعريفات الـ API مرة واحدة في
.proto - تولد كود العميل والخادم بعدة لغات بطريقة تلقائية
إذا أردت فهم أعمق لفكرة Streaming مقابل buffering و caching، يمكنك قراءة: ما هو الفرق بين Buffering و Caching و Streaming؟
مثال عملي مبسط: خدمة إشعارات باستخدام gRPC Streaming
تخيل خدمة NotificationsService ترسل إشعارات لحظية لخادم واجهة أمامية (Gateway).
باستخدام Server Streaming
يمكنك تعريف RPC مثل:
rpc SubscribeNotifications(SubscribeRequest) returns (stream Notification) {}
الـ Gateway يستدعي SubscribeNotifications مرة واحدة، ثم يستقبل Notification كلما حدث تغيير في النظام (أحداث جديدة، رسائل، إلخ). هذا يشبه long-polling لكن مبني على gRPC وHTTP/2 بكفاءة أعلى.
باستخدام Bidirectional Streaming
إذا أردت السماح للـ Gateway بإرسال أوامر تحكم أو ACKs للخدمة، يمكنك استخدام:
rpc NotificationChannel(stream ClientMessage) returns (stream ServerMessage) {}
بهذا الشكل:
- الـ Gateway يرسل
ClientMessage متى احتاج تحديث الاشتراكات أو فلترة أنواع الإشعارات - الخدمة ترد بـ
ServerMessage عند وجود إشعار جديد أو تغيير في الحالة
التعامل مع الأخطاء و Retry مع Streaming
في الاتصالات الطويلة مثل gRPC streaming، يجب أن تفترض دائمًا احتمال:
- انقطاع الاتصال بسبب مشاكل في الشبكة
- إعادة تشغيل (restart) للخدمات
- مشاكل مؤقتة في البنية التحتية
لذلك من المهم دمج مفاهيم مثل Retry Pattern وخطط إعادة الاتصال (reconnection strategy). يمكنك الرجوع إلى: Retry Pattern في الأنظمة الموزعة: كيف تعالج فشل الطلبات بدون انهيار النظام.
نصائح عملية لإدارة الأخطاء في gRPC Streaming
- استخدم status codes المعيارية في gRPC (مثل
UNAVAILABLE عند مشاكل الشبكة) - طبّق Exponential Backoff عند إعادة الاتصال لتقليل الضغط على الشبكة والخدمات
- في bidirectional streaming، حاول تصميم بروتوكول الرسائل بحيث يكون idempotent قدر الإمكان
- استخدم Timeouts و Deadlines مبنية على طبيعة كل stream
الاعتبارات التصميمية في الأنظمة الموزعة
عند استخدام gRPC streaming بين خدمات متعددة في نظام موزع، ضع في اعتبارك النقاط التالية:
1. Service Discovery
حتى تستفيد من streaming بين الخدمات، يجب أن تستطيع كل خدمة العثور على الخدمات الأخرى بكل ديناميكية. هنا يأتي دور: شرح Service Discovery: كيف تجد الخدمات بعضها في الأنظمة الموزعة.
- خدمة الـ client تحتاج لاختيار instance مناسب من خدمة الـ server
- يجب أن تراعي توازن الأحمال (load balancing) خاصة مع الاتصالات طويلة الأمد
2. المراقبة والتتبع (Observability)
Streaming يجعل الطلب الواحد يمتد لوقت طويل، مما يصعّب تتبعه مقارنة بطلبات قصيرة. لذلك يفضّل:
- استخدام Distributed Tracing مع أدوات مثل OpenTelemetry
- تتبع إنشاء وإنهاء الـ stream، وعدد الرسائل المتبادلة داخله
يمكنك البدء بقراءة: OpenTelemetry: الأداة الحديثة لتطبيق Distributed Tracing في الأنظمة الكبيرة.
3. التحكم في التدفق (Flow Control)
HTTP/2 يوفر آليات flow control، وgRPC يستفيد منها، لكن ما زال عليك تصميم بروتوكولك الداخلي بحيث:
- لا يغرق الخادم بسيل رسائل أسرع من قدرته على المعالجة
- تتعامل مع backpressure؛ أي إعلام الطرف الآخر بضرورة الإبطاء
يمكنك مثلًا:
- إضافة حقول في الرسائل لتحديد معدل الإرسال
- استخدام رسائل تحكم خاصة (control messages) لإيقاف أو استئناف الـ stream
مقارنة سريعة بين أنواع gRPC Streaming
| النوع | اتجاه البيانات | متى يستخدم؟ |
| Server Streaming | من الخادم إلى العميل فقط (متعدد الرسائل) | تحديثات أو نتائج متعددة من الخادم بعد طلب واحد من العميل |
| Client Streaming | من العميل إلى الخادم فقط (متعدد الرسائل) | رفع بيانات أو ملفات أو قياسات من العميل إلى الخادم مع رد نهائي واحد |
| Bidirectional Streaming | متبادل بين الطرفين (متعدد الرسائل) | اتصالات تفاعلية في الزمن الحقيقي، مثل الدردشة والتحكم اللحظي |
خلاصة: متى تختار gRPC Streaming؟
يمكن تلخيص استخدام gRPC streaming في الأنظمة الموزعة كالتالي:
- إذا كان عندك بيانات كثيرة تريد نقلها بطريقة فعّالة: استخدم server streaming أو client streaming
- إذا كنت تحتاج تواصل لحظي ثنائي الاتجاه: استخدم bidirectional streaming
- إذا كان عندك تحديثات مستمرة في النظام وترغب في إعلام عدة عملاء بها في الوقت الحقيقي، فـ gRPC streaming يوفر بديلاً قويًا لـ WebSockets خصوصًا داخل بيئة الـ microservices
في النهاية، قوة gRPC لا تأتي فقط من كونه بروتوكول سريع، بل من قدرته على دمج أنماط streaming بشكل طبيعي داخل نموذج RPC، مما يجعله خيارًا مثاليًا عند تصميم أنظمة موزعة تحتاج إلى أداء عالٍ واتصالات تفاعلية فعّالة.