Java程序员的现代RPC指南
Java程序员的现代RPC指南
1.前言
1.1 RPC框架简介
最早接触RPC还是初学Java时,直接用Socket API传东西好麻烦。于是发现了JDK直接支持的RMI,然后就用得不亦乐乎,各种大作业里凡是涉及到分布式通信的都用RMI,真是方便。后来用上了Spring,发现Spring提供了好多Exporter,可以无侵入地将一个POJO暴露为RPC服务。
接触了这么多RPC框架后,发现当时公司内部自己实现了一套支持压缩、加密等附加功能的RPC基础框架,于是就读了一下源码,发现原来自己实现个简单的RPC挺简单啊,选好序列化框架后用反射为服务接口生成存根就行了。核心技术就是:序列化和动态反射。
前一阵子又接触到了多语言支持的RPC框架。其实传统的方式也是能支持多种编程语言的,只要序列化框架为多种语言都提供了版本支持,那么序列化后使用相同的网络协议传输就能实现跨语言的RPC了,这也是最轻量级的自制RPC了,灵活但是手动工作量比较大。再就是重量级的SOAP WebService或简单方便的REST,后者一般采用JSON格式携带数据,最典型的场景就是前后台的服务调用,从JS到Java的RPC。
本文要重点介绍的则是另外一套RPC框架。相比一般的RPC框架来说,它能够支持多种语言间的RPC;相比WebService,它却没有SOAP那么重量级,又比轻量级的REST高效。对于组件之间需要频繁通信、又对性能要求较高的分布式系统来说,它是不错的解决方案。
1.2 Protobuf vs. Thrift
Protobuf全名为Protocol Buffer,是Google推出的支持多语言的RPC基础设施。通过自定义语言无关的IDL文件和Protoc代码生成器达到跨语言RPC通信的目的。但也正因为跨语言,与我们仅限于Java的那些RPC框架相比稍显复杂一些。之前研究Protobuf序列化性能时,也正因这一点而采用了Java简化版Protostuff,详情请见《序列化战争:主流序列化框架Benchmark》。
Thrift是Facebook推出的RPC框架,与PB相比提供了内建的RPC支持,而PB开源版里并没有RPC功能(也是后面实践时才发现的)。Thrift的RPC提供了多种网络模式和序列化的选项,可以根据不同场景来灵活搭配使用。
关于Protobuf、Thrift以及本文未涉及的Avro,在《大数据日知录》里有具体的比较,感兴趣的可以参考一下。
1.3 核心技术
前面说了传统RPC框架的核心技术,“现代”RPC为了支持多语言所以稍显复杂一点儿。核心技术主要有:IDL、代码生成器、序列化、RPC:
- 在IDL文件中通过不与具体编程语言相关的语法定义通信类
- 利用代码生成器生成出Java语言对应的代码
- 引入框架的运行时Jar包,获得序列化、RPC等能力
所以前两者决定了框架对不同编程语言的支持能力,而后两者决定了运行时的调用性能。
2.Maven集成
不管使用哪种框架,既然涉及到了代码生成,那就要想法与项目构建的过程结合到一起。这里以Java项目最常用的Maven为例,看一下如何将代码生成器与Maven相结合,并且有哪些注意事项。
2.1 Compile阶段
按照我们的设想,代码生成过程应该在编译阶段,这样生成的代码就能跟已有代码一起被编译、打包、发布,两者没有什么差别。一旦修改了IDL,直接编译就能看到最新生成的代码了,这就是我们想要的效果。
2.2 Ant集成插件
因为有些框架并不提供专门的插件,所以与Maven最简单通用的集成方法就是采用maven-antrun-plugin插件。此插件可以执行任意命令,标准写法如下:
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<tasks>
<exec executable="xxx.exe">
<arg value="arg1 arg2 ..."/>
</exec>
</tasks>
<sourceRoot>target/generated-sources</sourceRoot>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
3.Protobuf实战
3.1 编写IDL
语法非常简单,其中java_generic_services选项决定是否生成Service类,默认不生成,据说是不建议使用。
package com.test;
option java_generic_services = true;
message Request
{
required int32 type = 1;
}
message Response
{
required int32 cpu = 1;
required int32 memorySize = 2;
}
service AgentService
{
rpc detectHardware(Request) returns (Response);
}
3.2 Protoc编译
Windows版预编译好的protoc.exe支持C++,Java,Python三种最常用的语言,如果你只使用这几种语言的话那就很简单了。之所以把Protobuf相关文件都放到src/protobuf而非src/main/resources下是因为:src/main/resources里东西默认会被包含到最终的jar里。如果我们不想把protoc.exe和一堆.proto文件打到jar包里发布的话,要么加一个Maven的拷贝filter,或者像本文的方法将Protobuf相关文件都放到src/protobuf下。
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<tasks>
<exec executable="src/protobuf/protoc.exe" failonerror="true">
<arg value="--java_out=src/main/java"/>
<arg value="src/protobuf/idl/*.proto"/>
</exec>
</tasks>
<sourceRoot>target/generated-sources</sourceRoot>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
3.3 使用源码
以下是利用Protobuf生成的代码进行序列化和反序列化的示例。由于缺少RPC功能,所以也只能测试一下序列化功能了。
public class PbRpcTest {
public static void main(String[] args) throws InvalidProtocolBufferException {
// Build request
Agent.Request.Builder reqBuilder = Agent.Request.newBuilder();
reqBuilder.setType(1);
Agent.Request req = reqBuilder.build();
System.out.println(req);
// Parse from bytes
byte[] bytes = req.toByteArray();
Agent.Request req2 = Agent.Request.parseFrom(bytes);
System.out.println(req2.getType());
}
}
4.Thrift
4.1 编译IDL
与Protobuf的IDL相似,Thrift的IDL也很简单。
namespace java com.test
service AgentService {
string detectHardware()
}
4.2 Java
Thrift支持多种网络和序列化模式,这里采取最简单的同步阻塞和二进制序列化的方式。
public class RpcClientTest {
public static void main(String[] args) throws TException {
TSocket transport = new TSocket("127.0.0.1", 8090);
TProtocol protocol = new TBinaryProtocol(transport);
AgentService.Client client = new AgentService.Client(protocol);
transport.open();
System.out.println(client.detectHardware());
}
}
public class RpcServerTest {
public static void main(String[] args) throws TTransportException {
AgentService.Processor<AgentService.Iface> processor = new AgentService.Processor<AgentService.Iface>(
new AgentServiceImpl()
);
TServerSocket transport = new TServerSocket(8090);
TServer.Args tArgs = new TServer.Args(transport);
tArgs.processor(processor);
tArgs.protocolFactory(new TBinaryProtocol.Factory());
TServer server = new TSimpleServer(tArgs);
server.serve();
}
}
public class AgentServiceImpl implements AgentService.Iface {
@Override
public String detectHardware() throws TException {
return "hello";
}
}
4.3 Python
Python要想运行时支持Thrift,也需要安装相应的插件。我是在Windows下的Cygwin中完成安装的,然后在cmd中执行却报错还是没找到thrift模块,结果发现是cmd和Cygwin默认执行的Python版本不一样。汗,之前可能2和3都装了忘记了,生成的代码用Python 3运行的话会有问题:
$ tar -xzvf thrift-0.9.3.tar.gz
$ cd thrift-0.9.3/
$ python setup.py install
注意一定要指定IP地址,否则Java客户端调用Python服务端时会报”Connection refused”错误,详见Stackoverflow上的问题解答。
import sys, glob
sys.path.append('gen-py')
#sys.path.insert(0, glob.glob('../../lib/py/build/lib*')[0])
from agent import AgentService
from agent.ttypes import *
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer
class AgentServiceHandler:
def __init__(self):
print('init')
def detectHardware(self):
print('detect!')
return 'hello~~~'
handler = AgentServiceHandler()
processor = AgentService.Processor(handler)
transport = TSocket.TServerSocket(host="127.0.0.1", port=8090)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TBinaryProtocol.TBinaryProtocolFactory()
server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
print('Starting the server...')
server.serve()
print('done.')
这就是目录结构,现在就可以启动服务端,客户端仍然用之前的Java客户端,调用成功!
$ tree py-demo/ -I "*.pyc"
py-demo/
|-- agent.thrift
|-- gen-py
| |-- __init__.py
| `-- agent
| |-- __init__.py
| |-- __pycache__
| |-- AgentService.py
| |-- AgentService-remote
| |-- constants.py
| `-- ttypes.py
|-- server.py
`-- thrift-0.9.3.exe
$ python server.py
init
Starting the server...
detect!
4.总结
与直接使用Java其他RPC框架相比的确麻烦了一些,例如Spring中就自带了一些Exporter可以无侵入的实现RPC服务。但熟悉了Protobuf和Thrift以后发现实际上还是挺方便的,而且Windows版预编译好的Protoc支持C++,Java,Python三种最常用的语言,Thrift则支持几乎主流的各种语言,足够我们使用了。
参考资料:
Java程序员的现代RPC指南的更多相关文章
- Java程序员的现代RPC指南(Windows版预编译好的Protoc支持C++,Java,Python三种最常用的语言,Thrift则支持几乎主流的各种语言)
Java程序员的现代RPC指南 1.前言 1.1 RPC框架简介 最早接触RPC还是初学Java时,直接用Socket API传东西好麻烦.于是发现了JDK直接支持的RMI,然后就用得不亦乐乎,各种大 ...
- Java程序员的Golang入门指南(下)
Java程序员的Golang入门指南(下) 4.高级特性 上面介绍的只是Golang的基本语法和特性,尽管像控制语句的条件不用圆括号.函数多返回值.switch-case默认break.函数闭包.集合 ...
- Java程序员的Golang入门指南(上)
Java程序员的Golang入门指南 1.序言 Golang作为一门出身名门望族的编程语言新星,像豆瓣的Redis平台Codis.类Evernote的云笔记leanote等. 1.1 为什么要学习 如 ...
- 一名资深架构师规划Java程序员五年职业生涯指南
每个程序员.或者说每个工作者都应该有自己的职业规划,如果你不是富二代,不是官二代,也没有职业规划,希望你可以思考一下自己的将来.今天我给大家分享的是一篇来自阿里大牛对五年工作经验程序员的职业建议,希望 ...
- 写给Java程序员的Java虚拟机学习指南
大家好,我是极客时间<深入拆解Java虚拟机>作者.Oracle Labs高级研究员郑雨迪.有幸借这个专题的机会,能和大家分享为何Java工程师要学Java虚拟机?如何掌握Java虚拟机? ...
- Java程序员学习之路
1. Java语言基础 谈到Java语 言基础学习的书籍,大家肯定会推荐Bruce Eckel的<Thinking in Java>.它是一本写的相当深刻的技术书籍,Java语言基础部分基 ...
- 9本java程序员必读的书(附下载地址)
本文列出的9本书在Java程序员界都是被认为很棒的书.当一个程序员开始初学Java时,他的第一个问题应该是如何选择一本书来作为指导学习Java.这个问题也就表明,相对于其他的教程和博客,Java书籍还 ...
- 【转】java架构师之路:JAVA程序员必看的15本书的电子版下载地址
作为Java程序员来说,最痛苦的事情莫过于可以选择的范围太广,可以读的书太多,往往容易无所适从.我想就我自己读过的技术书籍中挑选出来一些,按照学习的先后顺序,推荐给大家,特别是那些想不断提高自己技术水 ...
- 转载:java程序员如何拿到2万月薪
作者:匿名用户链接:https://www.zhihu.com/question/39890405/answer/83676977来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...
随机推荐
- POJ-2240 Arbitrage---判断正环+枚举
题目链接: https://vjudge.net/problem/POJ-2240 题目大意: 已知n种货币,以及m种货币汇率及方式,问能否通过货币转换,使得财富增加. 思路: 由于这里问的是财富有没 ...
- 1.7 理解dropout
Dropout为什么有正则化的作用? 下面来直观理解一下. 上面讲到,dropout每次迭代都会让一部分神经元失活,这样使得神经网络会比原始的神经网络规模变小,因此采用一个较小神经网络好像和使用正则化 ...
- AngularJS 全局scope与指令 scope通信
在项目开发时,全局scope 和 directive本地scope使用范围不够清晰,全局scope与directive本地scope通信掌握的不够透彻,这里对全局scope 和 directive本地 ...
- [LeetCode] Special Binary String 特殊的二进制字符串
Special binary strings are binary strings with the following two properties: The number of 0's is eq ...
- [LeetCode] Print Binary Tree 打印二叉树
Print a binary tree in an m*n 2D string array following these rules: The row number m should be equa ...
- Python网络爬虫笔记(二):链接爬虫和下载限速
(一)代码1(link_crawler()和get_links()实现链接爬虫) import urllib.request as ure import re import urllib.parse ...
- jq图片展示插件highslide.js简单dom
今天用用了一款图片展示插件highslide.js,感觉用起来很是舒畅,几乎不用怎么写代码,只需要知道如何写参数就行了. 那么这么牛叉的插件我们该如何用哪,下面我就跟大家讲解一下. 一.引入 首先 ...
- git中的merge与rebase
之前一直对git的merge与rebase很困惑,而且一般也只使用merge而不是使用rebase.今天受高人指点理清了两者的区别. 首先对于两者而言,他们的结果是一样的,差异在于合并的方式(产生的结 ...
- python day one 变量,if
一.变量,常量 变量:用于储存和标记信息在内存中,方便日后被调用.操作和修改 常量:固定不变大的量.字母大写 命名规则: 1.由字母数字下划线组成,命名要有意义,区分大小写. 2.命名建议用下划线分割 ...
- Shiro加密
在开发的时候,很多数据我们都希望是以加密过后的形式存储起来,而不是最原始的数据. 在shiro中也提供了编码,解码,加密,加密算法实现等等一系列的内容. 编码/解码 在org.apache.shiro ...