شروع کار با gRPC-Python

۱. مقدمه

در این آزمایشگاه کد، شما از gRPC-Python برای ایجاد یک کلاینت و سرور استفاده خواهید کرد که پایه و اساس یک برنامه مسیریابی نوشته شده در پایتون را تشکیل می‌دهند.

در پایان این آموزش، شما یک کلاینت خواهید داشت که با استفاده از gRPC به یک سرور راه دور متصل می‌شود تا نام یا آدرس پستی آنچه را که در مختصات خاص روی نقشه قرار دارد، دریافت کند. یک برنامه کامل ممکن است از این طراحی کلاینت-سرور برای شمارش یا خلاصه کردن نقاط مورد علاقه در طول یک مسیر استفاده کند.

این سرویس در یک فایل Protocol Buffers تعریف شده است که برای تولید کد تکراری برای کلاینت و سرور استفاده می‌شود تا بتوانند با یکدیگر ارتباط برقرار کنند و در زمان و تلاش شما برای پیاده‌سازی آن قابلیت صرفه‌جویی شود.

این کد تولید شده نه تنها پیچیدگی‌های ارتباط بین سرور و کلاینت، بلکه سریال‌سازی و از سریال‌زدایی داده‌ها را نیز برطرف می‌کند.

آنچه یاد خواهید گرفت

  • نحوه استفاده از بافرهای پروتکل برای تعریف یک API سرویس.
  • نحوه ساخت یک کلاینت و سرور مبتنی بر gRPC از تعریف Protocol Buffers با استفاده از تولید خودکار کد.
  • آشنایی با ارتباطات کلاینت-سرور با gRPC

این آزمایشگاه کد برای توسعه‌دهندگان پایتون که تازه با gRPC آشنا شده‌اند یا به دنبال مرور gRPC هستند، یا هر کسی که به ساخت سیستم‌های توزیع‌شده علاقه‌مند است، مناسب است. هیچ تجربه قبلی در gRPC لازم نیست.

۲. قبل از شروع

آنچه نیاز دارید

  • پایتون ۳.۹ یا بالاتر. ما پایتون ۳.۱۳ را توصیه می‌کنیم. برای دستورالعمل‌های نصب مخصوص هر پلتفرم، به بخش «راه‌اندازی و کاربرد پایتون» مراجعه کنید. روش دیگر، نصب یک پایتون غیرسیستمی با استفاده از ابزارهایی مانند uv یا pyenv است.
  • pip برای نصب بسته‌های پایتون
  • venv برای ایجاد محیط‌های مجازی پایتون.

بسته‌های ensurepip و venv بخشی از کتابخانه استاندارد پایتون هستند و معمولاً به صورت پیش‌فرض در دسترس هستند.

با این حال، برخی از توزیع‌های مبتنی بر دبیان (از جمله اوبونتو) هنگام توزیع مجدد پایتون، آنها را حذف می‌کنند. برای نصب بسته‌ها، دستور زیر را اجرا کنید:

sudo apt install python3-pip python3-venv

کد را دریافت کنید

برای ساده‌سازی یادگیری شما، این codelab یک چارچوب کد منبع از پیش ساخته شده برای کمک به شما در شروع کار ارائه می‌دهد. مراحل زیر شما را در تکمیل برنامه، از جمله تولید کد gRPC با استفاده از افزونه کامپایلر grpc_tools.protoc Protocol Buffer، راهنمایی می‌کند.

grpc-codelabs

کد منبع scaffold برای این codelab در دایرکتوری codelabs/grpc-python-getting-started/start_here موجود است. اگر ترجیح می‌دهید خودتان کد را پیاده‌سازی نکنید، کد منبع تکمیل‌شده در دایرکتوری completed موجود است.

ابتدا، دایرکتوری کاری codelab را ایجاد کنید و با دستور cd به آن وارد شوید:

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 که فقط شامل دایرکتوری codelab است را دانلود کرده و به صورت دستی آن را از حالت فشرده خارج کنید.

۳. تعریف سرویس

اولین قدم شما تعریف سرویس gRPC برنامه، متد RPC آن و انواع پیام‌های درخواست و پاسخ آن با استفاده از زبان تعریف رابط Protocol Buffers است. سرویس شما موارد زیر را ارائه خواهد داد:

  • یک متد RPC به نام GetFeature که سرور پیاده‌سازی می‌کند و کلاینت آن را فراخوانی می‌کند.
  • انواع پیام‌های Point و Feature ساختارهای داده‌ای هستند که هنگام استفاده از متد GetFeature بین کلاینت و سرور رد و بدل می‌شوند. کلاینت مختصات نقشه را به عنوان یک Point در درخواست GetFeature خود به سرور ارائه می‌دهد و سرور با یک Feature مربوطه که هر آنچه را که در آن مختصات قرار دارد توصیف می‌کند، پاسخ می‌دهد.

این متد RPC و انواع پیام‌های آن، همگی در فایل 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) {}
}

این یک روش RPC تکی است: یک RPC ساده که در آن کلاینت یک درخواست به سرور ارسال می‌کند و منتظر پاسخ می‌ماند، درست مانند یک فراخوانی تابع محلی.

۴. کد کلاینت و سرور را تولید کنید

در مرحله بعد، کد gRPC پیش‌فرض را برای کلاینت و سرور از فایل .proto با استفاده از کامپایلر بافر پروتکل تولید کنید.

برای تولید کد پایتون gRPC، ما grpcio-tools را ایجاد کردیم که شامل موارد زیر است:

  1. کامپایلر پروتکل معمولی که کد پایتون را از تعاریف message تولید می‌کند.
  2. افزونه‌ی gRPC protobuf که کد پایتون (متن‌های کلاینت و سرور) را از تعاریف service تولید می‌کند.

ما بسته پایتون grpcio-tools را با استفاده از pip نصب خواهیم کرد. بیایید یک محیط مجازی پایتون جدید (venv) ایجاد کنیم تا وابستگی‌های پروژه شما از بسته‌های سیستمی جدا شود:

python3 -m venv --upgrade-deps .venv

برای فعال کردن محیط مجازی در شل bash/zsh:

source .venv/bin/activate

برای پوسته‌های ویندوز و غیر استاندارد، به جدول https://docs.python.org/3/library/venv.html#how-venvs-work مراجعه کنید.

سپس، grpcio-tools را نصب کنید (این کار بسته grpcio را نیز نصب می‌کند):

pip install grpcio-tools

برای تولید کد قالبی پایتون از دستور زیر استفاده کنید:

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 یک "فایل stub" یا "فایل type hint" است که از تعاریف message تولید شده است. این فایل فقط شامل امضاها بدون هیچ پیاده‌سازی است. فایل‌های stub می‌توانند توسط IDEها برای ارائه تکمیل خودکار بهتر و تشخیص خطا استفاده شوند.
  3. route_guide_pb2_grpc.py از تعاریف service تولید می‌شود و شامل کلاس‌ها و توابع مخصوص gRPC است.

کد مخصوص gRPC شامل موارد زیر است:

  1. RouteGuideStub ، که می‌تواند توسط یک کلاینت gRPC برای فراخوانی RPCهای RouteGuide استفاده شود.
  2. RouteGuideServicer که رابط کاربری پیاده‌سازی‌های سرویس RouteGuide را تعریف می‌کند.
  3. تابع add_RouteGuideServicer_to_server که برای ثبت یک RouteGuideServicer در یک سرور gRPC استفاده می‌شود.

۵. سرویس را ایجاد کنید

ابتدا بیایید نگاهی به نحوه ایجاد یک سرور 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 برای RPC و یک شیء grpc.ServicerContext که اطلاعات خاص RPC مانند محدودیت‌های timeout را ارائه می‌دهد، ارسال می‌کند. این متد یک پاسخ 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() را فراخوانی کنید تا نخ فراخوانی‌کننده را تا زمان خاتمه سرور مسدود کنید.

۶. مشتری را ایجاد کنید

در این بخش، به ایجاد یک کلاینت برای سرویس RouteGuide خود خواهیم پرداخت. می‌توانید کد اولیه کلاینت را در start_here/route_guide_client.py مشاهده کنید.

ایجاد یک مقاله خرد

برای فراخوانی متدهای سرویس، ابتدا باید یک stub ایجاد کنیم.

ما کلاس RouteGuideStub از ماژول route_guide_pb2_grpc را که از .proto درون فایل route_guide_client.py تولید شده است، نمونه‌سازی می‌کنیم.

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

روش‌های فراخوانی سرویس

برای متدهای RPC که یک پاسخ واحد برمی‌گردانند - که به عنوان متدهای پاسخ-یکتایی شناخته می‌شوند - gRPC پایتون از هر دو معنای جریان کنترل همزمان (مسدودکننده) و غیرهمزمان (غیرمسدودکننده) پشتیبانی می‌کند.

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)}")

۷. امتحانش کنید

سرور را اجرا کنید:

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

۸. قدم بعدی چیست؟