بدء استخدام gRPC-Python

1. مقدمة

في هذا الدرس التطبيقي حول الترميز، ستستخدم gRPC-Python لإنشاء عميل وخادم يشكّلان الأساس لتطبيق يحدّد المسارات مكتوب بلغة Python.

في نهاية هذا الدليل التعليمي، سيكون لديك عميل يتصل بخادم بعيد باستخدام gRPC للحصول على اسم أو عنوان بريدي للمكان الذي يقع عند إحداثيات معيّنة على الخريطة. قد يستخدم تطبيق متكامل تصميم العميل والخادم هذا لتعداد نقاط الاهتمام أو تلخيصها على طول مسار معيّن.

يتم تحديد الخدمة في ملف بتنسيق Protocol Buffers، وسيتم استخدام هذا الملف لإنشاء رمز نص نموذجي للعميل والخادم حتى يتمكّنا من التواصل مع بعضهما البعض، ما يوفّر عليك الوقت والجهد في تنفيذ هذه الوظيفة.

لا يهتم هذا الرمز الذي تم إنشاؤه بتعقيدات الاتصال بين الخادم والعميل فحسب، بل أيضًا بتسلسل البيانات وإلغاء تسلسلها.

ماذا ستتعلّم؟

  • كيفية استخدام "مخازن البروتوكولات المؤقتة" (Protocol Buffers) لتحديد واجهة برمجة تطبيقات الخدمة
  • كيفية إنشاء عميل وخادم يستندان إلى gRPC استنادًا إلى "مخازن البروتوكولات المؤقتة" من خلال إنشاء الرموز البرمجية آليًا
  • فهم عملية التواصل بين العميل والخادم باستخدام gRPC

هذا الدرس التطبيقي حول الترميز موجّه لمطوّري Python المبتدئين في gRPC أو الذين يريدون تجديد معلوماتهم في المجال، أو أي شخص آخر مهتم بتطوير أنظمة موزّعة. لا يُشترط توفّر خبرة سابقة في gRPC.

2. قبل البدء

المتطلبات

  • الإصدار 3.9 أو الإصدارات الأحدث من Python ننصحك باستخدام الإصدار 3.13 من Python. للحصول على تعليمات التثبيت الخاصة بكل نظام أساسي، يُرجى الاطّلاع على إعداد Python واستخدامه. بدلاً من ذلك، يمكنك تثبيت إصدار غير تابع للنظام من Python باستخدام أدوات مثل uv أو pyenv.
  • pip لتثبيت حِزم Python
  • venv لإنشاء بيئات Python افتراضية

تُعدّ الحزمتان ensurepip وvenv جزءًا من مكتبة Python العادية، وتتوفّران عادةً تلقائيًا.

ومع ذلك، تختار بعض التوزيعات المستندة إلى Debian (بما في ذلك Ubuntu) استبعادها عند إعادة توزيع Python. لتثبيت الحِزم، شغِّل الأمر التالي:

sudo apt install python3-pip python3-venv

الحصول على الشفرة‏

لتبسيط عملية التعلّم، يقدّم لك هذا الدرس التطبيقي حول الترميز بنية أساسية مُعدّة مسبقًا لرمز المصدر لمساعدتك على البدء. ستوضّح لك الخطوات التالية كيفية إكمال التطبيق، بما في ذلك إنشاء رمز gRPC باستخدام مكوّن برنامج تجميع مخازن البروتوكولات المؤقتة grpc_tools.protoc.

grpc-codelabs

يتوفّر رمز المصدر الخاص بالبنية الأساسية لهذا الدرس التطبيقي حول الترميز في الدليل codelabs/grpc-python-getting-started/start_here. إذا كنت تفضّل عدم تنفيذ الرمز بنفسك، يتوفّر الرمز المصدر المكتمل في الدليل completed.

أولاً، أنشئ دليل عمل الدرس التطبيقي وادخله:

mkdir grpc-python-getting-started && cd grpc-python-getting-started

نزِّل الدرس التطبيقي حول الترميز واستخرِجه:

curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
  | tar xvz --strip-components=4 \
  grpc-codelabs-1/codelabs/grpc-python-getting-started/start_here

بدلاً من ذلك، يمكنك تنزيل ملف ‎ .zip الذي يحتوي على دليل الدرس العملي فقط وفك ضغطه يدويًا.

3. تحديد الخدمة

تتمثّل خطوتك الأولى في تحديد خدمة gRPC للتطبيق وطريقة استدعاء إجراء عن بُعد (RPC) وأنواع رسائل الطلبات والردود باستخدام لغة تعريف واجهة مخازن البروتوكولات المؤقتة. ستوفّر خدمتك ما يلي:

  • طريقة استدعاء إجراء عن بُعد تُسمّى GetFeature ينفّذها الخادم ويستدعيها العميل.
  • نوعا الرسائل Point وFeature هما بنى بيانات يتم تبادلها بين العميل والخادم عند استخدام الطريقة GetFeature. يقدّم العميل إحداثيات الخريطة كـ Point في طلب GetFeature إلى الخادم، ويردّ الخادم بـ Feature مطابق يصف أي شيء يقع في تلك الإحداثيات.

سيتم تحديد طريقة "استدعاء الإجراء عن بُعد" هذه وأنواع الرسائل الخاصة بها في ملف protos/route_guide.proto الخاص بالرمز المصدر المقدَّم.

يُشار إلى "مخازن البروتوكولات المؤقتة" عادةً باسم protobuf. لمزيد من المعلومات عن مصطلحات gRPC، يُرجى الاطّلاع على المفاهيم الأساسية والبنية ودورة الحياة في gRPC.

أنواع الرسائل

في ملف protos/route_guide.proto الخاص بالرمز المصدر، حدِّد أولاً نوع رسالة Point. يمثّل Point زوج إحداثيات خط العرض وخط الطول على الخريطة. في هذا الدرس التطبيقي، استخدِم أعدادًا صحيحة للإحداثيات:

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

الرقمان 1 و2 هما رقما تعريف فريدان لكل حقل من الحقول في بنية message.

بعد ذلك، حدِّد نوع رسالة Feature. يستخدم Feature الحقل string للاسم أو العنوان البريدي الخاص بمكان محدّد من خلال Point:

message Feature {
  // The name or address of the feature.
  string name = 1;

  // The point where the feature is located.
  Point location = 2;
}

طريقة الخدمة

يحتوي الملف route_guide.proto على بنية service باسم RouteGuide تحدّد طريقة واحدة أو أكثر توفّرها خدمة التطبيق.

أضِف GetFeature (وهي طريقة rpc) داخل تعريف RouteGuide. كما هو موضّح سابقًا، ستبحث هذه الطريقة عن اسم أو عنوان مكان من مجموعة إحداثيات معيّنة، لذا اطلب من GetFeature عرض Feature لـ Point معيّن:

service RouteGuide {
  // Definition of the service goes here

  // Obtains the feature at a given position.
  rpc GetFeature(Point) returns (Feature) {}
}

هذه طريقة أحادية لاستدعاء الإجراء عن بُعد: استدعاء إجراء بسيط عن بُعد يرسل فيه العميل طلبًا إلى الخادم وينتظر تلقّي رد، تمامًا مثل استدعاء دالة محلية.

4. إنشاء رمز العميل والخادم

بعد ذلك، أنشئ رمز gRPC النموذجي لكل من العميل والخادم من ملف .proto باستخدام برنامج تجميع مخازن البروتوكولات المؤقتة.

لإنشاء رموز برمجية بلغة Python في gRPC، أنشأنا grpcio-tools. ويشمل ذلك:

  1. مترجم protoc العادي الذي ينشئ رموز Python البرمجية من تعريفات message
  2. إضافة gRPC protobuf التي تنشئ رمز Python (طرق كعب العميل والخادم) من تعريفات service

سنثبّت حزمة grpcio-tools Python باستخدام pip. لننشئ بيئة افتراضية جديدة بلغة Python (venv) لعزل التبعيات الخاصة بمشروعك عن حِزم النظام:

python3 -m venv --upgrade-deps .venv

لتفعيل البيئة الافتراضية في shell bash/zsh، اتّبِع الخطوات التالية:

source .venv/bin/activate

بالنسبة إلى Windows والأصداف غير العادية، يمكنك الاطّلاع على الجدول في https://docs.python.org/3/library/venv.html#how-venvs-work.

بعد ذلك، ثبِّت grpcio-tools (يؤدي ذلك أيضًا إلى تثبيت حزمة grpcio):

pip install grpcio-tools

استخدِم الأمر التالي لإنشاء رمز النص النموذجي لـ Python:

python -m grpc_tools.protoc --proto_path=./protos  \
 --python_out=. --pyi_out=. --grpc_python_out=. \
 ./protos/route_guide.proto

سيؤدي ذلك إلى إنشاء الملفات التالية للواجهات التي حدّدناها في route_guide.proto:

  1. يحتوي route_guide_pb2.py على الرمز الذي ينشئ الفئات بشكل ديناميكي من تعريفات message.
  2. route_guide_pb2.pyi هو "ملف رمز بديل" أو "ملف تلميح نوع" تم إنشاؤه من تعريفات message. يحتوي فقط على التواقيع بدون تنفيذ. يمكن أن تستخدم بيئات التطوير المتكاملة ملفات Stub لتوفير ميزة الإكمال التلقائي ورصد الأخطاء بشكل أفضل.
  3. يتم إنشاء route_guide_pb2_grpc.py من تعريفات service ويتضمّن فئات ودوال خاصة بـ gRPC.

يتضمّن الرمز البرمجي الخاص بـ gRPC ما يلي:

  1. RouteGuideStub، الذي يمكن أن يستخدمه عميل gRPC لاستدعاء إجراءات RouteGuide عن بُعد.
  2. RouteGuideServicer، الذي يحدّد واجهة عمليات تنفيذ خدمة RouteGuide
  3. الدالة add_RouteGuideServicer_to_server المستخدَمة لتسجيل RouteGuideServicer في خادم gRPC

5- إنشاء الخدمة

لنلقِ نظرة أولاً على كيفية إنشاء خادم RouteGuide. ينقسم إنشاء خادم RouteGuide وتشغيله إلى عنصرَي عمل:

  • تنفيذ واجهة مقدّم الخدمة التي تم إنشاؤها من تعريف الخدمة باستخدام دوال تنفّذ "العمل" الفعلي للخدمة
  • تشغيل خادم gRPC على منفذ محدّد للاستماع إلى الطلبات من العملاء وإرسال الردود

يمكنك العثور على خادم RouteGuide الأوّلي في start_here/route_guide_server.py.

تنفيذ RouteGuide

يحتوي route_guide_server.py على فئة RouteGuideServicer فرعية من الفئة route_guide_pb2_grpc.RouteGuideServicer التي تم إنشاؤها:

# RouteGuideServicer provides an implementation
# of the methods of the RouteGuide service.
class RouteGuideServicer(route_guide_pb2_grpc.RouteGuideServicer):

تنفّذ RouteGuideServicer جميع طرق خدمة RouteGuide.

لنلقِ نظرة على عملية تنفيذ بسيطة لبروتوكول RPC بالتفصيل. تحصل الطريقة GetFeature على Point من العميل وتعرض معلومات الميزة المطابقة من قاعدة البيانات في Feature.

def GetFeature(self, request, context):
    feature = get_feature(self.db, request)
    if feature is None:
        return route_guide_pb2.Feature(name="", location=request)
    else:
        return feature

يتم تمرير طلب route_guide_pb2.Point إلى الطريقة لاستدعاء الإجراء عن بُعد، وكائن grpc.ServicerContext يوفّر معلومات خاصة باستدعاء الإجراء عن بُعد، مثل حدود المهلة. ويعرض الردّ route_guide_pb2.Feature.

بدء الخادم

بعد تنفيذ جميع طرق RouteGuide، تتمثّل الخطوة التالية في بدء تشغيل خادم gRPC ليتمكّن العملاء من استخدام خدمتك فعليًا:

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    route_guide_pb2_grpc.add_RouteGuideServicer_to_server(
        RouteGuideServicer(),
        server,
    )
    listen_addr = "localhost:50051"
    server.add_insecure_port(listen_addr)
    print(f"Starting server on {listen_addr}")
    server.start()
    server.wait_for_termination()

طريقة الخادم start() غير حظر. سيتم إنشاء سلسلة محادثات جديدة للتعامل مع الطلبات. في كثير من الأحيان، لن يكون لدى سلسلة التعليمات التي تستدعي server.start() أي عمل آخر لتنفيذه في الوقت الحالي. في هذه الحالة، يمكنك استدعاء server.wait_for_termination() لحظر سلسلة استدعاء الدوال البرمجية بشكل كامل إلى أن يتم إيقاف الخادم.

6. إنشاء العميل

في هذا القسم، سنلقي نظرة على كيفية إنشاء برنامج لخدمة RouteGuide. يمكنك الاطّلاع على رمز العميل الأوّلي في start_here/route_guide_client.py.

إنشاء مقالة موجزة

لاستدعاء طرق الخدمة، علينا أولاً إنشاء رمز بديل.

ننشئ مثيلاً لفئة RouteGuideStub من وحدة route_guide_pb2_grpc، تم إنشاؤها من .proto داخل ملف route_guide_client.py.

channel = grpc.insecure_channel("localhost:50051")
stub = route_guide_pb2_grpc.RouteGuideStub(channel)

طُرق خدمات الاستدعاء

بالنسبة إلى طرق طلبات استدعاء الإجراء عن بُعد (RPC) التي تعرض استجابة واحدة، والمعروفة باسم طرق response-unary، يتيح gRPC Python دلالات التحكّم المتزامنة (الحظر) وغير المتزامنة (عدم الحظر) في سير العمل.

Simple RPC

أولاً، لنحدّد Point لاستدعاء الخدمة. يجب أن يكون ذلك بسيطًا مثل إنشاء مثيل لكائن من حزمة route_guide_pb2 مع بعض السمات:

point = route_guide_pb2.Point(latitude=412346009, longitude=-744026814)

إنّ استدعاء إجراء بسيط GetFeature بشكل متزامن هو أمر مباشر تقريبًا مثل استدعاء طريقة محلية. ينتظر طلب استدعاء الإجراء عن بُعد (RPC) رد الخادم، وسيعرض إما ردًا أو يطرح استثناءً. يمكننا استدعاء الطريقة والاطّلاع على الردّ على النحو التالي:

feature = stub.GetFeature(point)
print(feature)

يمكنك فحص حقول عنصر Feature وعرض نتيجة الطلب:

if feature.name:
    print(f"Feature called '{feature.name}' at {format_point(feature.location)}")
else:
    print(f"Found no feature at at {format_point(feature.location)}")

7. للتجربة:

شغِّل الخادم:

python route_guide_server.py

من نافذة طرفية مختلفة، فعِّل البيئة الافتراضية مرة أخرى، ثم شغِّل العميل:

python route_guide_client.py

ستظهر لك نتيجة مشابهة لما يلي، مع حذف الطوابع الزمنية لتوضيح الصورة:

name: "16 Old Brook Lane, Warwick, NY 10990, USA"
location {
  latitude: 412346009
  longitude: -744026814
}

Feature called '16 Old Brook Lane, Warwick, NY 10990, USA' at latitude: 412346009, longitude: -744026814

8. الخطوات التالية