最近公司在做大数据系统,需要把Python端的数据分析对接java客户端,因为分析的服务和客户端不在同一个服务器上运行,这样做也最大程度的保证了彼此的资源不受到影响,于是果断舍弃了Jpython和runtime()之流。
询问了某个大佬,他说哪有这么麻烦,直接远程ssh登录,使用beeline就好了,那么安全性呢,他说可以使用文件或者环境量去传递密码或者做加密处理不就好了,也是一种办法,哈。
除此之外,用python 把方法封装到http也是一种方法,可能需要用到flask框架。所有的方法的实现和封装都在python端完成了,不管你什么语言,我直接拿到你的url用就好了。那么服务器的性能开销可能就比较大了,如果上线运行的话,可能还要考虑http加密问题,这个没研究过,需不需要自己造轮子还不好说。
最后一张解决方案就是今天要说的rpc(远程过程调用)方案。在大规模分布式系统中RPC框架已经是标配。
微服务架构是近期比较流行的架构,所谓"everything is service" 是一个小项目向大项目转变的必经之路。 要实现服务化,则首先要解决各个服务之间的通信问题。那么就会面临数据序列化、反序列化、连接管理、收发线程、超时处理等问题。如果自己实现一套这样的机制,不但重复劳动,性能和效率也难以保证。rpc框架的出现解决了这些问题,让调用者不必关心底层细节。
google gRpc(protobuf IDL)、facebook thrift(thrif IDL)、微信svr_kit(XML & protobuf IDL)、百度brpc(protobuf)等等,都是一些成熟的rpc第三方框架,并且开源。
软件设计中重要原则,KISS(Keep it Simple and Stupid)就是一个很典型的例子,其核心思想就是“解耦,解耦,再解耦”,职责单一化,功能单一化为以后的维护和重构都会带来极大的便利。
松耦合:微服务化,一个server只做一件事情,鸡蛋不放到一个篮子,挂了,只影响一个服务
维护 & 重构:上线发布只用关心单一功能,不怕影响到其他服务,重构只用关心单一功能,不怕踩其他地雷等等。
比较易于使用和上手,又没有明显短版的框架应该首推facebook开源的thrif 和谷歌开源的grpc。
下面通过两个简单的实例来感受一下:
创建一个简单的服务 Hello。首先根据 Thrift 的语法规范编写脚本文件 Hello.thrift,代码如下:
清单 1. Hello.thrift
namespace java service.demo
service Hello{
string helloString(1:string para)
i32 helloInt(1:i32 para)
bool helloBoolean(1:bool para)
void helloVoid()
string helloNull()
}
其中定义了服务 Hello 的五个方法,每个方法包含一个方法名,参数列表和返回类型。每个参数包括参数序号,参数类型以及参数名。 Thrift 是对 IDL(Interface Definition Language) 描述性语言的一种具体实现。因此,以上的服务描述文件使用 IDL 语法编写。使用 Thrift 工具编译 Hello.thrift,就会生成相应的 Hello.java 文件。该文件包含了在 Hello.thrift 文件中描述的服务 Hello 的接口定义,即 Hello.Iface 接口,以及服务调用的底层通信细节,包括客户端的调用逻辑 Hello.Client 以及服务器端的处理逻辑 Hello.Processor,用于构建客户端和服务器端的功能。
创建 HelloServiceImpl.java 文件并实现 Hello.java 文件中的 Hello.Iface 接口,代码如下:
清单 2. HelloServiceImpl.java
package service.demo;
import org.apache.thrift.TException;
public class HelloServiceImpl implements Hello.Iface {
@Override
public boolean helloBoolean(boolean para) throws TException {
return para;
}
@Override
public int helloInt(int para) throws TException {
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return para;
}
@Override
public String helloNull() throws TException {
return null;
}
@Override
public String helloString(String para) throws TException {
return para;
}
@Override
public void helloVoid() throws TException {
System.out.println("Hello World");
}
}
创建服务器端实现代码,将 HelloServiceImpl 作为具体的处理器传递给 Thrift 服务器,代码如下:
清单 3. HelloServiceServer.java
package service.server;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TBinaryProtocol.Factory;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
import service.demo.Hello;
import service.demo.HelloServiceImpl;
public class HelloServiceServer {
/**
* 启动 Thrift 服务器
* @param args
*/
public static void main(String[] args) {
try {
// 设置服务端口为 7911
TServerSocket serverTransport = new TServerSocket(7911);
// 设置协议工厂为 TBinaryProtocol.Factory
Factory proFactory = new TBinaryProtocol.Factory();
// 关联处理器与 Hello 服务的实现
TProcessor processor = new Hello.Processor(new HelloServiceImpl());
TServer server = new TThreadPoolServer(processor, serverTransport,
proFactory);
System.out.println("Start server on port 7911...");
server.serve();
} catch (TTransportException e) {
e.printStackTrace();
}
}
}
创建客户端实现代码,调用 Hello.client 访问服务端的逻辑实现,代码如下:
清单 4. HelloServiceClient.java
package service.client;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import service.demo.Hello;
public class HelloServiceClient {
/**
* 调用 Hello 服务
* @param args
*/
public static void main(String[] args) {
try {
// 设置调用的服务地址为本地,端口为 7911
TTransport transport = new TSocket("localhost", 7911);
transport.open();
// 设置传输协议为 TBinaryProtocol
TProtocol protocol = new TBinaryProtocol(transport);
Hello.Client client = new Hello.Client(protocol);
// 调用服务的 helloVoid 方法
client.helloVoid();
transport.close();
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}
}
代码编写完后运行服务器,再启动客户端调用服务 Hello 的方法 helloVoid,在服务器端的控制台窗口输出“Hello World”(helloVoid 方法实现在控制台打印字符串,没有返回值,所以客户端调用方法后没有返回值输出,读者可以自己尝试其他有返回值方法的调用,其结果可以打印在客户端的控制台窗口 )。
创建一个简单的服务 Hello。首先根据 Thrift 的语法规范编写脚本文件 Helloworld.proto,代码如下:
清单 1. Helloworld.proto
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
通过proto文件可以很轻松的定义结构化数据。Protobuf 有如 XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。
然后在python端运行proto插件生成对应的数据结构文件和服务端需要的文件。
清单2 greeter_client_with_options.py
定义服务端
from __future__ import print_function
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.
#
# For more channel options, please see https://grpc.io/grpc/core/group__grpc__arg__keys.html
with grpc.insecure_channel(
target='localhost:50051', #定义ip和端口号
options=[('grpc.lb_policy_name', 'pick_first'),
('grpc.enable_retries', 0), ('grpc.keepalive_timeout_ms',
10000)]) as channel:
stub = helloworld_pb2_grpc.GreeterStub(channel)
# Timeout in seconds.
# Please refer gRPC Python documents for more detail. https://grpc.io/grpc/python/grpc.html
response = stub.SayHello(
helloworld_pb2.HelloRequest(name='you'), timeout=10)
print("Greeter client received: " + response.message)
if __name__ == '__main__':
logging.basicConfig()
run()
定义客户端:
清单3 greeter_client_with_options.py
from __future__ import print_function
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.
#
# For more channel options, please see https://grpc.io/grpc/core/group__grpc__arg__keys.html
with grpc.insecure_channel(
target='localhost:50051',
options=[('grpc.lb_policy_name', 'pick_first'),
('grpc.enable_retries', 0), ('grpc.keepalive_timeout_ms',
10000)]) as channel:
stub = helloworld_pb2_grpc.GreeterStub(channel)
# Timeout in seconds.
# Please refer gRPC Python documents for more detail. https://grpc.io/grpc/python/grpc.html
response = stub.SayHello(
helloworld_pb2.HelloRequest(name='you'), timeout=10)
print("Greeter client received: " + response.message)
if __name__ == '__main__':
logging.basicConfig()
run()
当然也可以在python端定义service ,然后在java定义client
首先使用maven定义依赖和插件(官方文档有详细说明)然后使同用一个proto(本例清单1)文件生成相应的数据结构文件
这里只定义客户端
清单4 HelloWorldClient.java
package io.grpc.examples.helloworld;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A simple client that requests a greeting from the {@link HelloWorldServer}.
*/
public class HelloWorldClient {
private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
/** Construct client connecting to HelloWorld server at {@code host:port}. */
public HelloWorldClient(String host, int port) {
this(ManagedChannelBuilder.forAddress(host, port)
// Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
// needing certificates.
.usePlaintext()
.build());
}
/** Construct client for accessing HelloWorld server using the existing channel. */
HelloWorldClient(ManagedChannel channel) {
this.channel = channel;
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
/** Say hello to server. */
public void greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
}
/**
* Greet server. If provided, the first element of {@code args} is the name to use in the
* greeting.
*/
public static void main(String[] args) throws Exception {
HelloWorldClient client = new HelloWorldClient("localhost", 50051);
try {
/* Access a service running on the local machine on port 50051 */
String user = "world";
if (args.length > 0) {
user = args[0]; /* Use the arg as the name to greet if provided */
}
client.greet(user);
} finally {
client.shutdown();
}
}
}
目录结构及测试结果。
参考文档 :
测试:
https://zhuanlan.zhihu.com/p/28927834
https://github.com/grpc/grpc-java
https://github.com/grpc/grpc/blob/master/examples/python/helloworld
怎么看待谷歌的开源 RPC 框架 gRPC?
Apache Thrift - 可伸缩的跨语言服务开发框架
Thrift下java服务器与客户端开发指南
grpc python文档