图灵学院-周瑜,大都督

如何将一个应用改造为一个Dubbo应用

首先,新建两个SpringBoot项目,一个叫consumer,一个叫provider

provider项目

项目结构

image.png

pom文件

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. <version>2.6.6</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.projectlombok</groupId>
  9. <artifactId>lombok</artifactId>
  10. <version>1.18.22</version>
  11. </dependency>
  12. </dependencies>

ProviderApplication

  1. @SpringBootApplication
  2. public class ProviderApplication {
  3. public static void main(String[] args) {
  4. SpringApplication.run(ProviderApplication.class);
  5. }
  6. }

User

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. public class User {
  5. private String uid;
  6. private String username;
  7. }

UserService

  1. @Service
  2. public class UserService {
  3. public User getUser(String uid) {
  4. User zhouyu = new User(uid, "zhouyu");
  5. return zhouyu;
  6. }
  7. }

UserController

  1. @RestController
  2. public class UserController {
  3. @Resource
  4. private UserService userService;
  5. @GetMapping("/user/{uid}")
  6. public User getUser(@PathVariable("uid") String uid) {
  7. return userService.getUser(uid);
  8. }
  9. }

application.properties

  1. server.port=8080

consumer项目

项目结构

image.png

pom文件

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. <version>2.6.6</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.projectlombok</groupId>
  9. <artifactId>lombok</artifactId>
  10. <version>1.18.22</version>
  11. </dependency>
  12. </dependencies>

ConsumerApplication

  1. @SpringBootApplication
  2. public class ConsumerApplication {
  3. @Bean
  4. public RestTemplate restTemplate(){
  5. return new RestTemplate();
  6. }
  7. public static void main(String[] args) {
  8. SpringApplication.run(ConsumerApplication.class);
  9. }
  10. }

User

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. public class User {
  5. private String uid;
  6. private String username;
  7. }

OrderService

  1. @Service
  2. public class OrderService {
  3. @Resource
  4. private RestTemplate restTemplate;
  5. public String createOrder(){
  6. User user = restTemplate.getForObject("http://localhost:8080/user/1", User.class);
  7. System.out.println("创建订单");
  8. return user.toString()+" succeeded in creating the order";
  9. }
  10. }

OrderController

  1. @RestController
  2. public class OrderController {
  3. @Resource
  4. private OrderService orderService;
  5. @GetMapping("/createOrder")
  6. public String createOrder() {
  7. return orderService.createOrder();
  8. }
  9. }


application.properties

  1. server.port=8081

consumer中的OrderService会通过RestTemplate调用provider中的UserService。

改造成Dubbo

改造成Dubbo项目,有几件事情要做:

  1. 添加dubbo核心依赖
  2. 添加要使用的注册中心依赖
  3. 添加要使用的协议的依赖
  4. 配置dubbo相关的基本信息
  5. 配置注册中心地址
  6. 配置所使用的协议

增加依赖

  1. <dependency>
  2. <groupId>org.apache.dubbo</groupId>
  3. <artifactId>dubbo-spring-boot-starter</artifactId>
  4. <version>3.0.7</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.dubbo</groupId>
  8. <artifactId>dubbo-rpc-dubbo</artifactId>
  9. <version>3.0.7</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.apache.dubbo</groupId>
  13. <artifactId>dubbo-registry-zookeeper</artifactId>
  14. <version>3.0.7</version>
  15. </dependency>

配置properties

  1. dubbo.application.name=provider-application
  2. dubbo.protocol.name=dubbo
  3. dubbo.protocol.port=20880
  4. dubbo.registry.address=zookeeper://127.0.0.1:2181

改造服务

consumer和provider中都用到了User类,所以可以单独新建一个maven项目用来存consumer和provider公用的一些类,新增一个common模块,把User类转移到这个模块中

要改造成Dubbo,得先抽象出来服务,用接口表示。

像UserService就是一个服务,不过我们得额外定义一个接口,我们把之前的UserService改为UserServiceImpl,然后新定义一个接口UserService,该接口表示一个服务,UserServiceImpl为该服务的具体实现。

  1. public interface UserService {
  2. public User getUser(String uid);
  3. }
  1. @DubboService
  2. public class UserServiceImpl implements UserService {
  3. public User getUser(String uid) {
  4. User zhouyu = new User(uid, "zhouyu");
  5. return zhouyu;
  6. }
  7. }

注意:要把Spring中的@Service注解替换成Dubbo中的@DubboService注解。

然后把UserService接口也转移到common模块中去,在provider中依赖common。

改造之后的provider为:
image.png

其实UserController也可以去掉,去掉之后provider就更加简单了
image.png

此时就可以启动该Provider了,注意先启动zookeeper(高版本的Zookeeper启动过程中不仅会占用2181,也会占用8080,所以可以把provider的端口改为8082

开启Dubbo

在ProviderApplication上加上@EnableDubbo(scanBasePackages = “com.zhouyu.service”),表示Dubbo会去扫描某个路径下的@DubboService,从而对外提供该Dubbo服务。

  1. @SpringBootApplication
  2. @EnableDubbo(scanBasePackages = "com.zhouyu.service")
  3. public class ProviderApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(ProviderApplication.class);
  6. }
  7. }

调用Dubbo服务

引入依赖

在consumer中如果想要调用Dubbo服务,也要引入相关的依赖:

  1. 引入common,主要是引入要用调用的接口
  2. 引入dubbo依赖
  3. 引入需要使用的协议的依赖
  4. 引入需要使用的注册中心的依赖
  1. <dependency>
  2. <groupId>org.apache.dubbo</groupId>
  3. <artifactId>dubbo-spring-boot-starter</artifactId>
  4. <version>3.0.7</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.dubbo</groupId>
  8. <artifactId>dubbo-rpc-dubbo</artifactId>
  9. <version>3.0.7</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.apache.dubbo</groupId>
  13. <artifactId>dubbo-registry-zookeeper</artifactId>
  14. <version>3.0.7</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>com.zhouyu</groupId>
  18. <artifactId>common</artifactId>
  19. <version>1.0-SNAPSHOT</version>
  20. </dependency>

引入服务

通过@DubboReference注解来引入一个Dubbo服务。

  1. @Service
  2. public class OrderService {
  3. @DubboReference
  4. private UserService userService;
  5. public String createOrder(){
  6. User user = userService.getUser("1");
  7. System.out.println("创建订单");
  8. return user.toString()+" succeeded in creating the order";
  9. }
  10. }

这样就不需要用RestTemplate了。

配置properties

  1. dubbo.application.name=consumer-application
  2. dubbo.registry.address=zookeeper://127.0.0.1:2181

调用

如果User没有实现Serializable接口,则会报错。

总结

自此,Dubbo的改造就完成了,总结一下:

  1. 添加pom依赖
  2. 配置dubbo应用名、协议、注册中心
  3. 定义服务接口和实现类
  4. 使用@DubboService来定义一个Dubbo服务
  5. 使用@DubboReference来使用一个Dubbo服务
  6. 使用@EnableDubbo开启Dubbo

Dubbo3.0新特性介绍

注册模型的改变

在服务注册领域,市面上有两种模型,一种是应用级注册,一种是接口级注册,在Spring Cloud中,一个应用是一个微服务,而在Dubbo2.7中,一个接口是一个微服务。

所以,Spring Cloud在进行服务注册时,是把应用名以及应用所在服务器的IP地址和应用所绑定的端口注册到注册中心,相当于key是应用名,value是ip+port,而在Dubbo2.7中,是把接口名以及对应应用的IP地址和所绑定的端口注册到注册中心,相当于key是接口名,value是ip+port。

所以在Dubbo2.7中,一个应用如果提供了10个Dubbo服务,那么注册中心中就会存储10对keyvalue,而Spring Cloud就只会存一对keyvalue,所以以Spring Cloud为首的应用级注册是更加适合的。

所以Dubbo3.0中将注册模型也改为了应用级注册,提升效率节省资源的同时,通过统一注册模型,也为各个微服务框架的互通打下了基础。

新一代RPC协议

定义了全新的 RPC 通信协议 – Triple,一句话概括 Triple:它是基于 HTTP/2 上构建的 RPC 协议,完全兼容 gRPC,并在此基础上扩展出了更丰富的语义。 使用 Triple 协议,用户将获得以下能力

  • 更容易到适配网关、Mesh架构,Triple 协议让 Dubbo 更方便的与各种网关、Sidecar 组件配合工作。
  • 多语言友好,推荐配合 Protobuf 使用 Triple 协议,使用 IDL 定义服务,使用 Protobuf 编码业务数据。
  • 流式通信支持。Triple 协议支持 Request Stream、Response Stream、Bi-direction Stream

当使用Triple协议进行RPC调用时,支持多种方式来调用服务,只不过在服务接口中要定义不同的方法,比如:

  1. public interface DemoService {
  2. // UNARY
  3. String sayHello(String name);
  4. // SERVER_STREAM
  5. default void sayHelloServerStream(String name, StreamObserver<String> response) {
  6. }
  7. // CLIENT_STREAM / BI_STREAM
  8. default StreamObserver<String> sayHelloStream(StreamObserver<String> response) {
  9. return response;
  10. }
  11. }

UNARY

unary,就是正常的调用方法

服务实现类对应的方法:

  1. // UNARY
  2. @Override
  3. public String sayHello(String name) {
  4. return "Hello " + name;
  5. }

服务消费者调用方式:

  1. String result = demoService.sayHello("zhouyu");

SERVER_STREAM

服务实现类对应的方法:

  1. // SERVER_STREAM
  2. @Override
  3. public void sayHelloServerStream(String name, StreamObserver<String> response) {
  4. response.onNext(name + " hello");
  5. response.onNext(name + " world");
  6. response.onCompleted();
  7. }

服务消费者调用方式:

  1. demoService.sayHelloServerStream("zhouyu", new StreamObserver<String>() {
  2. @Override
  3. public void onNext(String data) {
  4. // 服务端返回的数据
  5. System.out.println(data);
  6. }
  7. @Override
  8. public void onError(Throwable throwable) {}
  9. @Override
  10. public void onCompleted() {
  11. System.out.println("complete");
  12. }
  13. });

CLIENT_STREAM

服务实现类对应的方法:

  1. // CLIENT_STREAM
  2. @Override
  3. public StreamObserver<String> sayHelloStream(StreamObserver<String> response) {
  4. return new StreamObserver<String>() {
  5. @Override
  6. public void onNext(String data) {
  7. // 接收客户端发送过来的数据,然后返回数据给客户端
  8. response.onNext("result:" + data);
  9. }
  10. @Override
  11. public void onError(Throwable throwable) {}
  12. @Override
  13. public void onCompleted() {
  14. System.out.println("completed");
  15. }
  16. };
  17. }

服务消费者调用方式:

  1. StreamObserver<String> streamObserver = demoService.sayHelloStream(new StreamObserver<String>() {
  2. @Override
  3. public void onNext(String data) {
  4. System.out.println("接收到响应数据:"+ data);
  5. }
  6. @Override
  7. public void onError(Throwable throwable) {}
  8. @Override
  9. public void onCompleted() {
  10. System.out.println("接收到响应数据完毕");
  11. }
  12. });
  13. // 发送数据
  14. streamObserver.onNext("request zhouyu hello");
  15. streamObserver.onNext("request zhouyu world");
  16. streamObserver.onCompleted();

BI_STREAM

和CLIENT_STREAM一样

Dubbo3.0跨语言调用

在工作中,我们用Java语言通过Dubbo提供了一个服务,另外一个应用(也就是消费者)想要使用这个服务,如果消费者应用也是用Java语言开发的,那没什么好说的,直接在消费者应用引入Dubbo和服务接口相关的依赖即可。

但是,如果消费者应用不是用Java语言写的呢,比如是通过python或者go语言实现的,那就至少需要满足两个条件才能调用Java实现的Dubbo服务:

  1. Dubbo一开始是用Java语言实现的,那现在就需要一个go语言实现的Dubbo框架,也就是现在的dubbo-go,然后在go项目中引入dubbo-go,从而可以在go项目中使用dubbo,比如使用go语言去暴露和使用Dubbo服务。
  2. 我们在使用Java语言开发一个Dubbo服务时,会把服务接口和相关类,单独抽象成为一个Maven项目,实际上就相当于一个单独的jar包,这个jar能被Java项目所使用,但不能被go项目所使用,所以go项目中该如何使用Java语言所定义的接口呢?直接用是不太可能的,只能通过间接的方式来解决这个问题,除开Java语言之外,那有没有其他技术也能定义接口呢?并且该技术也是Java和go都支持,这就是protobuf。

protobuf

我们可以通过protobuf来定义接口,然后通过protobuf的编译器将接口编译为特定语言的实现。

在provider项目中定义一个userservice.proto文件,路径为src/main/proto/userservice.proto:

  1. syntax = "proto3";
  2. package api;
  3. option go_package = "./;api";
  4. option java_multiple_files = true;
  5. option java_package = "com.zhouyu";
  6. option java_outer_classname = "UserServiceProto";
  7. service UserService {
  8. rpc GetUser (UserRequest) returns (User) {}
  9. }
  10. // The response message containing the greetings
  11. message UserRequest {
  12. string uid = 1;
  13. }
  14. // The response message containing the greetings
  15. message User {
  16. string uid = 1;
  17. string username = 2;
  18. }

相当于定义了一个HelloService服务,并且定义了一个getUser方法,接收UserRequest类型的参数,返回User类型的对象。

编译成Java

在provider项目中的pom文件中添加相关maven插件:

  1. <build>
  2. <extensions>
  3. <extension>
  4. <groupId>kr.motd.maven</groupId>
  5. <artifactId>os-maven-plugin</artifactId>
  6. <version>1.6.1</version>
  7. </extension>
  8. </extensions>
  9. <plugins>
  10. <plugin>
  11. <groupId>org.apache.maven.plugins</groupId>
  12. <artifactId>maven-compiler-plugin</artifactId>
  13. <version>3.7.0</version>
  14. <configuration>
  15. <source>1.8</source>
  16. <target>1.8</target>
  17. </configuration>
  18. </plugin>
  19. <plugin>
  20. <groupId>org.xolstice.maven.plugins</groupId>
  21. <artifactId>protobuf-maven-plugin</artifactId>
  22. <version>0.6.1</version>
  23. <configuration>
  24. <protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}</protocArtifact>
  25. <outputDirectory>build/generated/source/proto/main/java</outputDirectory>
  26. <clearOutputDirectory>false</clearOutputDirectory>
  27. <protocPlugins>
  28. <protocPlugin>
  29. <id>dubbo</id>
  30. <groupId>org.apache.dubbo</groupId>
  31. <artifactId>dubbo-compiler</artifactId>
  32. <version>0.0.3</version>
  33. <mainClass>org.apache.dubbo.gen.dubbo.Dubbo3Generator</mainClass>
  34. </protocPlugin>
  35. </protocPlugins>
  36. </configuration>
  37. <executions>
  38. <execution>
  39. <goals>
  40. <goal>compile</goal>
  41. <goal>test-compile</goal>
  42. </goals>
  43. </execution>
  44. </executions>
  45. </plugin>
  46. <plugin>
  47. <groupId>org.codehaus.mojo</groupId>
  48. <artifactId>build-helper-maven-plugin</artifactId>
  49. <version>3.0.0</version>
  50. <executions>
  51. <execution>
  52. <phase>generate-sources</phase>
  53. <goals>
  54. <goal>add-source</goal>
  55. </goals>
  56. <configuration>
  57. <sources>
  58. <source>build/generated/source/proto/main/java</source>
  59. </sources>
  60. </configuration>
  61. </execution>
  62. </executions>
  63. </plugin>
  64. </plugins>
  65. </build>

并且可以把common依赖去掉,然后运行provider中lifecycle的compile,就会进行编译了,
image.png
并且会编译出对应的接口等信息,编译完成后,会生成一些类:
image.png

如果Java没有蓝色的,就
image.png

其中就包括了一个UserService接口,所以我们的UserServiceImpl就可以实现这个接口了:

  1. @DubboService
  2. public class UserServiceImpl implements UserService {
  3. public User getUser(UserRequest userRequest) {
  4. User user = User.newBuilder().setUid(userRequest.getUid()).setUsername("zhouyu").build();
  5. return user;
  6. }
  7. }

而对于想要调用UserService服务的消费者而言,其实也是一样的改造,只需要使用同一份userservice.proto进行编译就可以了,比如现在有一个go语言的消费者。

go消费者调用java服务

首先,在IDEA中新建一个go模块:
image.png

然后把userservice.proto复制到go-consumer/proto下,然后进行编译,编译成为go语言对应的服务代码,只不过go语言中没有maven这种东西可以帮助我们编译,我们只能用原生的protobuf的编译器进行编译。

这就需要大家在机器上下载、安装protobuf的编译器:protoc

  1. 下载地址:https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protoc-3.20.1-win64.zip
  2. 解压之后,把protoc-3.20.1-win64\bin添加到环境变量中去
  3. 在cmd中执行protoc —version,能正常看到版本号即表示安装成功

另外还需要安装go:

  1. 下载地址:https://studygolang.com/dl/golang/go1.18.1.windows-amd64.msi
  2. 然后直接下一步安装
  3. 在cmd中(新开一个cmd窗口)执行go version,能正常看到版本号即表示安装成功

然后在go-consumer下新建文件夹api,进入到go-consumer/proto下,运行:

  1. go env -w GO111MODULE=on
  2. go env -w GOPROXY=https://goproxy.cn,direct
  3. go get -u github.com/dubbogo/tools/cmd/protoc-gen-go-triple
  4. go install github.com/golang/protobuf/protoc-gen-go
  5. go install github.com/dubbogo/tools/cmd/protoc-gen-go-triple
  6. protoc -I. userservice.proto --go_out=../api --go-triple_out=../api

这样就会在go-consumer/api下生成一个userservice.pb.go文件和userservice_triple.pb.go文件

如果IDEA中提示要安装插件,就安装一下:
image.png

安装完之后,代码可能会报错,可以在go-consumer目录下执行命令下载依赖:

  1. go mod tidy

然后就可以写go语言的服务消费者了,新建一个consumer.go,内容为:

  1. package main
  2. import (
  3. "context"
  4. "dubbo.apache.org/dubbo-go/v3/common/logger"
  5. "dubbo.apache.org/dubbo-go/v3/config"
  6. _ "dubbo.apache.org/dubbo-go/v3/imports"
  7. "go-consumer/api"
  8. )
  9. var userServiceImpl = new(api.UserServiceClientImpl)
  10. // export DUBBO_GO_CONFIG_PATH=conf/dubbogo.yml
  11. func main() {
  12. config.SetConsumerService(userServiceImpl)
  13. config.Load()
  14. logger.Info("start to test dubbo")
  15. req := &api.UserRequest{
  16. Uid: "1",
  17. }
  18. user, err := userServiceImpl.GetUser(context.Background(), req)
  19. if err != nil {
  20. logger.Error(err)
  21. }
  22. logger.Infof("client response result: %v\n", user)
  23. }

然后在go-consumer下新建conf/dubbogo.yml,用来配置注册中心:

  1. dubbo:
  2. registries:
  3. demoZK:
  4. protocol: zookeeper
  5. address: 127.0.0.1:2181
  6. consumer:
  7. references:
  8. UserServiceClientImpl:
  9. protocol: tri
  10. interface: com.zhouyu.UserService

注意这里配置的协议为tri,而不是dubbo,在provider端也得把协议改为tri:
image.png

然后就可以运行consumer.go了,只不过需要在environment中添加一个参数:
image.png

运行成功:
image.png