gRPC是google開源提供的一個RPC軟件框架,它的特點是極大簡化了傳統RPC的開發流程和代碼量,使用戶可以免除許多陷阱並聚焦於實際應用邏輯中。作為一種google的最新RPC解決方案,gRPC具備了以下這些強項:
1、gRPC在HTTP/2協議上用protobuf取代了json實現了最佳效率
2、用IDL(Interface Definition Language),一種簡單的描述語言來自動產生RPC的api源代碼
3、支持blocking/non-blocking雙向數據流交互,適合程序的流程控制
gRPC的使用非常簡單,具體流程如下:
1、在一個.proto字符類文件中用IDL來描述用戶自定義的數據類型和服務
2、用protoc編譯器編譯文件並產生自定義數據類型和服務的api源代碼
3、在server端實現.proto中定義的服務函數
4、在client端通過自動產生的stub來調用服務
下面我們就來示范gRPC的編程流程。gRPC支持下面這幾種服務類型:
1、Unary:獨立的一對client-request/server-response,是我們常用的http交互模式
2、Server-Streaming:client發出一個request后從server端接收一串多個response
3、Client-Streaming:client向server發送一串多個request后從server接收一個response
4、Bidirectional-Streaming:由client首先發送request啟動連接,然后在這個連接上兩端可以不斷交互信息。
在本篇討論中我們先示范Unary-service的編程流程,下面是.proto文件內容:
syntax = "proto3"; import "google/protobuf/wrappers.proto"; import "scalapb/scalapb.proto"; package learn.grpc.services; /* * Returns a greeting for the given person optionally including a custom message. */ service HelloWorld { rpc SayHello(ToBeGreeted) returns (Greeting) {} } message Person { string name = 1; } message ToBeGreeted { Person person = 1; google.protobuf.StringValue msg = 2; } message Greeting { string message = 1; }
這段IDL描述了一個HelloWorld服務,包括了一個服務函數SayHello。三種數據類型:Person,ToBeGreeted,Greeting。通過對.proto文件進行編譯后產生文件中包括一個HelloWorldGrpc.scala文件,里面提供了一些重要的api:
trait HelloWorld -> 用於實現HelloWorld服務的trait trait HelloWorldBlockingClient -> 用於實現客戶端stub class HelloWorldBlockingStub -> blocking客戶端stub class HelloWorldStub -> non-blocking客戶端stub def bindService -> 服務類型綁帶方法
我們先實現HelloWorld服務:
class HelloService extends HelloWorldGrpc.HelloWorld { override def sayHello(request: ToBeGreeted): Future[Greeting] = { val greeter = request.person match { case Some(p) => p.name case None => "friendo" } Future.successful(Greeting(message = s"Hello $greeter, ${request.msg}")) } }
可以看到我們直接使用了IDL描述的自定義數據類型如:ToBeGreeted,Greeting。在客戶端調用服務並輸出返回結果response:
//build connection channel
val channel = io.grpc.ManagedChannelBuilder .forAddress("LocalHost",50051) .usePlaintext(true) .build() //construct requestHelloService
val greeter = ToBeGreeted() .withMsg("remote greetings!") .withPerson(ToBeGreeted.Person("mickey")) //async call
val asyncStub: HelloWorldGrpc.HelloWorldStub = HelloWorldGrpc.stub(channel) val futResponse: Future[Greeting] = asyncStub.sayHello(greeter) import scala.concurrent.ExecutionContext.Implicits.global futResponse.foreach(greeting => println(greeting.message)) val greeter2 = ToBeGreeted(person = Some(Person("jacky")),msg = Some("how are you?")) //sync call
val syncStub: HelloWorldGrpc.HelloWorldBlockingClient = HelloWorldGrpc.blockingStub(channel) val response: Greeting = syncStub.sayHello(greeter2) println(s"${response.message}")
下面是bindService方法的使用示范:
def main(args: Array[String]): Unit = { val service = HelloWorldGrpc.bindService(new HelloService,ExecutionContext.global) runServer(service) }
runServer函數定義如下:
package learn.grpc.server import io.grpc.{ServerBuilder,ServerServiceDefinition} trait gRPCServer { def runServer(service: ServerServiceDefinition): Unit = { val server = ServerBuilder .forPort(50051) .addService(service) .build .start // make sure our server is stopped when jvm is shut down
Runtime.getRuntime.addShutdownHook(new Thread() { override def run(): Unit = server.shutdown() }) server.awaitTermination() } }
注意我們還使用了io.grpc庫里的類型和方法,這是protobuf項目提供的一個標准庫。在客戶端也需要使用它來構建通道:
//build connection channel
val channel = io.grpc.ManagedChannelBuilder .forAddress("LocalHost",50051) .usePlaintext(true) .build()
我們將在后面的討論里介紹gRPC的streaming編程方法。下面是本次示范的源代碼:
project/scalapb.sbt
addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.18") libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.7.1"
build.sbt
import scalapb.compiler.Version.scalapbVersion import scalapb.compiler.Version.grpcJavaVersion name := "learn-gRPC" version := "0.1" scalaVersion := "2.12.6" libraryDependencies ++= Seq( "com.thesamet.scalapb" %% "scalapb-runtime" % scalapbVersion % "protobuf", "io.grpc" % "grpc-netty" % grpcJavaVersion, "com.thesamet.scalapb" %% "scalapb-runtime-grpc" % scalapbVersion ) PB.targets in Compile := Seq( scalapb.gen() -> (sourceManaged in Compile).value )
src/main/protobuf/hello.proto
syntax = "proto3"; import "google/protobuf/wrappers.proto"; import "scalapb/scalapb.proto"; package learn.grpc.services; /* * Returns a greeting for the given person optionally including a custom message. */ service HelloWorld { rpc SayHello(ToBeGreeted) returns (Greeting) {} } message Person { string name = 1; } message ToBeGreeted { Person person = 1; google.protobuf.StringValue msg = 2; } message Greeting { string message = 1; }
src/main/scala/gRPCServer.scala
package learn.grpc.server import io.grpc.{ServerBuilder,ServerServiceDefinition} trait gRPCServer { def runServer(service: ServerServiceDefinition): Unit = { val server = ServerBuilder .forPort(50051) .addService(service) .build .start // make sure our server is stopped when jvm is shut down
Runtime.getRuntime.addShutdownHook(new Thread() { override def run(): Unit = server.shutdown() }) server.awaitTermination() } }
src/main/scala/HelloServer.scala
package learn.grpc.hello.server import learn.grpc.services.hello._ import learn.grpc.server.gRPCServer import scala.concurrent._ object HelloServer extends gRPCServer { class HelloService extends HelloWorldGrpc.HelloWorld { override def sayHello(request: ToBeGreeted): Future[Greeting] = { val greeter = request.person match { case Some(p) => p.name case None => "friendo" } Future.successful(Greeting(message = s"Hello $greeter, ${request.msg}")) } } def main(args: Array[String]): Unit = { val service = HelloWorldGrpc.bindService(new HelloService,ExecutionContext.global) runServer(service) } }
src/main/scala/HelloClient.scala
package learn.grpc.hello.client import learn.grpc.services.hello.ToBeGreeted.Person import learn.grpc.services.hello._ import scala.concurrent.Future object HelloClient { def main(args: Array[String]): Unit = { //build connection channel val channel = io.grpc.ManagedChannelBuilder .forAddress("LocalHost",50051) .usePlaintext(true) .build() //construct requestHelloService val greeter = ToBeGreeted() .withMsg("remote greetings!") .withPerson(ToBeGreeted.Person("mickey")) //async call val asyncStub: HelloWorldGrpc.HelloWorldStub = HelloWorldGrpc.stub(channel) val futResponse: Future[Greeting] = asyncStub.sayHello(greeter) import scala.concurrent.ExecutionContext.Implicits.global futResponse.foreach(greeting => println(greeting.message)) val greeter2 = ToBeGreeted(person = Some(Person("jacky")),msg = Some("how are you?")) //sync call val syncStub: HelloWorldGrpc.HelloWorldBlockingClient = HelloWorldGrpc.blockingStub(channel) val response: Greeting = syncStub.sayHello(greeter2) println(s"${response.message}") } }