Apache Dubbo实战:JavaSDK使用

文章目录

一、写在前面

官方文档:https://cn.dubbo.apache.org/zh-cn/overview/

概念介绍部分就不多做介绍了,看官方文档吧。

本文实践为主,从头到尾梳理Dubbo使用的细节。

二、基于zookeeper:快速创建dubbo应用

1、maven包(客户端+服务端)(注意spring版本)

xml 复制代码
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-bom</artifactId>
                <version>3.3.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
xml 复制代码
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-zookeeper-spring-boot-starter</artifactId>
        </dependency>

2、application.yml 配置文件(客户端+服务端)

yml 复制代码
dubbo:
  application:
  	# 客户端和服务端要不一样,客户端:dubbo-springboot-demo-consumer
    name: dubbo-springboot-demo-provider
  protocol:
    name: tri
    # 随机端口
    port: -1
  registry:
    id: zk-registry
    address: zookeeper://127.0.0.1:2181

3、定义公共接口

java 复制代码
package com.demo.springbootdemo;

public interface DemoService {

    String sayHello(String name);

}

4、启动类添加注解@EnableDubbo

java 复制代码
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
// @EnableDubbo 注解必须配置,否则将无法加载 Dubbo 注解定义的服务,@EnableDubbo 可以定义在主类上
@EnableDubbo

5、服务端

java 复制代码
import org.apache.dubbo.config.annotation.DubboService;

// 定义好 Dubbo 服务接口后,提供服务接口的实现逻辑,并用 @DubboService 注解标记,就可以实现 Dubbo 的服务暴露
// 支持很多参数
// @DubboService(registry="zk-registry") 可以手动选择注册中心(通过id关联)
// @DubboService(version = "1.0.0", group = "dev", timeout = 5000)
// @DubboService(methods = {@Method(name = "sayHello", timeout = 5000)}) // 指定某个方法超时时间
@DubboService
public class DemoServiceImpl implements DemoService {

    @Override
    public String sayHello(String name) {
        return "Hello " + name;
    }
}

6、客户端

java 复制代码
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Consumer implements CommandLineRunner {
    // scope="remote" is used to force mock remote service call
    // 也有很多参数
    // @DubboReference(version = "1.0.0", group = "dev", timeout = 5000)
    @DubboReference(scope = "remote")
    private DemoService demoService;

    @Override
    public void run(String... args) throws Exception {
        String result = demoService.sayHello("world");
        System.out.println("Receive result ======> " + result);
    }
}

7、启动试试吧

8、拓展:使用 Java Config 代替注解

注意,Java Config 是 DubboService 或 DubboReference 的替代方式,对于有复杂配置需求的服务建议使用这种方式。

java 复制代码
@Configuration
public class ProviderConfiguration {
    @Bean
    public ServiceConfig demoService() {
        ServiceConfig service = new ServiceConfig();
        service.setRegistry("zk-registry");
        return service;
    }
}
java 复制代码
@Configuration
public class ProviderConfiguration {
    @Bean
    public ServiceBean demoService() {
        ServiceBean service = new ServiceBean();
        service.setInterface(DemoService.class);
        service.setRef(new DemoServiceImpl());
        service.setGroup("dev");
        service.setVersion("1.0.0");
        Map<String, String> parameters = new HashMap<>();
        service.setParameters(parameters);
        return service;
    }
}

三、拓展配置

1、注册中心

yml 复制代码
# application.yml
dubbo
 registry
   address: zookeeper://localhost:2181
   # zk认证,可以不配置
   username: hello
   password: 1234

多级注册中心:

对于所有的 Service 服务,向所有全局默认注册中心注册服务地址。

对于所有的 Reference 服务,从所有全局默认注册中心订阅服务地址。

yml 复制代码
# application.yml (Spring Boot)
dubbo
 registries
  beijingRegistry
   address: zookeeper://localhost:2181
  shanghaiRegistry
   address: zookeeper://localhost:2182

# 设置默认全局的注册中心
# application.yml (Spring Boot)
dubbo
 registries
  beijingRegistry
   address: zookeeper://localhost:2181
   default: true
  shanghaiRegistry
   address: zookeeper://localhost:2182
   default: false

# 可以在开发调试的过程中,设置本地启动的只注册在本地zk,并且调用测试环境的dubbo服务。
# 防止自己的服务注册到测试环境
dubbo:
  registries:
    #仅在开发时启用
    private:
      id: private
      # register: true:服务提供者会将服务注册到这个注册中心。
	  # subscribe: false:服务消费者不会从这个注册中心订阅服务。
      register: true
      subscribe: false
      address: zookeeper://127.0.0.1:2181
    public:
      id: public
      protol: zookeeper
      # register: false:服务提供者不会将服务注册到这个注册中心。
      # subscribe: true:服务消费者会从这个注册中心订阅服务。
      register: false
      subscribe: true
      address: zookeeper://test.zk.com:2181

2、版本与分组

Dubbo服务中,接口并不能唯一确定一个服务,只有 接口+分组+版本号 的三元组才能唯一确定一个服务。

当同一个接口针对不同的业务场景、不同的使用需求或者不同的功能模块等场景,可使用服务分组来区分不同的实现方式。同时,这些不同实现所提供的服务是可并存的,也支持互相调用。

当接口实现需要升级又要保留原有实现的情况下,即出现不兼容升级时,我们可以使用不同版本号进行区分。

java 复制代码
@DubboService(group = "group1", version = "1.0")
public class DevelopProviderServiceV1 implements DevelopService{
    @Override
    public String invoke(String param) {
        StringBuilder s = new StringBuilder();
        s.append("ServiceV1 param:").append(param);
        return s.toString();
    }
}

@DubboService(group = "group2", version = "2.0")
public class DevelopProviderServiceV2 implements DevelopService{
    @Override
    public String invoke(String param) {
        StringBuilder s = new StringBuilder();
        s.append("ServiceV2 param:").append(param);
        return s.toString();
    }
}
java 复制代码
// 客户端指定 分组和版本号
@DubboReference(group = "demo")
private DemoService demoService;

@DubboReference(group = "demo2")
private DemoService demoService2;

//group值为*,标识匹配任意服务分组
@DubboReference(group = "*")
private DemoService demoService2;

@DubboReference(group = "group1", version = "1.0")
private DevelopService developService;

@DubboReference(group = "group2", version = "2.0")
private DevelopService developServiceV2;

3、传递调用参数

java 复制代码
// 客户端传递参数
RpcContext.getClientAttachment().setAttachment("index", "1"); // 隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,比如用于框架集成
xxxService.xxx(); // 远程调用
// ...
java 复制代码
// 服务端,读取调用参数
public class XxxServiceImpl implements XxxService {

    public void xxx() {
        // 获取客户端隐式传入的参数,比如用于框架集成
        String index = RpcContext.getServerAttachment().getAttachment("index");
    }
}

参数透传问题

请注意!setAttachment 设置的 KV 对,在完成下面一次远程调用会被清空,即多次远程调用要多次设置!这一点与 Dubbo2 中的行为是不一致的!

比如,对于 Dubbo2 而言,在 A 端设置的参数,调用 B 以后,如果 B 继续调用了 C,原来在 A 中设置的参数也会被带到 C 端过去(造成参数污染的问题)。对于 Dubbo3,B 调用 C 时的上下文是干净的,不会包含最开始在 A 中设置的参数。

Dubbo3 提供的了支持参数透传的能力。通过实现以下 SPI 用户可以自行指定需要透传的参数,select 的结果(可以从 RpcClientAttachment 获取当前所有参数)将作为需要透传的键值对传递到下一跳,如果返回 null 则表示不透传参数。

java 复制代码
@SPI
public interface PenetrateAttachmentSelector {

    /**
     * Select some attachments to pass to next hop.
     * These attachments can fetch from {@link RpcContext#getServerAttachment()} or user defined.
     *
     * @return attachment pass to next hop
     */
    Map<String, Object> select();

}

4、泛化调用

文档:https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/tasks/framework/generic/

泛化调用适用于老版本 dubbo 通信协议,如果您使用的是 3.3 及之后版本的 triple 协议,请直接使用 triple 自带的 http application/json 能力直接发起服务调用

泛化调用(客户端泛化调用)是指在调用方没有服务提供方 API(SDK)的情况下,对服务方进行调用,并且可以正常拿到调用结果。调用方没有接口及模型类元,知道服务的接口的全限定类名和方法名的情况下,可以通过泛化调用调用对应接口。

java 复制代码
private GenericService genericService;

public static void main(String[] args) throws Exception {
	ApplicationConfig applicationConfig = new ApplicationConfig();
	applicationConfig.setName("generic-call-consumer");
	RegistryConfig registryConfig = new RegistryConfig();
	registryConfig.setAddress("zookeeper://127.0.0.1:2181");

	ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
	referenceConfig.setInterface("org.apache.dubbo.samples.generic.call.api.HelloService");
	applicationConfig.setRegistry(registryConfig);
	referenceConfig.setApplication(applicationConfig);
	referenceConfig.setGeneric("true");
	// do not wait for result, 'false' by default
	referenceConfig.setAsync(true);
	referenceConfig.setTimeout(7000);

	genericService = referenceConfig.get();
}

public static void invokeSayHello() throws InterruptedException {
	Object result = genericService.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"world"});
	CountDownLatch latch = new CountDownLatch(1);

	CompletableFuture<String> future = RpcContext.getContext().getCompletableFuture();
	future.whenComplete((value, t) -> {
		System.err.println("invokeSayHello(whenComplete): " + value);
		latch.countDown();
	});

	System.err.println("invokeSayHello(return): " + result);
	latch.await();
}

5、泛化实现

文档:https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/tasks/framework/more/generic-impl/

泛接口实现方式主要用于服务器端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的远程服务 Mock 框架,可通过实现 GenericService 接口处理所有服务请求。

使用场景

注册服务: 服务提供者在服务注册表中注册服务,例如 Zookeeper,服务注册表存储有关服务的信息,例如其接口、实现类和地址。

部署服务: 服务提供商将服务部署在服务器并使其对消费者可用。

调用服务: 使用者使用服务注册表生成的代理调用服务,代理将请求转发给服务提供商,服务提供商执行服务并将响应发送回消费者。

监视服务:提供者和使用者可以使用 Dubbo 框架监视服务,允许他们查看服务的执行情况,并在必要时进行调整。

6、Filter过滤器

java 复制代码
package org.apache.dubbo.samples.extensibility.filter.provider;

import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.AsyncRpcResult;

public class AppendedFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        Result result= invoker.invoke(invocation);
        // Obtain the returned value
        Result appResponse = ((AsyncRpcResult) result).getAppResponse();
        // Appended value
        appResponse.setValue(appResponse.getValue()+"'s customized AppendedFilter");
        return result;
    }
}

SPI配置:

resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter文件中添加如下配置:
appended=org.apache.dubbo.samples.extensibility.filter.provider.AppendedFilter

配置文件:

resources/application.properties文件中添加如下配置,激活刚才的自定义 Filter 实现:

yml 复制代码
# Apply AppendedFilter
dubbo.provider.filter=appended

除了通过配置激活 Filter 实现之外,还可以通过为实现类增加 @Activate 注解,以在满足某些条件时自动激活 Filter 实现,如:

@Activate(group="provider")

public class AppendedFilter implements Filter {}

这个 Filter 实现将在 Provider 提供者端自动被激活。

附:Dubbo 支持的 Spring Boot Starter 清单

官方文档:https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/config/spring/spring-boot/#starter%E5%88%97%E8%A1%A8

以下是一些 dubbo-spring-boot-starter 版本对应的 SpringBoot、JDK 依赖:

版本 兼容 Spring Boot 范围

3.3.x [1.x ~ 3.x)

3.2.x [1.x ~ 3.x)

3.1.x [1.x ~ 2.x)

2.7.x [1.x ~ 2.x)

其他组件starter:

以下是 Dubbo 官方社区提供的 starter 列表(3.3.0+ 版本),方便在 Spring Boot 应用中快速使用:
dubbo-spring-boot-starter,管理 dubbo 核心依赖,用于识别 application.properties 或 application.yml 中 dubbo. 开头的配置项,扫描 @DubboService 等注解。
dubbo-spring-boot-starter3,管理 dubbo 核心依赖,与 dubbo-spring-boot-starter 相同,支持 spring boot 3.2 版本。
dubbo-nacos-spring-boot-starter,管理 nacos-client 等依赖,使用 Nacos 作为注册中心、配置中心时引入。
dubbo-zookeeper-spring-boot-starter,管理 zookeeper、curator 等依赖,使用 Zookeeper 作为注册中心、配置中心时引入(Zookeeper server 3.4 及以下版本使用)。
dubbo-zookeeper-curator5-spring-boot-starter,管理 zookeeper、curator5 等依赖,使用 Zookeeper 作为注册中心、配置中心时引入。
dubbo-sentinel-spring-boot-starter,管理 sentinel 等依赖,使用 Sentinel 进行限流降级时引入。
dubbo-seata-spring-boot-starter,管理 seata 等依赖,使用 Seata 作为分布式事务解决方案时引入。
dubbo-observability-spring-boot-starter,加入该依赖将自动开启 Dubbo 内置的 metrics 采集,可用于后续的 Prometheus、Grafana 等监控系统。
dubbo-tracing-brave-spring-boot-starter,管理 brave/zipkin、micrometer 等相关相关依赖,使用 Brave/Zipkin 作为 Tracer,将 Trace 信息 export 到 Zipkin。
dubbo-tracing-otel-otlp-spring-boot-starter,管理 brave/zipkin、micrometer 等相关相关依赖,使用 OpenTelemetry 作为 Tracer,将 Trace 信息 export 到 OTlp Collector。
dubbo-tracing-otel-zipkin-spring-boot-starter,管理 brave/zipkin、micrometer 等相关相关依赖,使用 OpenTelemetry 作为 Tracer,将 Trace 信息 export 到 Zipkin。