gRPC-Python ile Başlarken - Akış

1. Giriş

Bu codelab'de, Python'da yazılmış bir rota eşleme uygulamasının temelini oluşturan bir istemci ve sunucu oluşturmak için gRPC-Python'ı kullanacaksınız.

Eğitimin sonunda, bir müşterinin rotasındaki özellikler hakkında bilgi almak, müşterinin rotasının özetini oluşturmak ve trafik güncellemeleri gibi rota bilgilerini sunucu ve diğer müşterilerle paylaşmak için gRPC kullanarak uzak bir sunucuya bağlanan bir istemciniz olacak.

Hizmet, istemci ve sunucu için standart kod oluşturmak üzere kullanılacak bir Protocol Buffers dosyasında tanımlanır. Böylece, bu işlevselliği uygularken zamandan ve emekten tasarruf edersiniz.

Oluşturulan bu kod, yalnızca sunucu ile istemci arasındaki iletişimin karmaşıklıklarını değil, aynı zamanda veri serileştirme ve seri durumdan çıkarma işlemlerini de ele alır.

Neler öğreneceksiniz?

  • Hizmet API'sini tanımlamak için Protocol Buffers'ı kullanma.
  • Otomatik kod oluşturma kullanarak bir Protokol Arabellek tanımından gRPC tabanlı istemci ve sunucu oluşturma.
  • gRPC ile istemci-sunucu akışı iletişimi hakkında bilgi sahibi olmanız gerekir.

Bu codelab, gRPC'ye yeni başlayan veya gRPC ile ilgili bilgilerini tazelemek isteyen Python geliştiricilerin yanı sıra dağıtılmış sistemler oluşturmakla ilgilenen herkes için hazırlanmıştır. Daha önce gRPC deneyimi gerekmez.

2. Başlamadan önce

İhtiyacınız olanlar

  • Python 3.9 veya sonraki sürümler. Python 3.13'ü öneririz. Platforma özel yükleme talimatları için Python Kurulumu ve Kullanımı başlıklı makaleyi inceleyin. Alternatif olarak, uv veya pyenv gibi araçları kullanarak sistem dışı bir Python yükleyin.
  • Python paketlerini yüklemek için pip.
  • Python sanal ortamları oluşturmak için venv.

ensurepip ve venv paketleri, Python Standart Kitaplığı'nın bir parçasıdır ve genellikle varsayılan olarak kullanılabilir.

Ancak Ubuntu da dahil olmak üzere bazı Debian tabanlı dağıtımlar, python'u yeniden dağıtırken bunları hariç tutmayı tercih eder. Paketleri yüklemek için şu komutu çalıştırın:

sudo apt install python3-pip python3-venv

Kodu alın

Bu codelab, öğrenme sürecinizi kolaylaştırmak için başlamanıza yardımcı olacak önceden oluşturulmuş bir kaynak kodu iskeleti sunar. Aşağıdaki adımlar, grpc_tools.protoc Protocol Buffer derleyici eklentisini kullanarak gRPC kodu oluşturma da dahil olmak üzere uygulamayı tamamlama konusunda size yol gösterecektir.

grpc-codelabs

Bu codelab'in iskele kaynak kodu, codelabs/grpc-python-streaming/start_here dizininde bulunur. Kodu kendiniz uygulamayı tercih etmezseniz tamamlanmış kaynak kodu completed dizininde bulabilirsiniz.

Öncelikle codelab çalışma dizinini oluşturun ve bu dizine gidin:

mkdir grpc-python-streaming && cd grpc-python-streaming

Codelab'i indirip ayıklayın:

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-streaming/start_here

Alternatif olarak, yalnızca codelab dizinini içeren .zip dosyasını indirebilir ve manuel olarak sıkıştırmasını açabilirsiniz.

3. İletileri ve hizmetleri tanımlama

İlk adımınız, Protocol Buffers'ı kullanarak uygulamanın gRPC hizmetini, RPC yöntemini, istek ve yanıt mesajı türlerini tanımlamaktır. Hizmetinizde sunulacaklar:

  • Sunucunun uyguladığı ve istemcinin çağırdığı ListFeatures, RecordRoute ve RouteChat adlı RPC yöntemleri.
  • RPC yöntemleri çağrılırken istemci ile sunucu arasında değiştirilen veri yapıları olan Point, Feature, Rectangle, RouteNote ve RouteSummary ileti türleri.

Bu RPC yöntemleri ve mesaj türleri, sağlanan kaynak kodun protos/route_guide.proto dosyasında tanımlanır.

Protocol Buffers genellikle protobuf olarak bilinir. gRPC terminolojisi hakkında daha fazla bilgi için gRPC'nin Temel kavramlar, mimari ve yaşam döngüsü başlıklı makalesine bakın.

İleti türlerini tanımlama

Kaynak kodun protos/route_guide.proto dosyasında önce Point mesaj türünü tanımlayın. Point, haritadaki bir enlem-boylam koordinat çiftini temsil eder. Bu codelab'de koordinatlar için tam sayıları kullanın:

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

1 ve 2 sayıları, message yapısındaki her alan için benzersiz kimlik numaralarıdır.

Ardından, Feature mesaj türünü tanımlayın. Bir Feature, Point ile belirtilen bir konumdaki bir şeyin adı veya posta adresi için string alanını kullanır:

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

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

Bir alan içindeki birden fazla noktanın istemciye aktarılabilmesi için, enlem-boylam dikdörtgenini temsil eden bir Rectangle mesajına ihtiyacınız vardır. Bu mesaj, çapraz olarak zıt iki nokta lo ve hi olarak gösterilir:

message Rectangle {
  // One corner of the rectangle.
  Point lo = 1;

  // The other corner of the rectangle.
  Point hi = 2;
}

Ayrıca, belirli bir noktada gönderilen mesajı temsil eden bir RouteNote mesajı:

message RouteNote {
  // The location from which the message is sent.
  Point location = 1;

  // The message to be sent.
  string message = 2;
}

Son olarak, RouteSummary mesajı göndermeniz gerekir. Bu mesaj, sonraki bölümde açıklanan bir RecordRoute RPC'ye yanıt olarak alınır. Alınan bireysel puanların sayısı, algılanan özelliklerin sayısı ve her nokta arasındaki mesafenin kümülatif toplamı olarak kapsanan toplam mesafeyi içerir.

message RouteSummary {
  // The number of points received.
  int32 point_count = 1;

  // The number of known features passed while traversing the route.
  int32 feature_count = 2;

  // The distance covered in metres.
  int32 distance = 3;

  // The duration of the traversal in seconds.
  int32 elapsed_time = 4;
}

Hizmet yöntemlerini tanımlama

Bir hizmeti tanımlamak için .proto dosyanızda adlandırılmış bir hizmet belirtirsiniz. route_guide.proto dosyasında, uygulamanın hizmeti tarafından sağlanan bir veya daha fazla yöntemi tanımlayan RouteGuide adlı bir service yapısı bulunur.

Hizmet tanımınızda RPC yöntemleri tanımladığınızda, bunların istek ve yanıt türlerini belirtirsiniz. Bu codelab bölümünde şunları tanımlayalım:

ListFeatures

Belirtilen Rectangle içinde bulunan Feature nesnelerini alır. Dikdörtgen geniş bir alanı kaplayıp çok sayıda özellik içerebileceğinden sonuçlar tek seferde döndürülmek yerine yayınlanır.

Bu uygulamada sunucu tarafı akış RPC'si kullanacaksınız: İstemci, sunucuya bir istek gönderir ve mesaj dizisini okumak için bir akış alır. İstemci, döndürülen akıştan mesaj kalmayana kadar okur. Örneğimizde de görebileceğiniz gibi, yanıt türünden önce akış anahtar kelimesini yerleştirerek sunucu tarafı akış yöntemini belirtirsiniz.

rpc ListFeatures(Rectangle) returns (stream Feature) {}

RecordRoute

Geçilen bir rotadaki nokta akışını kabul eder ve geçiş tamamlandığında RouteSummary değerini döndürür.

Bu durumda istemci tarafı akış RPC uygundur: İstemci, bir mesaj dizisi yazar ve bunları yine sağlanan bir akışı kullanarak sunucuya gönderir. İstemci, mesajları yazmayı bitirdikten sonra sunucunun hepsini okumasını ve yanıtını döndürmesini bekler. İstemci tarafı yayın yöntemini, yayın anahtar kelimesini istek türünün önüne yerleştirerek belirtirsiniz.

rpc RecordRoute(stream Point) returns (RouteSummary) {}

RouteChat

Bir rota geçilirken gönderilen RouteNotes akışını kabul ederken diğer RouteNotes'ları (ör. diğer kullanıcılardan gelen) alır.

Bu, iki yönlü akışın tam olarak kullanım alanıdır. Her iki tarafın da okuma/yazma akışı kullanarak bir ileti dizisi gönderdiği çift yönlü akış RPC'si. İki akış bağımsız olarak çalışır. Bu nedenle, istemciler ve sunucular istedikleri sırada okuma ve yazma işlemi yapabilir. Örneğin, sunucu yanıtlarını yazmadan önce tüm istemci mesajlarını almayı bekleyebilir veya alternatif olarak bir mesajı okuyup ardından bir mesaj yazabilir ya da okuma ve yazma işlemlerini başka bir şekilde birleştirebilir. Her akıştaki iletilerin sırası korunur. Bu tür bir yöntemi, hem istekten hem de yanıttan önce yayın anahtar kelimesini yerleştirerek belirtirsiniz.

rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}

4. İstemci ve sunucu kodunu oluşturma

Ardından, protokol arabellek derleyicisini kullanarak .proto dosyasından hem istemci hem de sunucu için standart gRPC kodunu oluşturun.

gRPC Python kod üretimi için grpcio-tools'u oluşturduk. Şunları içermektedir:

  1. message tanımlarından Python kodu oluşturan normal protoc derleyicisi.
  2. service tanımlarından Python kodu (istemci ve sunucu saplamaları) oluşturan gRPC protobuf eklentisi.

grpcio-tools Python paketini pip kullanarak yükleyeceğiz. Projenizin bağımlılıklarını sistem paketlerinden ayırmak için yeni bir Python sanal ortamı (venv) oluşturalım:

python3 -m venv --upgrade-deps .venv

Sanal ortamı bash/zsh kabuğunda etkinleştirmek için:

source .venv/bin/activate

Windows ve standart dışı kabuklar için https://docs.python.org/3/library/venv.html#how-venvs-work adresindeki tabloya bakın.

Ardından, grpcio-tools'u yükleyin (bu işlem grpcio paketini de yükler):

pip install grpcio-tools

Python standart kodunu oluşturmak için aşağıdaki komutu kullanın:

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

Bu işlem, route_guide.proto içinde tanımladığımız arayüzler için aşağıdaki dosyaları oluşturur:

  1. route_guide_pb2.py, message tanımlarından oluşturulan sınıfları dinamik olarak oluşturan kodu içerir.
  2. route_guide_pb2.pyi, message tanımlarından oluşturulan bir "stub dosyası" veya "tür ipucu dosyası"dır. Yalnızca imzaları içerir ve uygulama içermez. Stub dosyaları, IDE'ler tarafından daha iyi otomatik tamamlama ve hata algılama sağlamak için kullanılabilir.
  3. route_guide_pb2_grpc.py, service tanımlarından oluşturulur ve gRPC'ye özgü sınıflar ve işlevler içerir.

gRPC'ye özel kod şunları içerir:

  1. RouteGuideStub, gRPC istemcisi tarafından RouteGuide RPC'lerini çağırmak için kullanılabilir.
  2. RouteGuideServicer hizmetinin uygulamaları için arayüzü tanımlar.RouteGuide
  3. add_RouteGuideServicer_to_server işlevi, RouteGuideServicer öğesini gRPC sunucusuna kaydetmek için kullanılır.

5. Sunucuyu oluşturma

Öncelikle RouteGuide sunucuyu nasıl oluşturduğunuza bakalım. RouteGuide sunucusu oluşturma ve çalıştırma iki iş öğesine ayrılır:

  • Hizmet tanımımızdan oluşturulan hizmet arayüzünü, hizmetin gerçek "işini" yapan işlevlerle uygulama.
  • İstemcilerden gelen istekleri dinlemek ve yanıtları iletmek için bir gRPC sunucusu çalıştırma.

route_guide_server.py konusuna göz atalım.

RouteGuide'ı uygulama

route_guide_server.py, oluşturulan sınıf route_guide_pb2_grpc.RouteGuideServicer'ı alt sınıfa ayıran bir RouteGuideServicer sınıfına sahip:

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

RouteGuideServicer, tüm RouteGuide hizmet yöntemlerini uygular.

Sunucu tarafı akış RPC'si

ListFeatures, istemciye birden fazla Feature gönderen bir yanıt akışı RPC'sidir:

def ListFeatures(self, request, context):
    """List all features contained within the given Rectangle."""
    left = min(request.lo.longitude, request.hi.longitude)
    right = max(request.lo.longitude, request.hi.longitude)
    top = max(request.lo.latitude, request.hi.latitude)
    bottom = min(request.lo.latitude, request.hi.latitude)
    for feature in self.db:
        lat, lng = feature.location.latitude, feature.location.longitude
        if left <= lng <= right and bottom <= lat <= top:
            yield feature

Burada istek mesajı, istemcinin Feature bulmak istediği bir route_guide_pb2.Rectangle'dır. Yöntem, tek bir yanıt döndürmek yerine sıfır veya daha fazla yanıt verir.

İstemci tarafı yayın RPC'si

İstek akışı yöntemi RecordRoute, istek değerlerinin yineleyicisini kullanır ve tek bir yanıt değeri döndürür.

def RecordRoute(self, request_iterator, context):
    """Calculate statistics about the trip composed of Points."""
    point_count = 0
    feature_count = 0
    distance = 0.0
    prev_point = None

    start_time = time.time()
    for point in request_iterator:
        point_count += 1
        if get_feature(self.db, point):
            feature_count += 1
        if prev_point:
            distance += get_distance(prev_point, point)
        prev_point = point

    elapsed_time = time.time() - start_time
    return route_guide_pb2.RouteSummary(
        point_count=point_count,
        feature_count=feature_count,
        distance=int(distance),
        elapsed_time=int(elapsed_time),
    )

Çift yönlü akış RPC'si

Son olarak, çift yönlü akış RPC'mize RouteChat() göz atalım:

def RouteChat(self, request_iterator, context):
    """
    Receive a stream of message/location pairs, and responds with
    a stream of all previous messages for the given location.
    """
    prev_notes = []
    for new_note in request_iterator:
        for prev_note in prev_notes:
            if prev_note.location == new_note.location:
                yield prev_note
        prev_notes.append(new_note)

Bu yöntemin semantiği, istek akışı yöntemi ve yanıt akışı yönteminin semantiğinin birleşimidir. İstek değerlerinin yineleyicisini alır ve kendisi de yanıt değerlerinin yineleyicisidir.

Sunucuyu başlatma

Tüm RouteGuide yöntemlerini uyguladıktan sonraki adım, istemcilerin hizmetinizi gerçekten kullanabilmesi için bir gRPC sunucusu başlatmaktır:

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()

Sunucu start() yöntemi engelleme yapmaz. İstekleri işlemek için yeni bir iş parçacığı oluşturulur. server.start() işlevini çağıran iş parçacığı, bu süre zarfında genellikle başka bir iş yapmaz. Bu durumda, sunucu sonlandırılana kadar arama iş parçacığını temiz bir şekilde engellemek için server.wait_for_termination() numaralı telefonu arayabilirsiniz.

6. İstemciyi oluşturma

route_guide_client.py konusuna göz atalım.

Taslak oluşturma

Hizmet yöntemlerini çağırmak için önce bir taslak oluşturmamız gerekir.

RouteGuideStub sınıfını, route_guide_pb2_grpc modülünden oluşturuyoruz. Bu modül, .proto. In run() yöntemimizden oluşturulmuştur:

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

Burada channel'nın bağlam yöneticisi olarak kullanıldığını ve yorumlayıcı with bloğundan çıktığında otomatik olarak kapatılacağını unutmayın.

Hizmet yöntemlerini arama

Tek bir yanıt döndüren RPC yöntemleri ("response-unary" yöntemleri) için gRPC Python hem eşzamanlı (engelleme) hem de eşzamansız (engellemeyen) kontrol akışı semantiğini destekler. Yanıt akışı RPC yöntemleri için çağrılar, yanıt değerlerinin yineleyicisini hemen döndürür. Bu yineleyicinin next() yöntemine yapılan çağrılar, yineleyiciden elde edilecek yanıt kullanılabilir hale gelene kadar engellenir.

Sunucu tarafı akış RPC'si

Yanıt akışı ListFeatures çağırmak, sıra türleriyle çalışmaya benzer:

def guide_list_features(stub):
    _lo = route_guide_pb2.Point(latitude=400000000, longitude=-750000000)
    _hi = route_guide_pb2.Point(latitude=420000000, longitude=-730000000)
    rectangle = route_guide_pb2.Rectangle(
        lo=_lo,
        hi=_hi,
    )
    print("Looking for features between 40, -75 and 42, -73")

    features = stub.ListFeatures(rectangle)
    for feature in features:
        print(
            f"Feature called '{feature.name}'"
            f" at {format_point(feature.location)}"
        )

İstemci tarafı yayın RPC'si

request-streaming RecordRoute işlevini çağırmak, yerel bir yönteme yineleyici iletmek gibidir. Tek bir yanıt döndüren yukarıdaki basit RPC gibi, senkron olarak çağrılabilir:

def guide_record_route(stub):
    feature_list = route_guide_resources.read_route_guide_database()
    route_iterator = generate_route(feature_list)

    route_summary = stub.RecordRoute(route_iterator)
    print(f"Finished trip with {route_summary.point_count} points")
    print(f"Passed {route_summary.feature_count} features")
    print(f"Traveled {route_summary.distance} meters")
    print(f"It took {route_summary.elapsed_time} seconds")

Çift yönlü akış RPC'si

Çift yönlü akış RouteChat çağrısı, (hizmet tarafında olduğu gibi) istek akışı ve yanıt akışı semantiğinin bir kombinasyonuna sahiptir.

İstek mesajlarını oluşturun ve yield kullanarak bunları tek tek gönderin.

def generate_notes():
    home = route_guide_pb2.Point(latitude=1, longitude=1)
    work = route_guide_pb2.Point(latitude=2, longitude=2)
    notes = [
        make_route_note("Departing from home", home),
        make_route_note("Arrived at work", work),
        make_route_note("Having lunch at work", work),
        make_route_note("Departing from work", work),
        make_route_note("Arrived home", home),
    ]
    for note in notes:
        print(
            f"Sending RouteNote for {format_point(note.location)}:"
            f" {note.message}"
        )
        yield note
        # Sleep to simulate moving from one point to another.
        # Only for demonstrating the order of the messages.
        time.sleep(0.1)

Sunucu yanıtlarını alma ve işleme:

def guide_route_chat(stub):
    responses = stub.RouteChat(generate_notes())
    for response in responses:
        print(
            "< Found previous note at"
            f" {format_point(response.location)}: {response.message}"
        )

Yardımcı yöntemleri çağırma

Çalıştırma sırasında, az önce oluşturduğumuz yöntemleri yürütün ve stub değerini iletin.

print("-------------- ListFeatures --------------")
guide_list_features(stub)
print("-------------- RecordRoute --------------")
guide_record_route(stub)
print("-------------- RouteChat --------------")
guide_route_chat(stub)

7. Deneyin

Sunucuyu çalıştırın:

python route_guide_server.py

Farklı bir terminalden sanal ortamı tekrar etkinleştirin (source .venv/bin/activate)) ve istemciyi çalıştırın:

python route_guide_client.py

Çıkışa göz atalım.

ListFeatures

Öncelikle özelliklerin listesini görürsünüz. Her özellik, istenen dikdörtgenin içinde olduğu tespit edildiğinde sunucudan (sunucu tarafı akış RPC'si) aktarılır:

-------------- ListFeatures --------------
Looking for features between 40, -75 and 42, -73
Feature called 'Patriots Path, Mendham, NJ 07945, USA' at (lat=407838351, lng=-746143763)
Feature called '101 New Jersey 10, Whippany, NJ 07981, USA' at (lat=408122808, lng=-743999179)
Feature called 'U.S. 6, Shohola, PA 18458, USA' at (lat=413628156, lng=-749015468)
Feature called '5 Conners Road, Kingston, NY 12401, USA' at (lat=419999544, lng=-740371136)
...

RecordRoute

İkincisi, RecordRoute, istemciden sunucuya aktarılan rastgele ziyaret edilen noktaların listesini (istemci taraflı akış RPC) gösterir:

-------------- RecordRoute --------------
Visiting point (lat=410395868, lng=-744972325)
Visiting point (lat=404310607, lng=-740282632)
Visiting point (lat=403966326, lng=-748519297)
Visiting point (lat=407586880, lng=-741670168)
Visiting point (lat=406589790, lng=-743560121)
Visiting point (lat=410322033, lng=-747871659)
Visiting point (lat=415464475, lng=-747175374)
Visiting point (lat=407586880, lng=-741670168)
Visiting point (lat=402647019, lng=-747071791)
Visiting point (lat=414638017, lng=-745957854)

İstemci, ziyaret edilen tüm noktaları yayınlamayı tamamladıktan sonra sunucudan yayınlanmayan bir yanıt (tekli RPC) alır. Bu yanıtta, müşterinin tam rotası üzerinde yapılan hesaplamaların özeti yer alır.

Finished trip with 10 points
Passed 10 features
Traveled 654743 meters
It took 0 seconds

RouteChat

Son olarak, RouteChat çıkışı çift yönlü akışı gösterir. İstemci, home veya work noktalarını "ziyaret ettiğinde", sunucuya RouteNote göndererek nokta için bir not kaydeder. Bir nokta daha önce ziyaret edilmişse sunucu, bu noktayla ilgili tüm önceki notları geri aktarır.

-------------- RouteChat --------------
Sending RouteNote for (lat=1, lng=1): Departing from home
Sending RouteNote for (lat=2, lng=2): Arrived at work
Sending RouteNote for (lat=2, lng=2): Having lunch at work
< Found previous note at (lat=2, lng=2): Arrived at work
Sending RouteNote for (lat=2, lng=2): Departing from work
< Found previous note at (lat=2, lng=2): Arrived at work
< Found previous note at (lat=2, lng=2): Having lunch at work
Sending RouteNote for (lat=1, lng=1): Arrived home
< Found previous note at (lat=1, lng=1): Departing from home

8. Sırada ne var?