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

‫1. مقدمة

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

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

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

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

أهداف الدورة التعليمية

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

هذا الدرس التطبيقي حول الترميز مخصّص لمطوّري Python الجدد على gRPC أو الذين يريدون مراجعة 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 مكوّن إضافي لمترجم Protocol Buffer.

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 مطابق يصف أي شيء يقع في تلك الإحداثيات.

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

يُعرف Protocol Buffers باسم 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 تحدّد طريقة واحدة أو أكثر توفّرها خدمة التطبيق.

أضِف الطريقة rpc داخل تعريف RouteGuide.GetFeature كما هو موضّح سابقًا، ستبحث هذه الطريقة عن اسم أو عنوان موقع جغرافي من مجموعة إحداثيات معيّنة، لذا اطلب من 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 RPC.
  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)

إنّ إجراء مكالمة متزامنة إلى RPC البسيط 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. الخطوات التالية