Dubbo!用更优雅的方式来实现RPC调用吧

3,875 阅读7分钟

Dubbo是阿里开源的一个RPC调用框架。可以大大简化RPC远程调用实现复杂度,使得可以更专注于业务能力的实现,可以向本地API调用一样使用远程服务接口。

本文主要是对Dubbo的一些关键信息进行记录,并演示下代码中使用Dubbo的具体方法。

1 Dubbo关键概念的说明

1.1 DUBBO基本原理

Dubbo的一个基本的组网以及原理图如下(图片摘自网络):

节点角色说明:

Provider: 暴露服务的服务提供方。  Consumer: 调用远程服务的服务消费方。  Registry: 服务注册与发现的注册中心。  Monitor: 统计服务的调用次调和调用时间的监控中心。

更多新详细的说明,可以参见dubbo官方文档,对于细节描述,本文不再赘述。

1.2 DUBBO对协议的支持

1. Dubbo可以选择协议

  1. Dubbo协议,采用NIO复用单一长连接,并使用线程池并发处理请求,减少握手和加大并发效率,性能较好**(推荐使用)**【问题:在大文件传输时,单一连接会成为瓶颈】
  2. Rmi协议,可与原生RMI互操作,基于TCP协议。【问题:偶尔会连接失败,需重建Stub】
  3. Hessian协议,可与原生的Hessian互操作,基于HTTP协议【需要hessian.jar支持,http短连接的开销大】

2. Dubbo可选用多种序列化方式

  1. Hessian Serialization,性能较好,多语言支持【问题:Hessian各版本兼容不好】
  2. Dubbo Serialization,通过不传输POJO的类元信息,在大量POJO传输时,性能好【问题:当参数对象增加字段时,需外部文件声明】
  3. Json Serialization,纯文本,可跨语言解析,缺省采用FastJson解析【问题:性能较差】
  4. Java Serialization,java原生支持【问题:性能较差】

3. 默认协议为Dubbo,序列化为Hessian

1.3 服务Port和dubbo协议Port之间的区别

进程启动后会有一个对应的port,比如SpringBoot的服务,在application.properties中会设定一个server.port值,这个port通常是指的http协议的对外访问端口号

dubbo作为一个独立协议,对外提供服务的时候,也需要指定一个dubbo协议对应的port信息。两个端口要区分开,不是同一个,这一点,初学者很容易弄混淆。

dubboport指定方式有两种:

    1. XML中指定
<!-- 用dubbo协议在20880端口暴露服务 -->
 <dubbo:protocol name="dubbo" port="20880" ></dubbo:protocol>
    1. SpringBoot的工程,代码中指定
    @Bean
    public ProtocolConfig protocolConfig() {
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setPort(8811);
        protocolConfig.setName("dubbo");
        return protocolConfig;
    }

2 Dubbo服务的代码实现

复杂、大型分布式系统中,一般都需要引入服务注册中心,Dubbo默认是配套Zookeeper作为注册中心,也可以对接其它的分布式服务注册中心。这里比较推荐使用阿里开源的Nacos作为Dubbo配套的注册中心,详细请参见我的另一篇文章:《玩转Nacos!替代Eureka作为配置中心与注册中心》

下面的示例代码中,没有使用注册中心,演示消费者直连服务提供者进行调用的场景,主要侧重点在于说明如何集成与使用Dubbo。

2.1 SpringBoot集成Dubbo对应的Maven依赖

需要引入dubbo包(示例,没有使用注册中心,所以不需要引入zookeeper相关包)。

    <dependency>
        <groupId>com.alibaba.spring.boot</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.0.0</version>
    </dependency>

2.2 Service Provider服务提供方

2.2.1 实现定义的需要对外开放的RPC接口,实现具体的实现逻辑

关注两个注解: 1、@Component 这个就是按照SpringBoot的逻辑,将服务自动Spring托管,完成初始化加载; 2、@com.alibaba.dubbo.config.annotation.Service 这个是Dubbo提供,表示此类是需要通过dubbo对外提供的服务。

如下所示:

@Component
@com.alibaba.dubbo.config.annotation.Service(timeout = 5000)
public class RpcDemoServiceImpl implements RpcDemoService {

    @Override
    public String queryCurrentTime(String queryId) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        String formattedDate = simpleDateFormat.format(new Date());
        System.out.println(queryId + " called, current time is: " + formattedDate);
        return queryId + "|" + formattedDate;
    }
}

2.2.2 启动类中定义需要扫描的dubbo目录

关注2个注解的含义: 1、@SpringBootApplication 指定按照一个常规的SpringBoot应用进行启动; 2、@EnableDubbo 开启对Dubbo的支持,并指定对应目录,将扫描指定的目录下的类,找到所有被@com.alibaba.dubbo.config.annotation.Service标识的需要对外发布的dubbo服务接口类。

如下所示:

@SpringBootApplication
@EnableDubbo(scanBasePackages = {"com.vzn.local.dubbo.provider.service"})
public class ProviderApp {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApp.class);
    }
}

2.2.3 指定dubbo相关设置

两种方式,可以在代码中直接写、也可以在配置文件中指定。

2.2.3.1 通过代码中Bean注入的方式指定

通过@Bean的方式,注入Dubbo的关键定义。与XML配置的内容相同,此处通过对应的JAVA对象的方式,设定具体的值。

主要的几个配置类有: 1、 ApplicationConfig 对应XML中的<dubbo:application />节点的配置信息。

2、 ProtocolConfig 对应XML中的<dubbo:protocol />节点的配置信息。

3、 RegistryConfig 对应XML中的<dubbo:registry />节点的配置信息。

示例代码如下:

@Configuration
public class DubboConfig {

    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("dubbo-provider");
        return applicationConfig;
    }

    @Bean
    public ProtocolConfig protocolConfig() {
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setPort(8811);
        protocolConfig.setName("dubbo");
        return protocolConfig;
    }

    @Bean
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("N/A");
        registryConfig.setRegister(false);

//        registryConfig.setAddress("zookeeper://127.0.0.1:2181");
        return registryConfig;
    }
}
2.2.3.2 通过application.properties中配置
spring.application.name=dubbo-provider
server.port=28811

# dubbo config
dubbo.application.id=dubbo-provider
dubbo.application.name=dubbo-provider
dubbo.protocol.name=dubbo
dubbo.protocol.port=8811
dubbo.registry.address=N/A

至此,一个简单的SpringBoot中通过代码与注解的方式集成dubbo的逻辑实现完毕,直接启动即可。

注意:

  1. 本地测试DEMO,没有设置注册中心,采用直连的方式来进行调用,因此RegistryConfig中配置的address为N/A
  2. 实际使用时,如果使用zookeeper作为注册中心,则此处配置为zookeeper对应的IPport值。(当然,也可以选择Nacos作为配置+注册中心进行使用,推荐)

2.3 Service Consumer服务消费方

2.3.1 本地引用RPC服务接口,实现对应service类

实现一个简单的SpringBoot的工程,在常规的@Service标识的类中,引入远程RPC服务对应的本地API接口,并通过@com.alibaba.dubbo.config.annotation.Reference注解进行标识,此注解即表示此bean将通过RPC的方式调用到远端真实的实现类中的方法,并返回执行结果。

这个就是Dubbo框架的一个优势,将所有的底层逻辑都给封装屏蔽了,使得业务代码中使用RPC的接口时,可以按照本地API的方式进行直接调用,极大的降低了编码难度,使得开发者可以更专注于自身业务,而无需关注具体应该如何RPC调用到远程服务端

示例代码如下:

@org.springframework.stereotype.Service
public class RpcConsumerService {

//    @Reference
//    private RpcDemoService rpcDemoService;

    // 下面两种方式指定的参数,都可以调用成功
    @Reference(protocol = "dubbo", url = "172.31.236.79:8811", interfaceClass = RpcDemoService.class)
    // @Reference(url = "dubbo://172.31.236.79:8811")
    private RpcDemoService rpcDemoService;

    public String getTime(String requestId) {
        return rpcDemoService.queryCurrentTime(requestId);
    }
}

说明:

本地DEMO代码,没有注册中心,采用直连方式,所以上面@Reference中指定了对应的url信息(直连服务具体的IP和dubbo协议port)。

2.3.2 启动类中开启dubbo的支持

如下:

@SpringBootApplication
@EnableDubbo(scanBasePackages = {"com.vzn.local.dubbo.consumer.service"})
public class ConsumerApp {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApp.class);
    }
}

2.3.3 代码中手动注入Dubbo相关的配置信息

如下,和provider类似,但略有不同:

@Configuration
public class DubboConfig {

    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("dubbo-consumer");
        return applicationConfig;
    }

    @Bean
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("N/A");
//        registryConfig.setAddress("zookeeper://127.0.0.1:2181");
        registryConfig.setClient("curator");
        return registryConfig;
    }

    @Bean
    public ConsumerConfig consumerConfig() {
        ConsumerConfig consumerConfig = new ConsumerConfig();
        consumerConfig.setTimeout(3000);
        return consumerConfig;
    }
}

或者直接在application.properties中配置:

spring.application.name=dubbo-consumer
server.port=28812

# dubbo config
dubbo.application.id=dubbo-consumer
dubbo.application.name=dubbo-consumer
dubbo.protocol.name=dubbo
dubbo.registry.address=N/A
dubbo.registry.client=curator

dubbo.server=false
dubbo.consumer.timeout=3000

消费者服务中,增加了一个ConsumerConfig的配置,本示例中简单定义了个调用端超时时间值。

2.3.4 提供个controller服务,用于测试调用

按照SpringBoot的常规实践,提供一个controller服务,用于触发测试使用。

@RestController
@RequestMapping("/demo/dubbo")
public class RpcController {

    @Resource(name = "rpcConsumerService")
    private RpcConsumerService service;

    @GetMapping("/get/time/{requestId}")
    public String getTime(@PathVariable("requestId") String requestId) {
        String time = service.getTime(requestId);
        System.out.println("RPC call the getTime result:" + time);
        return time;
    }
}

至此,一个简单的SpringBoot实现的Dubbo客户端服务就实现完成了,启动后,调用controller服务,即可验证效果。

2.4 测试调用验证

先后启动Provider和Consumer服务,然后在浏览器中发送通知触发consumer服务的方法:

http://127.0.0.1:28812/demo/dubbo/get/time/1

测试结果:

1|2019-12-23 14:21:11.463

可以发现,在consumer中的方法执行时,从consumer进程中RPC调用到了provider进程中对应的方法,并返回了方法具体的执行结果,测试验证完毕。


欢迎关注我的公众号“架构笔录”,原创技术文章第一时间推送,也可互动一起探讨交流技术。