در معماری سیستمهای توزیعشده مدرن، عملکرد و کارایی غیرقابل مذاکره هستند. با رشد برنامهها، سربار ارتباط بین سرویسها میتواند به یک گلوگاه بزرگ تبدیل شود. اینجاست که gRPC میدرخشد. با بهرهگیری از پروتوباف (Protobuf) به عنوان زبان تعریف رابط (IDL) و HTTP/2 برای انتقال، gRPC یک جایگزین قوی، کارآمد و دارای نوعبندی دقیق در برابر REST ارائه میدهد که آن را به گزینهای ایدهآل برای میکروسرویسهای Go تبدیل میکند.
چرا gRPC را برای Go انتخاب کنیم؟
Go به دلیل مدل همزمانی و کتابخانه استاندارد خود شهرت دارد و آن را به یکی از رقابتیترین گزینهها برای زیرساختهای بکاند تبدیل میکند. وقتی با gRPC جفت میشود، توسعهدهندگان موارد زیر را دریافت میکنند:
- نوعبندی قوی: تعریف شده در فایلهای .proto که ثبات قرارداد را تضمین میکند.
- کارایی باینری: سریالسازی پروتوباف به طور قابل توجهی کوچکتر و سریعتر از JSON است.
- استریم بومی: پشتیبانی داخلی برای استریمینگ سمت کلاینت، سمت سرور و دوطرفه.
- عملکرد: چندگانهسازی HTTP/2 سربار اتصال را کاهش میدهد.
تعریف قرارداد سرویس
پایه و اساس هر سرویس gRPC، فایل .proto است. این فایل ساختارهای داده و روشهای RPC را تعریف میکند. بیایید یک سرویس ساده برای سرویس کاربر تعریف کنیم.
1. تعریف پروتوباف
فایلی به نام user.proto ایجاد کنید. ما یک ساختار پیام برای کاربر و یک رابط سرویس را تعریف خواهیم کرد.
syntax = "proto3";
package user;
option go_package = "github.com/example/service/user";
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
service UserService {
rpc GetUser (GetUserRequest) returns (User) {}
rpc ListUsers (ListUsersRequest) returns (stream User) {}
}
message GetUserRequest {
int32 id = 1;
}
message ListUsersRequest {
string filter = 1;
}
به استفاده از stream User در روش ListUsers توجه کنید. این نشاندهنده استریمینگ سمت سرور است، جایی که سرور یک دنباله از پاسخها را به یک درخواست کلاینت ارسال میکند.
تولید کد
پس از آماده بودن فایل .proto، باید کد Go را که رابطها را پیادهسازی میکند، تولید کنید. مطمئن شوید که کامپایلر protoc و پلاگینهای Go نصب شدهاند.
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
protoc --go_out=. --go-grpc_out=. user.proto
این دستور دو فایل تولید میکند: user.pb.go (حاوی ساختارهای پیام) و user_grpc.pb.go (حاوی استابهای کلاینت و سرور).
پیادهسازی سرور
اکنون، بیایید رابط UserService را در Go پیادهسازی کنیم. ما یک سرور ایجاد میکنیم که رابط تولید شده را برآورده کند.
package main
import (
"context"
"log"
"net"
pb "github.com/example/service/user"
"google.golang.org/grpc"
)
// server implements the UserService
type server struct {
pb.UnimplementedUserServiceServer
}
func (s *server) GetUser(ctx context.Context, in *pb.GetUserRequest) (*pb.User, error) {
// Mock data retrieval
return &pb.User{
Id: in.Id,
Name: "John Doe",
Email: "john@example.com",
}, nil
}
func (s *server) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
// Mock list of users
users := []*pb.User{
{Id: 1, Name: "Alice", Email: "alice@example.com"},
{Id: 2, Name: "Bob", Email: "bob@example.com"},
}
for _, user := range users {
if err := stream.Send(user); err != nil {
return err
}
}
return nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &server{})
log.Println("Server listening on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
ساخت کلاینت
با اجرای سرور، یک کلاینت میتواند متصل شود و RPCها را فراخوانی کند. در اینجا نحوه فراخوانی روشهای GetUser و ListUsers آمده است.
package main
import (
"context"
"log"
pb "github.com/example/service/user"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
// Set up a connection to the server
conn, err := grpc.Dial(":50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewUserServiceClient(conn)
// Call unary RPC
ctx, cancel := context.WithTimeout(context.Background(), 1000000)
defer cancel()
r, err := c.GetUser(ctx, &pb.GetUserRequest{Id: 1})
if err != nil {
log.Fatalf("could not get user: %v", err)
}
log.Printf("User: %+v", r)
// Call server-streaming RPC
stream, err := c.ListUsers(ctx, &pb.ListUsersRequest{Filter: "all"})
if err != nil {
log.Fatalf("could not list users: %v", err)
}
for {
msg, err := stream.Recv()
if err == nil {
log.Printf("User: %+v", msg)
} else {
break
}
}
}
نتیجهگیری
یکپارچهسازی gRPC در معماری میکروسرویسهای Go شما، ابزاری قدرتمند برای ساخت سیستمهای مقیاسپذیر و با عملکرد بالا فراهم میکند. در حالی که REST همچنان یک استاندارد چندمنظوره برای APIهای عمومی است، کارایی و ایمنی نوع gRPC آن را برای ارتباط سرویس-به-سرویس داخلی برتر میسازد. با تسلط بر تعاریف پروتوباف و بهرهگیری از اصول همزمانی Go، توسعهدهندگان میتوانند سیستمهای توزیعشده مستحکمی بسازند که در برابر آزمون مقیاسپذیری دوام بیاورند.