当前位置:   article > 正文

Java程序员的现代RPC指南

byte[] requestbyte = req.build().tobytearray();

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插件。此插件可以执行任意命令,标准写法如下:

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <artifactId>maven-antrun-plugin</artifactId>
  5. <executions>
  6. <execution>
  7. <id>generate-sources</id>
  8. <phase>generate-sources</phase>
  9. <configuration>
  10. <tasks>
  11. <exec executable="xxx.exe">
  12. <arg value="arg1 arg2 ..."/>
  13. </exec>
  14. </tasks>
  15. <sourceRoot>target/generated-sources</sourceRoot>
  16. </configuration>
  17. <goals>
  18. <goal>run</goal>
  19. </goals>
  20. </execution>
  21. </executions>
  22. </plugin>
  23. </plugins>
  24. </build>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3.Protobuf实战

3.1 编写IDL

语法非常简单,其中java_generic_services选项决定是否生成Service类,默认不生成,据说是不建议使用。

  1. package com.test;
  2. option java_generic_services = true;
  3. message Request
  4. {
  5. required int32 type = 1;
  6. }
  7. message Response
  8. {
  9. required int32 cpu = 1;
  10. required int32 memorySize = 2;
  11. }
  12. service AgentService
  13. {
  14. rpc detectHardware(Request) returns (Response);
  15. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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下。

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <artifactId>maven-antrun-plugin</artifactId>
  5. <executions>
  6. <execution>
  7. <id>generate-sources</id>
  8. <phase>generate-sources</phase>
  9. <configuration>
  10. <tasks>
  11. <exec executable="src/protobuf/protoc.exe" failonerror="true">
  12. <arg value="--java_out=src/main/java"/>
  13. <arg value="src/protobuf/idl/*.proto"/>
  14. </exec>
  15. </tasks>
  16. <sourceRoot>target/generated-sources</sourceRoot>
  17. </configuration>
  18. <goals>
  19. <goal>run</goal>
  20. </goals>
  21. </execution>
  22. </executions>
  23. </plugin>
  24. </plugins>
  25. </build>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

3.3 使用源码

以下是利用Protobuf生成的代码进行序列化和反序列化的示例。由于缺少RPC功能,所以也只能测试一下序列化功能了。

  1. public class PbRpcTest {
  2. public static void main(String[] args) throws InvalidProtocolBufferException {
  3. // Build request
  4. Agent.Request.Builder reqBuilder = Agent.Request.newBuilder();
  5. reqBuilder.setType(1);
  6. Agent.Request req = reqBuilder.build();
  7. System.out.println(req);
  8. // Parse from bytes
  9. byte[] bytes = req.toByteArray();
  10. Agent.Request req2 = Agent.Request.parseFrom(bytes);
  11. System.out.println(req2.getType());
  12. }
  13. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

4.Thrift

4.1 编译IDL

与Protobuf的IDL相似,Thrift的IDL也很简单。

  1. namespace java com.test
  2. service AgentService {
  3. string detectHardware()
  4. }
  • 1
  • 2
  • 3
  • 4
  • 5

4.2 Java

Thrift支持多种网络和序列化模式,这里采取最简单的同步阻塞和二进制序列化的方式。

  1. public class RpcClientTest {
  2. public static void main(String[] args) throws TException {
  3. TSocket transport = new TSocket("127.0.0.1", 8090);
  4. TProtocol protocol = new TBinaryProtocol(transport);
  5. AgentService.Client client = new AgentService.Client(protocol);
  6. transport.open();
  7. System.out.println(client.detectHardware());
  8. }
  9. }
  10. public class RpcServerTest {
  11. public static void main(String[] args) throws TTransportException {
  12. AgentService.Processor<AgentService.Iface> processor = new AgentService.Processor<AgentService.Iface>(
  13. new AgentServiceImpl()
  14. );
  15. TServerSocket transport = new TServerSocket(8090);
  16. TServer.Args tArgs = new TServer.Args(transport);
  17. tArgs.processor(processor);
  18. tArgs.protocolFactory(new TBinaryProtocol.Factory());
  19. TServer server = new TSimpleServer(tArgs);
  20. server.serve();
  21. }
  22. }
  23. public class AgentServiceImpl implements AgentService.Iface {
  24. @Override
  25. public String detectHardware() throws TException {
  26. return "hello";
  27. }
  28. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

4.3 Python

Python要想运行时支持Thrift,也需要安装相应的插件。我是在Windows下的Cygwin中完成安装的,然后在cmd中执行却报错还是没找到thrift模块,结果发现是cmd和Cygwin默认执行的Python版本不一样。汗,之前可能2和3都装了忘记了,生成的代码用Python 3运行的话会有问题:

  1. $ tar -xzvf thrift-0.9.3.tar.gz
  2. $ cd thrift-0.9.3/
  3. $ python setup.py install
  • 1
  • 2
  • 3

注意一定要指定IP地址,否则Java客户端调用Python服务端时会报”Connection refused”错误,详见Stackoverflow上的问题解答

  1. import sys, glob
  2. sys.path.append('gen-py')
  3. #sys.path.insert(0, glob.glob('../../lib/py/build/lib*')[0])
  4. from agent import AgentService
  5. from agent.ttypes import *
  6. from thrift import Thrift
  7. from thrift.transport import TSocket
  8. from thrift.transport import TTransport
  9. from thrift.protocol import TBinaryProtocol
  10. from thrift.server import TServer
  11. class AgentServiceHandler:
  12. def __init__(self):
  13. print('init')
  14. def detectHardware(self):
  15. print('detect!')
  16. return 'hello~~~'
  17. handler = AgentServiceHandler()
  18. processor = AgentService.Processor(handler)
  19. transport = TSocket.TServerSocket(host="127.0.0.1", port=8090)
  20. tfactory = TTransport.TBufferedTransportFactory()
  21. pfactory = TBinaryProtocol.TBinaryProtocolFactory()
  22. server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
  23. print('Starting the server...')
  24. server.serve()
  25. print('done.')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

这就是目录结构,现在就可以启动服务端,客户端仍然用之前的Java客户端,调用成功!

  1. $ tree py-demo/ -I "*.pyc"
  2. py-demo/
  3. |-- agent.thrift
  4. |-- gen-py
  5. | |-- __init__.py
  6. | `-- agent
  7. | |-- __init__.py
  8. | |-- __pycache__
  9. | |-- AgentService.py
  10. | |-- AgentService-remote
  11. | |-- constants.py
  12. | `-- ttypes.py
  13. |-- server.py
  14. `-- thrift-0.9.3.exe
  15. $ python server.py
  16. init
  17. Starting the server...
  18. detect!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

4.总结

与直接使用Java其他RPC框架相比的确麻烦了一些,例如Spring中就自带了一些Exporter可以无侵入的实现RPC服务。但熟悉了Protobuf和Thrift以后发现实际上还是挺方便的,而且Windows版预编译好的Protoc支持C++,Java,Python三种最常用的语言,Thrift则支持几乎主流的各种语言,足够我们使用了。

参考资料:

  1. Protobuf语言指南
  2. Thrift入门及Java实例演示

转载于:https://www.cnblogs.com/xiaomaohai/p/6157586.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/650466
推荐阅读
相关标签
  

闽ICP备14008679号