本文正在參加「Python主題月」,詳情查看活動連接java
隨着微服務的流行,GRPC的發展勢頭也愈來愈盛。咱們的後端項目,可能有不少微服務,而這些服務也許是有不少語言寫的。好比身份驗證的服務是Java,而消息通知的服務是Python,其餘業務使用C等等。既然是微服務,咱們這些服務之間獲取還須要互相調用來協做完成業務,那麼不一樣語言之間如何調用呢?GRPC就是這樣的一種通訊工具幫助你,讓不一樣語言的程序能夠互相交流。不須要你去處理語言之間的障礙,讓你看起來就像調用本身寫的函數同樣簡單。node
GRPC顧名思義就是遠程調用,爲何呢?來自官方文檔的一句話:gRPC Remote Procedure Calls。python
你必須知道如下幾個概念git
下面以簡單流爲例,其餘流可參考官方代碼,各類語言的都能在這個倉庫找到,routeguide這個示例包含了全部類型的grpc服務。使用注意:使用python開發前你必須安裝grpcio-tools、grpcio。github
python -m pip install grpcio
python -m pip install grpcio-tools
複製代碼
首先根據Protocol Buffers文件,來定義約束咱們的服務。咱們仍是以咱們最多見的helloworld爲例。在項目根目錄下建立文件夾protos,並在該文件夾下建立helloworld.proto文件,內容以下。spring
syntax = "proto3";
// To be compatible with Java configuration, it does not work in Python
// If false, only a single .java file will be generated for this .proto file.
// If true, separate .java files will be generated for each of the Java classes/enums/etc.
option java_multiple_files = true;
// The package you want to use for your generated Java/Kotlin classes.
option java_package = "com.wangscaler.examples.helloworld";
// The class name (and hence the file name) for the wrapper Java class you want to generate.
option java_outer_classname = "HelloWorldProto";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayAuthor (AuthorRequest) returns (AuthorReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The request message containing the user's name.
message AuthorRequest {
string name = 1;
int32 age = 2;
}
// The response message containing the greetings
message HelloReply {
string message = 1; } // The response message containing the greetings message AuthorReply {
string message = 1; int32 code = 2; } 複製代碼
咱們定義了一個Greeter服務,而且這個服務提供了兩個接口SayHello和SayAuthor。分別給這兩個接口的請求參數和響應參數作了限制。後端
不一樣的是SayHello的入參是一個字符串類型的,響應參數也是一個字符串類型的;而AuthorReply入參多了一個int類型的age,而響應參數也多了個int類型的code。瀏覽器
在項目根目錄下執行springboot
python -m grpc_tools.protoc -I./protos --python_out=. --grpc_python_out=. ./protos/helloworld.proto
複製代碼
執行完以後,會在項目根路徑下生成helloworld_pb2.py、helloworld_pb2_grpc.py兩個python文件。bash
定義Greeter去繼承helloworld_pb2_grpc.GreeterServicer,重寫父類的SayHello、SayAuthor來實現咱們的業務。
from concurrent import futures
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
print("Get a message from %s client" % request.name)
return helloworld_pb2.HelloReply(message='Hello world, %s client !' % request.name)
def SayAuthor(self, request, context):
print("Hi author(%(name)s), your age is %(age)d" % {"name": request.name, "age": request.age})
return helloworld_pb2.AuthorReply(
message='Hello, %s ! ' % request.name, code=0)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
if __name__ == '__main__':
logging.basicConfig()
serve()
複製代碼
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
def run():
# NOTE(gRPC Python Team): .close() is possible on a channel and should be
# used in circumstances in which the with statement does not fit the needs
# of the code.
with grpc.insecure_channel('localhost:50051') as channel:
stub = helloworld_pb2_grpc.GreeterStub(channel)
hello_response = stub.SayHello(helloworld_pb2.HelloRequest(name='python'))
author_response = stub.SayAuthor(helloworld_pb2.AuthorRequest(name='scaler', age=18))
print("Greeter client received: " + hello_response.message)
print("Greeter client received message: %(message)s and received code: %(code)d !" % {
"message": author_response.message,
"code": author_response.code})
if __name__ == '__main__':
logging.basicConfig()
run()
複製代碼
執行以後控制檯打印的消息以下:
Greeter client received: Hello world, python client !
Greeter client received message: Hello, scaler ! and received code: 0 !
複製代碼
而咱們服務器端的打印消息以下:
Get a message from python client
Hi author(scaler), your age is 18
複製代碼
一開始咱們就說了,GRPC能夠兼容多語言的調用,因此咱們的java客戶端也是能夠調用的上面的python的服務器端的SayHello和SayAuthor接口。
自行使用IDEA建立,不過多介紹。
在maven項目的src/main文件夾下建立proto文件夾,並將上述python中建立的proro文件,複製到這個文件夾下。
使用mvn compile
命令,將在target/generated-sources/protobuf/grpc-java
和target/generated-sources/protobuf/java
生成咱們須要的文件。
文件的包名就是咱們proto文件中指定的java_package。
在src/main/java下建立包和java_package一致,即和生成的代碼的包名保持一致。
package com.wangscaler.examples;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/** * @author WangScaler * @date 2021/7/22 16:36 */
public class Client {
private static final Logger logger = Logger.getLogger(Client.class.getName());
private final GreeterGrpc.GreeterBlockingStub blockingStub;
public Client(Channel channel) {
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest helloRequest = HelloRequest.newBuilder().setName(name).build();
AuthorRequest authorRequest = AuthorRequest.newBuilder().setName("wangscaler").setAge(18).build();
HelloReply helloResponse;
AuthorReply authorResponse;
try {
helloResponse = blockingStub.sayHello(helloRequest);
authorResponse = blockingStub.sayAuthor(authorRequest);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeter client received: " + helloResponse.getMessage());
logger.info(MessageFormat.format("Greeter client received message: {0} and received code: {1} ! ", authorResponse.getMessage(), authorResponse.getCode()));
}
public static void main(String[] args) throws Exception {
String user = "java";
String target = "localhost:50010";
if (args.length > 0) {
if ("--help".equals(args[0])) {
System.err.println("Usage: [name [target]]");
System.err.println("");
System.err.println(" name The name you wish to be greeted by. Defaults to " + user);
System.err.println(" target The server to connect to. Defaults to " + target);
System.exit(1);
}
user = args[0];
}
if (args.length > 1) {
target = args[1];
}
ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
.usePlaintext()
.build();
try {
Client client = new Client(channel);
client.greet(user);
} finally {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
}
複製代碼
執行以後控制檯打印的消息以下:
信息: Will try to greet java ...
七月 22, 2021 5:00:17 下午 com.wangscaler.examples.Client greet
信息: Greeter client received: Hello world, java client !
七月 22, 2021 5:00:17 下午 com.wangscaler.examples.Client greet
信息: Greeter client received message: Hello, wangscaler ! and received code: 0 !
複製代碼
而咱們python服務器端的打印消息以下:
Get a message from java client
Hi author(wangscaler), your age is 18
複製代碼
Cool!咱們的Java客戶端像調用本身內部的函數同樣,調用了遠程的python服務器上的方法。這就是GRPC的強大之處。微服務之間的調用就像這同樣輕鬆簡單。
使用openssl生成ssl證書。
openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt
複製代碼
注意:執行命令以後須要輸入你的相關信息,若是你是在ssl本地測試,切記CN的值爲localhost,此時你的客戶端才能夠經過localhost訪問你的服務器。
from concurrent import futures
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
print("Get a message from %s client" % request.name)
return helloworld_pb2.HelloReply(message='Hello world, %s client !' % request.name)
def SayAuthor(self, request, context):
print("Hi author(%(name)s), your age is %(age)d" % {"name": request.name, "age": request.age})
return helloworld_pb2.AuthorReply(
message='Hello, %s ! ' % request.name, code=0)
def serve():
with open('server.key', 'rb') as f:
private_key = f.read()
with open('server.crt', 'rb') as f:
certificate_chain = f.read()
server_credentials = grpc.ssl_server_credentials(
((private_key, certificate_chain,),))
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_secure_port('[::]:50051', server_credentials)
server.start()
server.wait_for_termination()
if __name__ == '__main__':
logging.basicConfig()
serve()
複製代碼
至此咱們的GRPC,就加入了SSL身份驗證。
咱們使用以前的客戶端去鏈接,發現鏈接失敗,以下圖所示。
接下來,修改以下:
首先將openssl生成的server.key和server.crt複製到項目的根路徑下。最後修改代碼以下:
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
def run():
with open('server.crt', 'rb') as f:
trusted_certs = f.read()
credentials = grpc.ssl_channel_credentials(root_certificates=trusted_certs)
with grpc.secure_channel('localhost:50051', credentials) as channel:
stub = helloworld_pb2_grpc.GreeterStub(channel)
hello_response = stub.SayHello(helloworld_pb2.HelloRequest(name='python'))
author_response = stub.SayAuthor(helloworld_pb2.AuthorRequest(name='scaler', age=18))
print("Greeter client received: " + hello_response.message)
print("Greeter client received message: %(message)s and received code: %(code)d !" % {
"message": author_response.message,
"code": author_response.code})
if __name__ == '__main__':
logging.basicConfig()
run()
複製代碼
再次運行客戶端,控制檯正常打印
Greeter client received: Hello world, python client !
Greeter client received message: Hello, scaler ! and received code: 0 !
複製代碼
修改以前測試鏈接。報錯以下:
RPC failed: Status{code=UNAVAILABLE, description=Network closed for unknown reason, cause=null}
複製代碼
修改以下,將openssl生成的server.crt複製到項目路徑下,能找到就能夠,不復制也行。
package com.wangscaler.examples;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NegotiationType;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext;
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder;
import javax.net.ssl.SSLException;
import java.io.File;
import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/** * @author WangScaler * @date 2021/7/22 16:36 */
public class Client {
private static final Logger logger = Logger.getLogger(Client.class.getName());
private final GreeterGrpc.GreeterBlockingStub blockingStub;
public Client(Channel channel) {
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest helloRequest = HelloRequest.newBuilder().setName(name).build();
AuthorRequest authorRequest = AuthorRequest.newBuilder().setName("wangscaler").setAge(18).build();
HelloReply helloResponse;
AuthorReply authorResponse;
try {
helloResponse = blockingStub.sayHello(helloRequest);
authorResponse = blockingStub.sayAuthor(authorRequest);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeter client received: " + helloResponse.getMessage());
logger.info(MessageFormat.format("Greeter client received message: {0} and received code: {1} ! ", authorResponse.getMessage(), authorResponse.getCode()));
}
private static SslContext buildSslContext(String trustCertCollectionFilePath) throws SSLException {
SslContextBuilder builder = GrpcSslContexts.forClient();
if (trustCertCollectionFilePath != null) {
builder.trustManager(new File(trustCertCollectionFilePath));
}
return builder.build();
}
public static void main(String[] args) throws Exception {
String user = "java";
String target = "localhost:50010";
if (args.length > 0) {
if ("--help".equals(args[0])) {
System.err.println("Usage: [name [target]]");
System.err.println("");
System.err.println(" name The name you wish to be greeted by. Defaults to " + user);
System.err.println(" target The server to connect to. Defaults to " + target);
System.exit(1);
}
user = args[0];
}
if (args.length > 1) {
target = args[1];
}
String[] targets = new String[2];
targets = target.split(":");
String host = targets[0];
int port = Integer.parseInt(targets[1]);
SslContext sslContext = Client.buildSslContext("D://springboot/test-grpc/src/main/java/com/wangscaler/examples/server.crt");
ManagedChannel channel = NettyChannelBuilder.forAddress(host, port)
.negotiationType(NegotiationType.TLS)
.sslContext(sslContext)
.build();
try {
Client client = new Client(channel);
client.greet(user);
} finally {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
}
複製代碼
再次鏈接。控制檯正常的調用到服務器端的函數。
信息: Will try to greet java ...
七月 27, 2021 9:38:51 上午 com.wangscaler.examples.Client greet
信息: Greeter client received: Hello world, java client !
七月 27, 2021 9:38:51 上午 com.wangscaler.examples.Client greet
信息: Greeter client received message: Hello, wangscaler ! and received code: 0 !
複製代碼