ما هو RPC؟ شرح مبسط للتواصل بين الخدمات عن بُعد

ما هو RPC؟ شرح مبسط للتواصل بين الخدمات عن بُعد

RPC اختصار لـ Remote Procedure Call أو "استدعاء إجراء عن بُعد"، وهو أسلوب للتواصل بين الأنظمة والخدمات الموزعة يسمح لك باستدعاء دالة أو إجراء في خادم آخر وكأنها دالة عادية في نفس البرنامج.

في الأنظمة الموزعة الحديثة (Microservices، خدمات سحابية، Backends كبيرة)، يصبح التواصل بين الخدمات جزءًا أساسيًا من التصميم. وهنا يأتي دور RPC كأحد أشهر الأنماط بجانب REST وEvent Streaming وغيرها.

في هذا المقال من قسم الأنظمة الموزعة على مدونة "افهم صح"، سنشرح:

  • مفهوم RPC بشكل مبسط
  • كيف يعمل RPC تحت الغطاء
  • مميزات وعيوب RPC
  • الفرق بين RPC وREST
  • أمثلة على استخدامه في الأنظمة الموزعة

ما هو RPC بالضبط؟

الفكرة الأساسية في RPC هي إخفاء تفاصيل الشبكة عن المطوّر. بدلًا من أن ترسل طلب HTTP وتتعامل مع JSON يدويًا، تتعامل مع دوال (Functions/Procedures) عادية:

  • في الكود تكتب: result = userService.getUserById(5)
  • لكن فعليًا: يتم إرسال طلب عبر الشبكة إلى خدمة أخرى تنفّذ الدالة وترجع النتيجة

يعني: الاستدعاء يبدو محليًا ولكنه في الحقيقة "عن بُعد". وهنا يأتي اسم Remote Procedure Call.

كيف يعمل RPC من الداخل؟ (بشكل مبسط)

لكي تنجح هذه الخدعة (استدعاء دالة عن بُعد وكأنها محلية)، RPC يعتمد على عدة مكونات أساسية:

1. الـ Stub (الواجهة الوهمية)

الـ Stub هو كود يتم توليده (أو كتابته) على جانب العميل (Client) ليعمل كوكيل (Proxy) للدالة البعيدة:

  • أنت تستدعي الدالة في كودك العادي
  • الـ Stub يتولى مهمة:
    • أخذ البراميترز (Parameters)
    • تحويلها إلى صيغة مناسبة للإرسال عبر الشبكة (Serialization)
    • إرسالها إلى الخادم (Server)

2. Serialization / Deserialization (تغليف وفك تغليف البيانات)

لكي يتم إرسال البيانات عبر الشبكة، يجب تحويل الكائنات (Objects) والأنواع المعقدة إلى Bytes أو صيغة متفق عليها (مثل JSON أو Protocol Buffers):

  • Serialization: تحويل البيانات من شكلها في الذاكرة إلى صيغة قابلة للإرسال
  • Deserialization: إعادة تحويلها من الصيغة المرسلة إلى كائنات في الذاكرة

أنظمة RPC الحديثة مثل gRPC تستخدم Protocol Buffers لتكون سريعة وذات حجم بيانات صغير مقارنة بـ JSON.

3. Transport Layer (طبقة النقل)

RPC يحتاج بروتوكول لنقل البيانات بين العميل والخادم، وغالبًا يكون:

  • TCP في الأنظمة التي تحتاج موثوقية عالية وضمان ترتيب الحزم (يمكنك قراءة المزيد عن بروتوكول TCP وكيف يعمل)
  • أو أحيانًا HTTP/2 أو حتى UDP في حالات خاصة (لكن نادرًا في RPC التقليدي، ولمعرفة المزيد عن بروتوكول UDP)

4. الخادم (Server) وتنفيذ الدالة

على الجانب الآخر، الخادم يستقبل الطلب:

  1. يفك الـ Serialization للبيانات المستلمة
  2. يستدعي الدالة الحقيقية مع البراميترز المستلمة
  3. يحصل على النتيجة
  4. يحوّل النتيجة إلى صيغة صالحة للإرسال (Serialization)
  5. يرسل النتيجة مرة أخرى للعميل

العميل يقوم بعد ذلك بعملية Deserialization ويعيد النتيجة إليك في الكود كقيمة عادية من الدالة.

لماذا نستخدم RPC؟

في الأنظمة الموزعة (Microservices، Distributed Systems)، نحتاج لخدمات تتكلم مع بعضها:

  • خدمة المستخدمين
  • خدمة الدفع
  • خدمة الإشعارات

هنا يمكن استخدام REST أو RPC أو أساليب أخرى مثل Event Streaming (يمكنك زيارة شرح Event Streaming وKafka لمزيد من التفاصيل).

ميزة RPC الأساسية:

  • يعطيك إحساس الاستدعاء المحلي، مما يبسّط الكود
  • يستخدم تعريفات قوية للأنواع (Strongly Typed) في معظم الأحيان
  • أبسط عند تعريف عقود (Contracts) بين الخدمات (مثل ملفات .proto في gRPC)

مثال مبسط على RPC

تخيل أن لديك خدمة UserService تعمل على خادم مستقل، وتريد أن تستعلم عن المستخدم من خدمة أخرى مثل OrderService.

بدون RPC (باستخدام REST يدويًا في الكود)

كود شبيه بالآتي:

  • تكوّن URL: https://user-service/api/users/5
  • ترسل طلب HTTP GET
  • تستقبل JSON
  • تحوّل JSON إلى كائن User

مع RPC

كل ما تحتاجه في كودك:

  • user = userService.getUserById(5)

الباقي يقوم به إطار عمل RPC:

  • إرسال الطلب عبر الشبكة
  • إدارة Serialization/Deserialization
  • التعامل مع الأخطاء

الفرق بين RPC وREST

كثير من المطورين يخلطون بين RPC وREST، لأن الاثنين يُستخدمان للتواصل بين الخدمات، لكنهما يعتمدان فلسفة مختلفة.

1. نمط التفكير (Thinking Model)

  • REST: مبني على الموارد (Resources) مثل /users، /orders. تركز على CRUD واستخدام HTTP verbs (GET, POST, PUT, DELETE).
  • RPC: مبني على العمليات (Operations / Methods) مثل getUserById، createOrder، cancelOrder.

2. الشكل على مستوى الـ API

  • في REST:
    • GET /users/5
    • POST /orders
  • في RPC:
    • Method: UserService.GetUserById
    • Method: OrderService.CreateOrder

3. البروتوكولات واستخدام HTTP

  • REST غالبًا فوق HTTP/1.1 باستخدام JSON
  • RPC قد يعمل فوق HTTP/2 (مثل gRPC) باستخدام Protocol Buffers أو بروتوكولات أخرى

4. التوافق مع الويب والمتصفحات

  • REST: سهل الاستهلاك من المتصفحات وعميل JavaScript عادي
  • gRPC مثلًا: يحتاج أحيانًا Proxies أو gRPC-Web إذا كنت تريد استخدامه من المتصفح مباشرة

متى أستخدم REST ومتى أستخدم RPC؟

  • REST:
    • مناسب للـ Public APIs
    • مناسب عندما يكون العملاء متنوعين (موبايل، متصفح، أطراف خارجية)
  • RPC:
    • مناسب للتواصل الداخلي بين الخدمات (Internal Microservices)
    • مناسب في الأنظمة التي تهتم بالأداء (Latency/Higher throughput)
    • مفيد عندما تحتاج إلى عقود صارمة Strong Typing بين الخدمات

أمثلة على أطر العمل (Frameworks) التي تستخدم RPC

هناك عدة تقنيات مشهورة تعتمد على RPC:

  • gRPC من Google:
    • يستخدم بروتوكول HTTP/2
    • يعتمد على Protocol Buffers في Serialization
    • يدعم Streaming (Client Streaming, Server Streaming, Bidirectional)
  • JSON-RPC:
    • بروتوكول بسيط يستخدم JSON لنقل البيانات
    • سهل القراءة والتجربة
  • XML-RPC:
    • إصدار قديم نسبيًا يعتمد على XML
    • أقل استخدامًا حاليًا مقارنة بـ gRPC وJSON-RPC

RPC في الأنظمة الموزعة الحديثة

عند تصميم نظام موزع (Distributed System) أو معمارية Microservices، غالبًا ما يكون هناك:

  • أكثر من خدمة تحتاج للاتصال ببعضها
  • تحديات مثل: Latency، الأعطال الجزئية، اكتشاف الخدمات، Rate Limiting، Retry

RPC لا يعمل بمفرده، بل يتكامل مع أنماط أخرى شرحتها في مقالات سابقة:

  • Service Discovery: لكي تعرف الخدمة عنوان الخدمة الأخرى التي تستدعيها عبر RPC
  • Retry Pattern: لإعادة المحاولة عند فشل استدعاء RPC بدلًا من انهيار النظام
  • Rate Limiting: لمنع إساءة استخدام الـ API عندما تكون الاستدعاءات كثيرة
  • Observability: لتتبع طلبات RPC عبر Logs, Metrics, Tracing

سيناريو بسيط: نظام طلبات أونلاين

تصور نظامًا يتكون من:

  • خدمة Users
  • خدمة Orders
  • خدمة Payment

عندما ينشئ المستخدم طلبًا:

  1. خدمة Orders تحتاج بيانات المستخدم
    • تستدعي عبر RPC: UserService.GetUserById(userId)
  2. بعد إنشاء الطلب، تحتاج تأكيد الدفع
    • تستدعي عبر RPC: PaymentService.Charge(userId, amount)

كل هذا يحدث عبر استدعاءات RPC بين الخدمات، بينما أنت كمطوّر تتعامل مع دوال/Methods وكأنها محلية.

مميزات RPC

  • تبسيط الكود:
    • المطور يستدعي دالة بدلًا من بناء طلب HTTP يدويًا في كل مرة
  • تحديد عقد واضح بين الخدمات:
    • ملفات تعريف (مثل .proto في gRPC) تحدد الرسائل والعمليات
    • إمكانية توليد كود Client/Server تلقائيًا من نفس العقد
  • أداء أفضل غالبًا:
    • خاصة مع بروتوكولات فعّالة مثل Protocol Buffers
    • تقليل حجم البيانات المرسلة مقارنة بـ JSON/REST في بعض الحالات
  • دعم Streaming في بعض الأطر:
    • استقبال/إرسال بيانات بشكل مستمر بدون فتح اتصال جديد في كل مرة

عيوب وتحديات RPC

رغم مميزاته، RPC يأتي بمجموعة تحديات يجب الانتباه لها:

1. إخفاء حقيقة أن الشبكة غير موثوقة

أحد المخاطر في RPC أن المطوّر ينسى أن الدالة "بعيدة" وأن الشبكة قد:

  • تتأخر (High Latency)
  • تنقطع (Timeouts)
  • تفشل جزئيًا (Partial Failures)

هذا يجعل من المهم جدًا تطبيق:

  • Timeouts
  • Retries مع Backoff
  • Circuit Breaker

2. الارتباط القوي (Tight Coupling)

غالبًا ما يؤدي RPC إلى ربط أقوى بين الخدمات:

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

3. صعوبة في الوصول من متصفحات الويب في بعض الأطر

مثال: gRPC تقليديًا ليس موجهًا مباشرة للمتصفحات بدون gRPC-Web أو Proxies إضافية، على عكس REST/JSON الذي يعمل بسهولة من أي Client.

4. التعقيد في Debugging بدون Observability

في الأنظمة الموزعة التي تستخدم RPC بكثافة، تعقب طلب واحد بين عشرات الخدمات يحتاج:

  • Tracing
  • Logs منظمة
  • Metrics واضحة

لذلك وجود منظومة Observability قوية يصبح ضروريًا.

RPC وDesign Patterns في الأنظمة الموزعة

RPC عادةً يتكامل مع أنماط أخرى لتقليل التأثير السلبي لمشاكل الشبكة:

  • Retry Pattern:
  • Rate Limiting:
    • لتحديد عدد استدعاءات RPC المسموحة خلال فترة زمنية
    • مهم جدًا عند وجود عملاء كُثُر أو احتمال إساءة استخدام النظام
  • Service Discovery:
    • بدل أن تضع IP ثابت لخدمة معينة، يتم اكتشاف عنوانها ديناميكيًا
    • هذا مهم جدًا في بيئات مثل Kubernetes حيث تتغير العناوين باستمرار

خلاصة: متى تفكر في استخدام RPC؟

استخدام RPC منطقي عندما:

  • تبني معمارية Microservices أو نظام موزع
  • تحتاج تواصل سريع وفعّال بين الخدمات الداخلية
  • تريد عقود واضحة Strongly Typed بين الخدمات
  • أغلب عملائك هم خدمات داخلية (Internal Services)، وليس متصفحات أو أطراف خارجية

بينما يكون REST خيارًا أفضل عندما:

  • تقدم API عام (Public)
  • عملاؤك متنوعون (موبايل، Web، أطراف خارجية)
  • تريد بساطة وعالمية أكبر

في النهاية، RPC ليس بديلًا مطلقًا لـ REST ولا العكس، بل هو أداة ضمن مجموعة أدوات تصميم الأنظمة الموزعة. اختيارك بينهما يعتمد على طبيعة النظام، نوع العملاء، والأهداف من حيث الأداء والمرونة.

فهم RPC يساعدك على تصميم أنظمة موزعة أكثر كفاءة، وفهم تحديات الشبكات، والتعامل بشكل أفضل مع مشكلات مثل الـ Latency، الأعطال الجزئية، والـ Observability، وهي مواضيع أساسية لأي مطوّر يعمل اليوم مع الأنظمة الحديثة.

حول المحتوى:

تعرف على مفهوم RPC وكيف يختلف عن REST، مع أمثلة على استخدامه في الأنظمة الموزعة.

هل كان هذا مفيدًا لك؟

أضف تعليقك