本文正在参加「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 !
复制代码