看国内的一些项目时 Dubbo 这个词经常闪现,一直也不以为然,未作搜索,当然也不知道它是做什么用的。直到最近阅读关于大型网站架构相关的书中反复提到 Dubbo 后,觉得不能再对它视而不见。Google 了一下,它是在阿里巴巴创建贡献给了 Apache 的开源项目,在阿里巴巴的大型应用中久经考验过的。Dubbo 是什么呢?借用官方 Dubbo 介绍
Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。Dubbo 被设计为高度可扩展,用户可以方便的实现流量拦截、选址的各种定制逻辑。
Dubbo 是国内企业贡献的,所以官方有原生的中文文档,它某些时候与 Spring Cloud 齐名,又有些像 AWS 的 ECS Service Discovery, Service Connect 加上 ELB 的功能。
Dubbo 可以与 Spring/Spring Boot 中很好的工作,不过本文只想完全脱离 Spring 框架,实际操作一个最基本的 Dubbo RPC 应用来理解 Dubbo 的架构。熟悉之后,Spring 的介入不外乎就是把本来代码实现的内容移入到配置文件或 Java 注解,不会是难事。
- 服务注册器是一个 Zookeeper
- Provider 启动时向 Registry 注册自己的服务,下线后 Zookeeper 会清除该服务注册
- Consumer 从 Registry 获得可用的服务列表,然后直接向对应的 Provider 发起远程调用
可以是多个不同的 Provider 提供不同的服务,或者相同的 Provider 启动多个副本提供相同的服务(集群),Consumer 获得服务后按一定的规则(比如轮询,失败重试下一个服务等规则)调用某个 Provider 实例上的服务。
首先大体上介绍本试验的内容
- 启动一个 ZooKeeper 服务器
- 向 ZooKeeper 注册 user-service 的两个副本 [192.168.86.49:50053, 192.168.86.49:50054]
- 再向 ZooKeeper 注册 order-service 一份 [192.168.86.49:50052]
- user-service 客户端从 ZooKeeper 找到有两个副本 [192.168.86.49:50053, 192.168.86.49:50054] 提供服务,多个调用会 user-service 时,请求会均衡到这两个副本上。如是其中的一个副本(如 192.168.86.49:50053) 下线,则只会调用剩下的 192.168.86.49:50054。假如调用某个服务失败(出现异常)也能重试另一个副本(192.168.86.49:50054)
- order-service 客户端从 ZooKeeper 找到 192.168.86.49:50052 服务进行调用
- 我们将用 ZooKeeper 客户端 zkCli.sh 观察服务注册和下线后在 ZooKeeper 中发生了什么
下面试验开始
启动一个 ZooKeeper 作为服务注册器
使用 Docker
$ docker run -p 2181:2181 zookeeper:3.9.2
从容器中暴露出 2181 端口号
Maven 项目引入所需的依赖
不管是 Java 的服务端还是客户端都需要引入 Dubbo 依赖,假设是用 Maven 管理项目。
当前 Dubbo 的正式版本是 3.2.15, 经过一番测试后发现它实际支持的 Java 版本是 1.8。用高版本的 JDK 如 11, 17, 21 的话,服务端能启动成功并完成向 ZooKeeper 的注册,但时而也会出现如下错误
java.lang.reflect.InaccessibleObjectException: Unable to make field private java.lang.String java.lang.StackTraceElement.fileName accessible: module java.base does not "opens java.lang" to unnamed module
但客户端用 Dubbo 3.2.15 + 高于 JDK 1.8 的版本调用服务总是失败的
所以如果用 Dubbo 3.2.15 的版本,请用 JDK 1.8, 在 pom.xml 中需要的依赖是
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>3.2.15</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>5.7.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-x-discovery</artifactId> <version>5.7.0</version> </dependency> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.21.1</version> </dependency> |
(官方的例子用的组合是 Dubbo 3.2.0-beta.4 + JDK 17,读者可自行尝试)
Dubbo 会引入其他许多的运行时依赖,如 spring-beans, spring-conext, spring-aop, spring-core, spring-jcl, spring-expression, spring 组件的版本是 5.3.37; io.netty 等一系列通信用的组件, alibaba:hessian 以及其他
没有 curator-framework 可能出现错误 java.lang.NoClassDefFoundError: org/apache/curator/framework/api/ACLProvider 或 java.lang.NoClassDefFoundError: org/apache/curator/framework/CuratorFrameworkFactory
没有 curator-x-discovery 可能会出现错误 java.lang.ClassNotFoundException: org.apache.curator.x.discovery.ServiceDiscoveryBuilder
但从 Dubbo 3.3.0 开始可完美支持 Java 21, 当前尚处于 beta.5 的版本
如果在高于 Java 1.8 的 JDK(如 Java 21) 中使用 Dubbo, 就可以引入如下依赖
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>3.3.0-beta.5</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.3.3</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>5.7.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-x-discovery</artifactId> <version>5.7.0</version> </dependency> |
而且我们发现,使用 Dubbo 3.3.0-beta.5 让整个项目更干净了,因为它不引用任何 Spring 相关的依赖
定义并实现服务
UserService
其中定义两个实现方法
|
1 2 3 4 5 6 |
package blog.yanbin.dubbo.providers; public interface UserService { boolean login(String username, String password); String userInfo(String username); } |
OrderService
|
1 2 3 4 5 |
package blog.yanbin.dubbo.providers; public interface OrderService { int addOrder(String productName, int number); } |
然后是它们的模拟实现类
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package blog.yanbin.dubbo.providers; import java.util.Objects; public class UserServiceImpl implements UserService { private final int servicePort; public UserServiceImpl(int servicePort) { this.servicePort = servicePort; } @Override public boolean login(String username, String password) { System.out.printf("userService %d get called%n", servicePort); return Objects.equals(username, "admin") && Objects.equals(password, "666"); } @Override public String userInfo(String username) { return String.format("Detailed information of %s from %s", username, servicePort); } } |
计划在不同的端口上启动两个 user-service 服务,以此来测试服务的集群。用 servicePort 区分出来,在调用 login 接口时将从控制台输出分辨出谁被调用
|
1 2 3 4 5 6 7 8 9 10 11 |
package blog.yanbin.dubbo.providers; import java.util.Random; public class OrderServiceImpl implements OrderService { @Override public int addOrder(String productName, int number) { return Math.abs(new Random().nextInt()); } } |
实现服务启动程序
有了前面的接口方法和实现类,还要用主程序把它们启动后并注册到 ZooKeeper 上供客户端查询,若是用 Spring/SpringBoot 的话,这些任务可通过配置文件和框架来实现
UserServiceStarter
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package blog.yanbin.dubbo.providers; import org.apache.dubbo.config.ProtocolConfig; import org.apache.dubbo.config.RegistryConfig; import org.apache.dubbo.config.ServiceConfig; import org.apache.dubbo.config.bootstrap.DubboBootstrap; import org.apache.dubbo.config.bootstrap.builders.ServiceBuilder; public class UserServiceStarter { public static void main(String[] args) { int servicePort = Integer.parseInt(args[0]); ServiceConfig<UserService> serviceConfig = ServiceBuilder.<UserService>newBuilder() .interfaceClass(UserService.class).ref(new UserServiceImpl(servicePort)).build(); DubboBootstrap.getInstance() .application("user-service") .registry(new RegistryConfig("zookeeper://localhost:2181")) .protocol(new ProtocolConfig("tri", servicePort)) .service(serviceConfig) .start() .await(); } } |
由于将在本机上不同的端口上启动 user-serivce 服务, 所以可通过参数指定端口号。如果上面未指定 registry,则服务不会注册到 ZooKeeper 上,但服务仍能启动,启动在 localhost:<servicePort> 上,客户端也能跳过 ZooKeeper 直接调用 tri://localhost:<servicePort>
如果不指定 protocol 的端口号,默认为 50051; 端口号置为 -1 , 协议为 tri 的话,则会从 50051 开始自增选择端口号,如 protocol(new ProtocolConfig("tri", -1), 这样就可以一个服务在本机上启动多次,端口号依次为 50051, 50052, 50053,....。不同协议的起始端口号是不一样的。
Dubbo 支持的协议有 dubbo, rest, http, hessian, redis, thrift, grpc, memcached, rmi, webservice. 默认协议是 dubbo, 因此也可以完全省略 protocol() 方法调用。希望在 Dubbo 客户端中从 ZooKeeper 中发现并调用 redis, memcached 等服务,则需要预先把它们注册到 ZooKeeper 中。
OrderServiceStarter
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package blog.yanbin.dubbo.providers; import org.apache.dubbo.config.ProtocolConfig; import org.apache.dubbo.config.RegistryConfig; import org.apache.dubbo.config.ServiceConfig; import org.apache.dubbo.config.bootstrap.DubboBootstrap; import org.apache.dubbo.config.bootstrap.builders.ServiceBuilder; public class OrderServiceStarter { public static void main(String[] args) { ServiceConfig<OrderService> serviceConfig = ServiceBuilder.<OrderService>newBuilder() .interfaceClass(OrderService.class).ref(new OrderServiceImpl()).build(); DubboBootstrap.getInstance() .application("order-service") .registry(new RegistryConfig("zookeeper://localhost:2181")) .protocol(new ProtocolConfig("tri", 50052)) .service(serviceConfig) .start() .await(); } } |
启动服务并注册到 ZooKeeper 上
因为使用了 Maven 来管理项目依赖,编译后用 java 命令来启动服务必须为 classpath 指定众多的 jar 包(除非用 Maven 的 Assembly 或 SpringBoot 插件做成 fat.jar ),所以用 Maven 的 exec 插件来启动主程序方便些
在 50053 端口号上启动 user-service
mvn compile exec:java -Dexec.mainClass=blog.yanbin.dubbo.providers.UserServiceStarter -Dexec.args=50053
服务启动后在 Zookeeper 中增加了几个键,用 zkCli 查看下
zkCli -server localhost:2181
连上后执行 zookeeper 命令
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[zk: localhost:2181(CONNECTED) 0] ls / [dubbo, services, zookeeper] [zk: localhost:2181(CONNECTED) 1] ls /dubbo [blog.yanbin.dubbo.providers.UserService] [zk: localhost:2181(CONNECTED) 2] ls /dubbo/blog.yanbin.dubbo.providers.UserService [configurators, providers] [zk: localhost:2181(CONNECTED) 3] ls /dubbo/blog.yanbin.dubbo.providers.UserService/configurators [] [zk: localhost:2181(CONNECTED) 9] get /dubbo/blog.yanbin.dubbo.providers.UserService/configurators 192.168.86.49 [zk: localhost:2181(CONNECTED) 4] ls /dubbo/blog.yanbin.dubbo.providers.UserService/providers [tri%3A%2F%2F192.168.86.49%3A50053%2Fblog.yanbin.dubbo.providers.UserService%3Fapplication%3Duser-service%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26environment%3Dproduct%26generic%3Dfalse%26interface%3Dblog.yanbin.dubbo.providers.UserService%26logger%3Djcl%26methods%3Dlogin%2CuserInfo%26prefer.serialization%3Dfastjson2%2Chessian2%26release%3D3.2.15%26service-name-mapping%3Dtrue%26side%3Dprovider%26timestamp%3D1724384955118] [zk: localhost:2181(CONNECTED) 5] ls /services [user-service] [zk: localhost:2181(CONNECTED) 6] ls /services/user-service [192.168.86.49:50053] [zk: localhost:2181(CONNECTED) 7] ls /services/user-service/192.168.86.49:50053 [] [zk: localhost:2181(CONNECTED) 8] get /services/user-service/192.168.86.49:50053 {"name":"user-service","id":"192.168.86.49:50053","address":"192.168.86.49","port":50053,"sslPort":null,"payload":{"@class":"org.apache.dubbo.registry.zookeeper.ZookeeperInstance","id":"192.168.86.49:50053","name":"user-service","metadata":{"dubbo.endpoints":"[{\"port\":50053,\"protocol\":\"tri\"}]","dubbo.metadata-service.url-params":"{\"prefer.serialization\":\"fastjson2,hessian2\",\"version\":\"1.0.0\",\"dubbo\":\"2.0.2\",\"release\":\"3.2.15\",\"side\":\"provider\",\"port\":\"50053\",\"protocol\":\"tri\"}","dubbo.metadata.revision":"228dd49adee966aa56da7a27a4ec9dff","dubbo.metadata.storage-type":null,"timestamp":"1724384955118"}},"registrationTimeUTC":1724384956129,"serviceType":"DYNAMIC","uriSpec":null} |
/dubbo/blog.yanbin.dubbo.providers.UserService/providers 的内容要进行 URL Decode
tri://192.168.86.49:50053/blog.yanbin.dubbo.providers.UserService?application=user-service&deprecated=false&dubbo=2.0.2&dynamic=true&environment=product&generic=false&interface=blog.yanbin.dubbo.providers.UserService&logger=jcl&methods=login,userInfo&prefer.serialization=fastjson2,hessian2&release=3.2.15&service-name-mapping=true&side=provider×tamp=1724384955118
再 50054 端口上启动另一份 user-service 服务
mvn compile exec:java -Dexec.mainClass=blog.yanbin.dubbo.providers.UserServiceStarter -Dexec.args=50054
查看 ZooKeeper 上的值
|
1 2 3 4 5 6 |
[zk: localhost:2181(CONNECTED) 14] ls /services/user-service [192.168.86.49:50053, 192.168.86.49:50054] [zk: localhost:2181(CONNECTED) 15] get /services/user-service/192.168.86.49:50054 {"name":"user-service","id":"192.168.86.49:50054","address":"192.168.86.49","port":50054,"sslPort":null,"payload":{"@class":"org.apache.dubbo.registry.zookeeper.ZookeeperInstance","id":"192.168.86.49:50054","name":"user-service","metadata":{"dubbo.endpoints":"[{\"port\":50054,\"protocol\":\"tri\"}]","dubbo.metadata-service.url-params":"{\"prefer.serialization\":\"fastjson2,hessian2\",\"version\":\"1.0.0\",\"dubbo\":\"2.0.2\",\"release\":\"3.2.15\",\"side\":\"provider\",\"port\":\"50054\",\"protocol\":\"tri\"}","dubbo.metadata.revision":"ab021a032ce32d362bf3b653a27050c1","dubbo.metadata.storage-type":null,"timestamp":"1724385567323"}},"registrationTimeUTC":1724385568323,"serviceType":"DYNAMIC","uriSpec":null} [zk: localhost:2181(CONNECTED) 24] ls /dubbo/blog.yanbin.dubbo.providers.UserService/providers [tri%3A%2F%2F192.168.86.49%3A50053%2Fblog.yanbin.dubbo.providers.UserService%3Fapplication%3Duser-service%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26environment%3Dproduct%26generic%3Dfalse%26interface%3Dblog.yanbin.dubbo.providers.UserService%26logger%3Djcl%26methods%3Dlogin%2CuserInfo%26prefer.serialization%3Dfastjson2%2Chessian2%26release%3D3.2.15%26service-name-mapping%3Dtrue%26side%3Dprovider%26timestamp%3D1724384955118, tri%3A%2F%2F192.168.86.49%3A50054%2Fblog.yanbin.dubbo.providers.UserService%3Fapplication%3Duser-service%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26environment%3Dproduct%26generic%3Dfalse%26interface%3Dblog.yanbin.dubbo.providers.UserService%26logger%3Djcl%26methods%3Dlogin%2CuserInfo%26prefer.serialization%3Dfastjson2%2Chessian2%26release%3D3.2.15%26service-name-mapping%3Dtrue%26side%3Dprovider%26timestamp%3D1724385567323] |
user-service 服务有两个,分别是 192.168.86.49:50053 和 192.168.86.49.50053
看 URL Decoded /dubbo/blog.yanbin.dubbo.providers.UserService/providers
tri://192.168.86.49:50053/blog.yanbin.dubbo.providers.UserService?application=user-service&deprecated=false&dubbo=2.0.2&dynamic=true&environment=product&generic=false&interface=blog.yanbin.dubbo.providers.UserService&logger=jcl&methods=login,userInfo&prefer.serialization=fastjson2,hessian2&release=3.2.15&service-name-mapping=true&side=provider×tamp=1724384955118,tri://192.168.86.49:50054/blog.yanbin.dubbo.providers.UserService?application=user-service&deprecated=false&dubbo=2.0.2&dynamic=true&environment=product&generic=false&interface=blog.yanbin.dubbo.providers.UserService&logger=jcl&methods=login,userInfo&prefer.serialization=fastjson2,hessian2&release=3.2.15&service-name-mapping=true&side=provider×tamp=1724385567323
启动 OrderService 服务
mvn compile exec:java -Dexec.mainClass=blog.yanbin.dubbo.providers.OrderServiceStarter
查看 ZooKeeper
|
1 2 3 4 5 6 7 8 9 10 11 12 |
[zk: localhost:2181(CONNECTED) 21] ls /dubbo [blog.yanbin.dubbo.providers.OrderService, blog.yanbin.dubbo.providers.UserService] [zk: localhost:2181(CONNECTED) 22] get /dubbo/blog.yanbin.dubbo.providers.OrderService/configurators 192.168.86.49 [zk: localhost:2181(CONNECTED) 25] ls /dubbo/blog.yanbin.dubbo.providers.OrderService/providers [tri%3A%2F%2F192.168.86.49%3A50052%2Fblog.yanbin.dubbo.providers.OrderService%3Fapplication%3Dorder-service%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26environment%3Dproduct%26generic%3Dfalse%26interface%3Dblog.yanbin.dubbo.providers.OrderService%26logger%3Djcl%26methods%3DaddOrder%26prefer.serialization%3Dfastjson2%2Chessian2%26release%3D3.2.15%26service-name-mapping%3Dtrue%26side%3Dprovider%26timestamp%3D1724385905085] [zk: localhost:2181(CONNECTED) 26] ls /services [order-service, user-service] [zk: localhost:2181(CONNECTED) 27] ls /services/order-service [192.168.86.49:50052] [zk: localhost:2181(CONNECTED) 29] get /services/order-service/192.168.86.49:50052 {"name":"order-service","id":"192.168.86.49:50052","address":"192.168.86.49","port":50052,"sslPort":null,"payload":{"@class":"org.apache.dubbo.registry.zookeeper.ZookeeperInstance","id":"192.168.86.49:50052","name":"order-service","metadata":{"dubbo.endpoints":"[{\"port\":50052,\"protocol\":\"tri\"}]","dubbo.metadata-service.url-params":"{\"prefer.serialization\":\"fastjson2,hessian2\",\"version\":\"1.0.0\",\"dubbo\":\"2.0.2\",\"release\":\"3.2.15\",\"side\":\"provider\",\"port\":\"50052\",\"protocol\":\"tri\"}","dubbo.metadata.revision":"f0d8ba04b6cebfa43ce7893de4acbbe0","dubbo.metadata.storage-type":null,"timestamp":"1724385905085"}},"registrationTimeUTC":1724385905985,"serviceType":"DYNAMIC","uriSpec":null} |
URL decoded /dubbo/blog.yanbin.dubbo.providers.OrderService/providers
tri://192.168.86.49:50052/blog.yanbin.dubbo.providers.OrderService?application=order-service&deprecated=false&dubbo=2.0.2&dynamic=true&environment=product&generic=false&interface=blog.yanbin.dubbo.providers.OrderService&logger=jcl&methods=addOrder&prefer.serialization=fastjson2,hessian2&release=3.2.15&service-name-mapping=true&side=provider×tamp=1724385905085
现在 Zookeeper 上注册有 user-service 和 order-service 服务
客户端调用测试
UserServiceClient
|
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 37 |
package blog.yanbin.dubbo.clients; import blog.yanbin.dubbo.providers.UserService; import org.apache.dubbo.config.ApplicationConfig; import org.apache.dubbo.config.ReferenceConfig; import org.apache.dubbo.config.RegistryConfig; import org.apache.dubbo.config.bootstrap.DubboBootstrap; import org.apache.dubbo.config.bootstrap.builders.ReferenceBuilder; import java.util.Collections; public class UserServiceClient { public static void main(String[] args) { ReferenceConfig<UserService> reference = ReferenceBuilder.<UserService>newBuilder() .interfaceClass(UserService.class) .application(new ApplicationConfig("userServiceClient")) .addRegistries(Collections.singletonList(new RegistryConfig("zookeeper://localhost:2181"))) // .url("tri://localhost:50053") .build(); DubboBootstrap.getInstance().reference(reference); UserService service = reference.get(); boolean loggedIn = service.login("admin", "666"); System.out.println("login result: " + loggedIn); loggedIn = service.login("admin", "www"); System.out.println("login result: " + loggedIn); for (int i = 0; i < 5; i++) { String userInfo = service.userInfo("admin"); System.out.println("userInfo: " + userInfo); } System.exit(0); } } |
调用
$ mvn compile exec:java -Dexec.mainClass=blog.yanbin.dubbo.clients.UserServiceClient
login result: true
login result: false
userInfo: Detailed information of admin from 50053
userInfo: Detailed information of admin from 50054
userInfo: Detailed information of admin from 50053
userInfo: Detailed information of admin from 50053
userInfo: Detailed information of admin from 50054
上面的 UserServiceClient 如果不用 addRegistries(...) 代码而启用 .url("tri://localhost:50053") 则不从 Zookeeper 中查询服务直接调用 localhost:50053 上的服务
如果关掉 50053 端口上对应的服务进程(Mac 下用 cmd + z),进程处在 suspended 状态,在 Zookeeper 上的 50053 也马上消失掉。如果用 kill -9 <pid> 来强杀进服务进程,在 ZooKeeper 上仍然有一会儿能看到 /services/user-service/192.168.86.49:50053 这个服务,但过个大约一分钟也就消失了。
|
1 2 |
[zk: localhost:2181(CONNECTED) 39] ls /services/user-service [192.168.86.49:50054] |
测试
$ mvn clean compile exec:java -Dexec.mainClass=blog.yanbin.dubbo.clients.UserServiceClient
login result: true
login result: false
userInfo: Detailed information of admin from 50054
userInfo: Detailed information of admin from 50054
userInfo: Detailed information of admin from 50054
userInfo: Detailed information of admin from 50054
userInfo: Detailed information of admin from 50054
本例中未模拟调用失败重试下一次服务的情况,如调用 192.168.86.49:50053 失败,再重试 192.168.86.49:50054 上的 user-service。Dubbo 有更多的调用策略设置,实战时可进一步深究。
测试 OrderService
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package blog.yanbin.dubbo.clients; import blog.yanbin.dubbo.providers.OrderService; import org.apache.dubbo.config.ReferenceConfig; import org.apache.dubbo.config.RegistryConfig; import org.apache.dubbo.config.bootstrap.DubboBootstrap; import org.apache.dubbo.config.bootstrap.builders.ReferenceBuilder; public class OrderServiceClient { public static void main(String[] args) { ReferenceConfig<OrderService> reference = ReferenceBuilder.<OrderService>newBuilder() .interfaceClass(OrderService.class).build(); DubboBootstrap.getInstance().reference(reference) .application("orderServiceClient") .registry(new RegistryConfig("zookeeper://localhost:2181")); OrderService service = reference.get(); int orderId = service.addOrder("laptop", 1); System.out.println("new orderId: " + orderId); System.exit(0); } } |
运行
$mvn clean compile exec:java -Dexec.mainClass=blog.yanbin.dubbo.clients.OrderServiceClient
new orderId: 525400174
Dubbo 3.2.15 与 高于 Java 1.8 的兼容问题
前面提到过 Dubbo 3.2.15 只能用 JDK 1.8, 实测时在运行客户端时,使用 Dubbo 3.2.15 时,如果用的 JDK 版本是 11, 17, 或 21 的话,会出都类似如下各种情形的错误
java.lang.reflect.InaccessibleObjectException: Unable to make field private java.lang.String java.lang.StackTraceElement.fileName accessible: module java.base does not "opens java.lang" to unnamed module
......
java.lang.reflect.InaccessibleObjectException: Unable to make field final int java.math.BigInteger.signum accessible
....
org.apache.dubbo.rpc.RpcException: java.util.concurrent.ExecutionException: org.apache.dubbo.rpc.StatusRpcException: CANCELLED : Canceled by remote peer, errorCode=8
......
java.lang.NoClassDefFoundError: io/netty/util/concurrent/DefaultPromise$1
配置的 JDK_JAVA_OPTIONS 环境变量也不管用
export JDK_JAVA_OPTIONS="--add-opens java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED"
最后一步步从 Java 21 降级到 Java 1.8 才跑通
链接
本文主要参考是官方的 Develop microservice applications based on Dubbo API,其中用的版本组合是 Dubbo 3.2.0-beta.4 + JDK 17。而本人使用 Dubbo 3.2.15 + JDK 1.8+ 都不行,或许是其他依赖的问题,还是因为通信协议是 dubbo 的缘故?以上试验中遇到的各种问题都是通过 Google 找到答案,感谢 Google。
以上只是一个简单的 RPC 远程调用的实例,实际不过是 Dubbo 功能的一小部分,它能做的远比这个丰富。一次粗浅的体验,就看现实中是否有机会用到才会去进一步深入。
本文链接 https://yanbin.blog/dubbo-basic-rpc-call-with-zookeeper/, 来自 隔叶黄莺 Yanbin Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
