רוב רכיבי התקשורת בין מערכות המשתמשים ב-REST ממירים את העמודות שלהם ל-JSON. כיום, ל-JSON אין סטנדרט בדיקת תבנית נפוץ: תבנית JSON אינה נפוצה. בדיקת תבנית סטנדרטית מאפשרת להעביר את הבדיקה לספרייה של צד שלישי ולסיים איתה. בלי זה, אנו חייבים לחזור לבדיקה ידנית בקוד. גרוע מכך, עלינו לשמור על קוד הבדיקה בהסכם עם התבנית.
XML מציע בדיקת תבנית מיוחדת: מסמך XML יכול להכריז על שפה שהוא חייב לעמוד בה. SOAP, שמבוסס על XML, משתמע מכך, גם.
אלטרנטיבות אחרות להמרה מציעות אפשרות בדיקת תבנית: למשל, Avro, Kryo, ו-Protocol Buffers. מעניין שמדובר, gRPC משתמש ב-Protobuf כדי להציע RPC בין רכיבים מופרדים:
gRPC היא פרויקט זמין לרשות הציבור החופשי מודרני המספק שירותי קריאה מרחוק (RPC) בעלי ביצועים גבוהים היכולים לפעול בכל סביבה. זה יכול לחבר ביעילות שירותים בתוך ומעבר למרכזי מידע עם תמיכה ניתנת להתאמה משולבת לבקרת העומס, עקיבה, בדיקת בריאות ואבטחה. זה מתאים גם במרחק האחרון של המחשוב המרוחק כדי לחבר מכשירים, יישומים ניידים ודפי מסחר ברשת לשירותים הראשיים.
כמו כן, פרוטוקול הוא מנגנון בינארי להמרת סידור, מציל הרבה פס תחבורה. לפיכך, gRPC הוא אפשרות מצוינת לתקשורת בין מערכות. אבל אם כל הרכיבים שלך מדברים gRPC, איך לקוחות פשוטים יכולים להתקשר אליהם? בפוסט זה נבנה שירות gRPC ונראה כיצד לקרוא לו מ-cURL.
A Simple gRPC Service
התיעוד של gRPC מקיף, אז הנה סיכום:
- gRPC הוא מסגרת קריאה לפעולה רחוקה.
- הוא פועל ברחבי טווח רחב של שפות.
- הוא מסתמך על פרוטוקולים בייב:
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.
- הוא חלק מתיק ה-CNCF ונמצא כרגע בשלב ההטמעה.
בואו נקבע את שירות ה-gRPC שלנו. נשתמש ב-Java, Kotlin, Spring Boot ובפרויקט מיוחד לשילוב gRPC ב-Spring Boot. מבנה הפרויקט מכיל שני פרויקטים: אחד למודל ואחד לקוד. בואו נתחיל עם פרויקט המודל.
I didn't want something complicated. Reusing a simple example is enough: the request sends a string, and the response prefixes it with Hello
. We design this model in a dedicated Protobuf schema file:
syntax = "proto3"; //1
package ch.frankel.blog.grpc.model; //2
option java_multiple_files = true; //3
option java_package = "ch.frankel.blog.grpc.model"; //3
option java_outer_classname = "HelloProtos"; //3
service HelloService { //4
rpc SayHello (HelloRequest) returns (HelloResponse) {
}
}
message HelloRequest { //5
string name = 1; //6
}
message HelloResponse { //7
string message = 1; //6
}
- גרסת הגדרת Protobuf
- חבילה
- התקנה ספציפית ל-Java
- הגדרת שירות
- הגדרת בקשה
- הגדרת שדה: קודם מגיע הסוג, אחריה השם ולבסוף, הסדר.
- הגדרת תשובה
נשתמש ב-Maven ליצור את הקוד הבוליארי ה-Java:
<project>
<dependencies>
<dependency>
<groupId>io.grpc</groupId> <!--1-->
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId> <!--1-->
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId> <!--1-->
<artifactId>jakarta.annotation-api</artifactId>
<version>1.3.5</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId> <!--2-->
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId> <!--3-->
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf-plugin.version}</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- תליינים בזמן הקמעה
- לספוג מידע על המערכת הפעולה; משמש בהתקנה הבאה
- ליצור קוד Java מקובץ
proto
.
לאחר הקמיעה, המבנה צריך להיראות בערך ככה:
אנו יכולים לארוז את המחלקות ב-JAR ולהשתמש בהן בפרויקט אפליקציה רשת. האחרון ב-Kotlin, אך רק משום שזו השפה המעבדת האהובה עליי.
אנו זקוקים רק למשתמש התחלתי ספציפי של Spring Boot כדי לשלב סיומות gRPC עם Spring Boot:
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
הנה הנקודה המשמעותית:
@GrpcService //1
class HelloService : HelloServiceImplBase() { //2
override fun sayHello(
request: HelloRequest, //2
observer: StreamObserver<HelloResponse> //3
) {
with(observer) {
val reply = HelloResponse.newBuilder() //2
.setMessage("Hello ${request.name}") //4
.build()
onNext(reply) //5
onCompleted() //5
}
}
}
- ה-
grpc-server-spring-boot-starter
מזהה את העיצובית ומשתמש בקסמים שלו. - מחלקות ההתייחסות שנוצרו בפרויקט הנ"ל
- שמה של השיטה מאפשר פרמטר של
StreamObserver
. המחלקה מגיעה מ-grpc-stub.jar
. - קבל את הבקשה וקדימה אותה כדי לבנות את ההודעה התגובה.
- שיחק את האירועים.
אנו יכולים כעת להתחיל את האפליקציה הרשתית עם ./mvnw spring-boot:run
.
בדיקת שירות gRPC
הרעיון העיקרי מאחורי הפוסט הוא שלגשת לשירות gRPC עם כלי רגילים איננה אפשרית. לבדוק, אנו זקוקים לכלי מיוחד בכל זאת. מצאתי grpcurl. בואו נתקין אותו ונשתמש בו כדי לרשום את השירותים הזמינים:
grpcurl --plaintext localhost:9090 list #1-2
- רשום את כל השירותים gRPC הזמינים ללא אימות TLS.
- כדי להימנע מהתנגשויות בין gRPC ותעבורות אחרות, כגון REST, Spring Boot משתמש בנקודת כניסה נוספת.
ch.frankel.blog.grpc.model.HelloService #1
grpc.health.v1.Health #2
grpc.reflection.v1alpha.ServerReflection #2
- השירות gRPC שהגדרנו
- שני שירותים נוספים שמספק התחלת המיוחד
אנו יכולים גם לחדור לתוך המבנה של השירות:
grpcurl --plaintext localhost:9090 describe ch.frankel.blog.grpc.model.HelloService
service HelloService {
rpc SayHello ( .ch.frankel.blog.grpc.model.HelloRequest ) returns ( .ch.frankel.blog.grpc.model.HelloResponse );
}
לבסוף, אנו יכולים לקרוא לשירות עם נתונים:
grpcurl --plaintext -d '{"name": "John"}' localhost:9090 ch.frankel.blog.grpc.model.HelloService/SayHello
{
"message": "Hello John"
}
גישה לשירות gRPC באמצעים רגילים
דמיינו שיש לנו יישום של קליינט ג'אווה סקריפט רגיל שצריך לגשת לשירות gRPC. מה יהיו האלטרנטיבות?
הגישה הכללית היא דרך grpc-web
:
A JavaScript implementation of gRPC for browser clients. For more information, including a quick start, see the gRPC-web documentation.
לקוחות gRPC-web מתחברים לשירותים gRPC דרך פרוקס מיוחד; כברירת מחדל, gRPC-web משתמש באנוי.
בעתיד, אנו מצפים שgRPC-web יתמוך במסגרות אינטרנט ספציפיות לשפות כמו פייתון, ג'אווה ונוד. לפרטים, ראה את מסלול הפיתוח.
– grpc-web
התיאור מציין מגבלה אחת: הוא עובד רק עבור ג'אווה סקריפט (כפי שהוא עומד כרגע). עם זאת, יש עוד אחת. זה די מפריע. אתה צריך להשיג את קובץ הproto
, לייצר קוד ראשי, ולגרום לקוד שלך לקרוא אותו. אתה חייב לעשות זאת עבור כל סוג לקוח. גרוע מכך, אם קובץ proto משתנה, אתה צריך לרצות מחדש את קוד הלקוח בכולם.
יש אלטרנטיבה, אם כי, אם אתה משתמש בשער API. אני אתאר איך לעשות זאת עם אפטשי אפיסיקס, אך אולי שערים אחרים יכולים לעשות זאת באותו אופן. grpc-transcode הוא תוסף המאפשר שיטור שיחות REST לgRPC וחזרה.
השלב הראשון הוא לרשום את קובץ הproto באפטשי אפיסיקס:
curl http://localhost:9180/apisix/admin/protos/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d "{ \"content\": \"$(sed 's/"/\\"/g' ../model/src/main/proto/model.proto)\" }"
השלב השני הוא ליצור נתיב עם התוסף לעיל:
curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/helloservice/sayhello", #1
"plugins": {
"grpc-transcode": {
"proto_id": "1", #2
"service": "ch.frankel.blog.grpc.model.HelloService", #3
"method": "SayHello" #4
}
},
"upstream": {
"scheme": "grpc",
"nodes": {
"server:9090": 1
}
}
}'
- הגדר נתיב דקדוקי.
- התייחס לקובץ proto המוגדר בפקודה הקודמת.
- שירות gRPC
- שיטת gRPC
בשלב זה, כל לקוח יכול לבצע בקשה HTTP לנקודת הקצה המוגדרת. אפשרות APISIX של אפט' אפאז' ימיר את השיחה ל-gRPC, יעביר אותה לשירות המוגדר, יקבל את התגובה ושוב ימיר אותה.
curl localhost:9080/helloservice/sayhello?name=John
{"message":"Hello John"}
בניגוד ל-grpc-web
, הגישה של שער הAPI מאפשרת לשתף את הקובץ proto
עם רכיב אחד: השער עצמו.
יתרונות של מימורים
בשלב זה, אנו יכולים לנצל את היכולות של שער ה-API. דמיינו שאנו רוצים ערך ברירת מחדל אם לא עבר name
, למשל World
. מפתחים היו שמחים לקבוע אותו בקוד, אך כל שינוי בערך דורש בניית מלאה ובניית פלטה. שינויים יכולים להיות כמעט מיידיים אם נשים את הערך הברירת המחדל בשרשרת עיבוד הנתיבים של השער. בואו נשנה את הנתיב שלנו בהתאם:
curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/helloservice/sayhello",
"plugins": {
"grpc-transcode": {
...
},
"serverless-pre-function": { #1
"phase": "rewrite", #2
"functions" : [
"return function(conf, ctx) #3
local core = require(\"apisix.core\")
if not ngx.var.arg_name then
local uri_args = core.request.get_uri_args(ctx)
uri_args.name = \"World\"
ngx.req.set_uri_args(uri_args)
end
end"
]
}
},
"upstream": {
...
}
}'
- תוסף כללי ללא מתאים
- שנה את הבקשה.
- קוד Lua קסם שעושה את הטריק
עכשיו, אנו יכולים לבצע את הבקשה עם טיעון ריק ולקבל את התוצאה הצפויה:
curl localhost:9080/helloservice/sayhello?name
{"message":"Hello World"}
מסקנה
בפוסט הזה תיארנו בקצרה את gRPC ואיך זה משתלם לתקשורת בין שירותים. פיתחנו שירות gRPC פשוט באמצעות Spring Boot ו-grpc-server-spring-boot-starter
. יש לזה מחיר, אם כי: לקוחות רגילים לא יכולים לגשת לשירות. הגענו להפנייה ל-grpcurl
כדי לבדוק אותו. אותו דבר נכון גם ללקוחות המבוססים על JavaScript – או הדפדפן.
כדי לנקוט במגבלה זו, אנו יכולים להשתמש בשער API. הדגמתי איך להגדיר את Apache APISIX עם תוספת grpc-transcode
כדי להשיג את התוצאה הרצויה.
קוד המקור המלא לפוסט זה נמצא ב-GitHub.