در مقالهی
قبلی بطور کلی با Protocol Buffers آشنا شدیم. در این قسمت با
gRPC آشنا شده و همچنین به پیاده سازی یک سرور و کلاینت، با استفاده از gRPC پرداخته که توسط آن به تبادل اطلاعات با یکدیگر میپردازند.
gRPC یک فریم ورک مدرن و متن باز با کارآیی بالاست. توسط گوگل پیاده سازی شده و جزء انجمن CNCF میباشد (مثل Docker & Kubernetes) که بر روی سیستم عاملهای متعددی اجرا میشود. به صورت خیلی کارا میتواند سرویسهای متعددی را به یکدیگر متصل کرده و همچنین از امکاناتی همچون load balancing, monitoring, tracing, health checking, authentication به صورت خیلی ساده پشتیبانی میکند. بسایر سریع و همچنین Low Latency است. مستقل از یک زبان برنامه نویسی خاص هست و برای streaming بسیار مناسب است و همچنین برای سیستمهای توزیع شده پیشنهاد میشود و به راحتی قابل توسعه و نگهداری است.
راجع به مزایای gRPC بسیار صحبت کردیم. برای طراحی سرویسهای متعددی که با یکدیگر در ارتباط هستند، مناسب میباشد. از HTTP/2 به صورت پیشفرض استفاده میکند (راجع به تفاوت HTTP/2 و HTTP1
اینجا را مطالعه بفرمایید).
شاید بزرگترین مشکلی که در حال حاضر دارد این است که REST را پشتیبانی نمیکند. بدین معنا که شما از طریق browser نمیتوانید یک در خواست را به یک سرور پیاده سازی شده توسط gRPC بصورت مستقیم ارسال کنید. راه حل اول برای حل این مشکل، پیاده سازی یک restful gateway با ابزار دلخواه خود و بقیه سرویسها بعد از آن به هم از طریق gRPC ارتباط برقرا میکنند، یا راه حل بهتر اینکه از
grpc-gateway استفاده شود. ابزاری است که به کمک آن میتوانید سیستم خود را با REST یکپارچه سازی نمایید (هر چند راههای دیگری برای وصل شدن از مرورگر به یک سرور gRPC با استفاده از کتابخانههای third party میسر شده، اما خارج از موضوع بحث است و مطالعهی بیشتر را به خواننده واگذار میکنم)
قدم اول در پیاده سازی یک سرور/کلاینت با استفاده از gRPC، آشنایی با protocol buffers هست. برای آشنایی، به مقالهی قبلی رجوع فرمایید. تمامی پیاده سازیهای ما از روی کدهای تولید شده از تعاریف protocol bufferهایی هست که نوشتهایم.
حال فرض کنید میخواهیم یک سرور gRPC را با استفاده از #C نوشته و پیاده سازی نماییم:
۱) قدم اول قطعا نوشتن protobuf میباشد. همانطور که در مقالهی قبلی ذکر شده است، به صورت زیر، مدل و همچنین متدهای لازم را معرفی مینماییم و نام آن را helloworld.proto قرار میدهیم.
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
مدل و سرویسها بصورت واضحی نوشته شدهاند؛ SayHello با ورودی HelloRequest و خروجی HelloReply تعیین شدهاست.
۲) حالا کافی است یک پروژهی Console را ساخته و ابتدا پکیجهای زیر را نصب نماییم.
Google.Protobuf
grpc
Grpc.Tools
از طریق Grpc.Tools میتوانیم protobufهای خود را بصورت خودکار بعد از build تولید نماییم. در csproj آیتم زیر را اضافه کرده و آدرس protobuf را تعیین مینماییم.
<ItemGroup>
<Protobuf Include="helloworld.proto" />
</ItemGroup>
حال کافی است کدهای زیر را جایگزین نماییم:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Helloworld;
using Grpc.Core;
namespace ServerGrpc
{
class GreeterImpl : Greeter.GreeterBase
{
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
System.Console.WriteLine("request made!");
return Task.FromResult(new HelloReply { Message = "Hello " + request.Name });
}
}
class Program
{
const int Port = 50051;
public static void Main(string[] args)
{
Server server = new Server
{
Services = { Greeter.BindService(new GreeterImpl()) },
Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) }
};
server.Start();
Console.WriteLine("Greeter server listening on port " + Port);
Console.WriteLine("Press any key to stop the server...");
Console.ReadKey();
server.ShutdownAsync().Wait();
}
}
}
همانطور که مشاهده میکنید، مدلها و سریسها بصورت خودکار تولید شدهاند (ضمن اینکه میتوانستیم بصورت دستی نیز protobuf را برای زبان دلخواه خود تولید نماییم).
سرور را بر روی پورت مشخصی ایجاد کرده و همچنین سرویس مورد نظرمان را پیاده سازی کردهایم؛ به صورت فوق همه چیز به سادهترین صورت در نظر گرفته شده است.
gRPC به صورت خودکار از پروتکل امن ssl استفاده میکند؛ اما برای راحتی کار ما از آن استفاده نکردهایم.
نکته: فایلهای generate شده را از طریق آدرس زیر میتوانید پیدا کنید:
obj/Debug/netcoreapp2.2(یا نسخهی دیگری که استفاده میکنید)
حالا بنا داریم یک کلاینت را با یک زبان برنامه نویسی کاملا مجزا نوشته و به سرور grpc متصل شویم. این کلاینت را با زبان Go خواهیم نوشت (بدیهی است میتوان جای زبانهای برنامه نویسی کلاینت و سرور را تغییر داد).
نکته: خیلی وارد جزیات زبان Go نمیشویم و فقط اشارهای به موارد کلی خواهیم کرد.
ابتدا باید از روی protobuf کد مربوط به Go را تولید نماییم؛ به صورت زیر:
protoc helloworld.proto --go_out=plugins=grpc:.
فرض کنید فایل generate شده در پوشهی proto قرار گرفته به نام "helloworld.pb.go"
یک فایل به نام main.go ساخته و کدهای زیر را وارد مینماییم.
package main
import (
"fmt"
"golang.org/x/net/context"
"google.golang.org/grpc"
"gosample/proto"
)
func main() {
initial()
}
func initial(){
conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
defer conn.Close()
client := helloworld.NewGreeterClient(conn)
data, _ := client.SayHello(context.Background(), &helloworld.HelloRequest{Name : "Ali"})
fmt.Println(data)
}
به سرور به صورت insecure متصل شده ایم؛ آخر برنامه connection را میبندیم و SayHello را فراخوانی کرده و جواب را بر روی خروجی نمایش میدهیم.
نکته: gosample اسم پروژهای است که من ساختهام و proto آدرس پوشهای است که فایل تولید شدهی grpc داخل آن قرار گرفتهاست؛ بقیه نیز کتابخانههای لازم برای کار با grpc میباشد.
نکته: gRPC برای streaming دیتا بسیار مناسب است (هم یکطرفه و همینطور دو طرفه).
نکته: به دلیل سادگی کار با ابزارهای مختلف، انتخاب خیلی خوبی برای سیستمهای توزیع شدهاست؛ همانطور که مشاهده کردید به راحتی قابلیت تعامل بین زبانهای برنامه نویسی متعددی برقرار است.
نکتهی آخر: از وارد شدن به موارد ریز اجتناب کردهام و صرفا این مقاله جهت آشنایی و دید کلی نسبت به این موضوع در نظر گرفته شدهاست.