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
veRouteChat
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
veRouteSummary
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:
message
tanımlarından Python kodu oluşturan normal protoc derleyicisi.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:
route_guide_pb2.py
,message
tanımlarından oluşturulan sınıfları dinamik olarak oluşturan kodu içerir.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.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:
RouteGuideStub
, gRPC istemcisi tarafından RouteGuide RPC'lerini çağırmak için kullanılabilir.RouteGuideServicer
hizmetinin uygulamaları için arayüzü tanımlar.RouteGuide
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?
- gRPC'nin işleyiş şeklini Introduction to gRPC (gRPC'ye Giriş) ve Core concepts (Temel kavramlar) başlıklı makalelerden öğrenebilirsiniz.
- Temel bilgiler eğitimini inceleyin.
- Python API referansını inceleyin.