Java RMI (Remote Method Invocation)- 远程方法调用,能够让客户端像使用本地调用一样调用服务端 Java 虚拟机中的对象方法。RMI 是面向对象语言领域对 RPC (Remote Procedure Call)的完善,用户无需依靠 IDL 的帮助来完成分布式调用,而是通过依赖接口这种更简单自然的方式。
一个典型的 RMI 调用如下图所示:
(来源:https://www.cs.rutgers.edu/~pxk/417/notes/images/rpc-rmi_flow.png)
Java RMI 是 Java 领域创建分布式应用的技术基石。后续的 EJB 技术,以及现代的分布式服务框架,其中的基本理念依旧是 Java RMI 的延续。在 RMI 调用中,有以下几个核心的概念:
通过接口进行远程调用
通过客户端的 Stub 对象和服务端的 Skeleton 对象的帮助将远程调用伪装成本地调用
通过 RMI 注册服务完成服务的注册和发现
对于第一点,客户端需要依赖接口,而服务端需要提供该接口的实现。
对于第二点,在 J2SE 1.5 版本之前需要通过 rmic 预先编译好客户端的 Stub 对象和服务端的 Skeleton 对象。在之后的版本中,不再需要事先生成 Stub 和 Skeleton 对象。
下面通过示例代码简单的展示 RMI 中的服务注册和发现
Hello obj = new HelloImpl(); // #1 Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0); // #2 Registry registry = LocateRegistry.createRegistry(1099); // #3 registry.rebind("Hello", stub); // #4
说明:
Registry registry = LocateRegistry.getRegistry(); // #1 Hello stub = (Hello) registry.lookup("Hello"); // #2 String response = stub.sayHello(); // #3
说明:
理解 RMI 的工作原理和基本概念,对掌握现代分布式服务框架很有帮助,建议进一步的阅读 RMI 官方教材 [^1]。
现代的分布式服务框架的基本概念与 RMI 是类似的,同样是使用 Java 的 Interface 作为服务契约,通过注册中心来完成服务的注册和发现,远程通讯的细节也是通过代理类来屏蔽。具体来说,Dubbo 在工作时有以下四个角色参与:
部署阶段
运行阶段
Dubbo 的应用一般都是通过 Spring 来组装的。为了快速获得一个可以工作的 Dubbo 应用,这里的示例摒弃了复杂的配置,而改用面向 Dubbo API 的方式来构建服务提供者和消费者,另外,注册中心和监控中心在本示例中也不需要安装和配置。
在生产环境,Dubbo 的服务需要一个分布式的服务注册中心与之配合,比如,ZooKeeper。为了方便开发,Dubbo 提供了直连[^2]以及组播[^3]两种方式,从而避免额外搭建注册中心的工作。在本例中,将使用组播的方式来完成服务的注册和发现。
public interface GreetingsService { String sayHi(String name); // #1 }
说明:
public class GreetingsServiceImpl implements GreetingsService { // #1 @Override public String sayHi(String name) { return "hi, " + name; // #2 } }
说明:
public class Application { public static void main(String[] args) throws IOException { ServiceConfig<GreetingsService> service = new ServiceConfig<>(); // #1 service.setApplication(new ApplicationConfig("first-dubbo-provider")); // #2 service.setRegistry(new RegistryConfig("multicast://224.5.6.7:1234")); // #3 service.setInterface(GreetingsService.class); // #4 service.setRef(new GreetingsServiceImpl()); // #5 service.export(); // #6 System.in.read(); // #7 } }
说明:
multicast://224.5.6.7:1234
。合法的组播地址范围为:224.0.0.0 - 239.255.255.255public class Application { public static void main(String[] args) { ReferenceConfig<GreetingsService> reference = new ReferenceConfig<>(); // #1 reference.setApplication(new ApplicationConfig("first-dubbo-client")); // #2 reference.setRegistry(new RegistryConfig("multicast://224.5.6.7:1234")); // #3 reference.setInterface(GreetingsService.class); // #4 GreetingsService greetingsService = reference.get(); // #5 String message = greetingsService.sayHi("dubbo"); // #6 System.out.println(message); // #7 } }
说明:
完整的示例在 https://github.com/dubbo/dubbo-samples/tree/master/dubbo-samples-api 上提供。在完整的示例中,由于配置了 exec-maven-plugin,可以很方便的在命令行下通过 maven 的方式执行。当然,您也可以在 IDE 里直接执行,但是需要注意的是,由于使用了组播的方式来发现服务,运行时需要指定 -Djava.net.preferIPv4Stack=true。
通过以下的命令来同步示例代码并完成构建:
$ git clone https://github.com/dubbo/dubbo-samples.git $ cd dubbo-samples/dubbo-samples-api/ $ mvn clean package INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building dubbo-samples-api 1.0-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ dubbo-samples-api --- ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.182 s [INFO] Finished at: 2018-05-28T14:56:08+08:00 [INFO] Final Memory: 20M/353M [INFO] ------------------------------------------------------------------------
当看到 BUILD SUCCESS 的时候表明构建完成,下面就可以开始进入运行阶段了。
通过运行以下的 maven 命令来启动服务提供者:
$ mvn -Djava.net.preferIPv4Stack=true -Dexec.mainClass=com.alibaba.dubbo.samples.server.Application exec:java [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building dubbo-samples-api 1.0-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ dubbo-samples-api --- log4j:WARN No appenders could be found for logger (com.alibaba.dubbo.common.logger.LoggerFactory). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. first-dubbo-provider is running.
当 first-dubbo-provider is running. 出现时,代表服务提供者已经启动就绪,等待客户端的调用。
通过运行以下的 maven 命令来调用服务:
$ mvn -Djava.net.preferIPv4Stack=true -Dexec.mainClass=com.alibaba.dubbo.samples.client.Application exec:java [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building dubbo-samples-api 1.0-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ dubbo-samples-api --- log4j:WARN No appenders could be found for logger (com.alibaba.dubbo.common.logger.LoggerFactory). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. hi, dubbo
可以看到, hi, dubbo 是从服务提供者返回的执行结果。
Dubbo 还提供了一个公共服务快速搭建基于 Spring Boot 的 Dubbo 应用。访问 http://start.dubbo.io 并按照下图所示来生成示例工程:
说明:
在本例中展示的是服务提供者,同样的,通过在生成界面选取 client 来生成对应的服务消费者。
用 IDE 打开生成好的工程,可以发现应用是一个典型的 Spring Boot 应用。程序的入口如下所示:
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { new EmbeddedZooKeeper(2181, false).start(); // #1 SpringApplication.run(DemoApplication.class, args); // #2 } }
说明:
可以直接在 IDE 中运行,输出结果如下:
2018-05-28 16:59:38.072 INFO 59943 --- [ main] a.b.d.c.e.WelcomeLogoApplicationListener : ████████▄ ███ █▄ ▀█████████▄ ▀█████████▄ ▄██████▄ ███ ▀███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ▄███▄▄▄██▀ ▄███▄▄▄██▀ ███ ███ ███ ███ ███ ███ ▀▀███▀▀▀██▄ ▀▀███▀▀▀██▄ ███ ███ ███ ███ ███ ███ ███ ██▄ ███ ██▄ ███ ███ ███ ▄███ ███ ███ ███ ███ ███ ███ ███ ███ ████████▀ ████████▀ ▄█████████▀ ▄█████████▀ ▀██████▀ :: Dubbo Spring Boot (v0.1.0) : https://github.com/dubbo/dubbo-spring-boot-project :: Dubbo (v2.0.1) : https://github.com/alibaba/dubbo :: Google group : http://groups.google.com/group/dubbo 2018-05-28 16:59:38.079 INFO 59943 --- [ main] e.OverrideDubboConfigApplicationListener : Dubbo Config was overridden by externalized configuration {dubbo.application.name=dubbo-demo-server, dubbo.application.qosAcceptForeignIp=false, dubbo.application.qosEnable=true, dubbo.application.qosPort=22222, dubbo.registry.address=zookeeper://localhost:2181?client=curator, dubbo.registry.id=my-registry, dubbo.scan.basePackages=com.example} #1 ... 2018-05-28 16:59:39.624 INFO 59943 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.746 seconds (JVM running for 2.963)
说明:
生成工程的时候如果选择了激活 qos 的话,就可以通过 telnet 或者 nc 来管理服务、查看服务状态。
$ telnet localhost 22222 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. ████████▄ ███ █▄ ▀█████████▄ ▀█████████▄ ▄██████▄ ███ ▀███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ▄███▄▄▄██▀ ▄███▄▄▄██▀ ███ ███ ███ ███ ███ ███ ▀▀███▀▀▀██▄ ▀▀███▀▀▀██▄ ███ ███ ███ ███ ███ ███ ███ ██▄ ███ ██▄ ███ ███ ███ ▄███ ███ ███ ███ ███ ███ ███ ███ ███ ████████▀ ████████▀ ▄█████████▀ ▄█████████▀ ▀██████▀ dubbo> dubbo>ls As Provider side: +------------------------------+---+ | Provider Service Name |PUB| +------------------------------+---+ |com.example.HelloService:1.0.0| Y | +------------------------------+---+ As Consumer side: +---------------------+---+ |Consumer Service Name|NUM| +---------------------+---+
目前 qos 支持以下几个命令,更详细的信息请查阅官方文档[^4]:
在本文中,从 RMI 开始,介绍了 Java 领域分布式调用的基本概念,也就是基于接口编程、通过代理将远程调用伪装成本地、通过注册中心完成服务的注册和发现。
然后为了简单起见,使用简单的组播注册方式和直接面向 Dubbo API 编程的方式介绍了如何开发一个 Dubbo 的完整应用。深入的了解 ServiceConfig 和 ReferenceConfig 的用法,对于进一步的使用 Spring XML 配置、乃至 Spring Boot 的编程方式有这很大的帮助。
最后,简单的介绍了如何通过 Dubbo 团队提供的公共服务 start.dubbo.io 快速搭建基于 Spring Boot 的 Dubbo 应用,并通过 qos 来做 Dubbo 服务的简单运维。
[^1]: Getting Started Using JavaTM RMI [^2]: 直连提供者 [^3]: Multicast 注册中心 [^4]: 在线运维命令