likes
comments
collection

Spring Cloud集成Dubbo实现RPC调用

作者站长头像
站长
· 阅读数 10

往期回顾

前面我们已经介绍了Nacos 的安装与配置,以及Spring Cloud 集成Nacos 作为服务的注册中心,集成Nacos 实现服务的负载均衡和一些常见的负载均衡

Nacos的安装与配置

Spring Cloud集成Nacos作为注册中心

LoadBalacer集成Nacos实现负载均衡

常见的负载均衡策略分析

为了方便测试,之前给大家演示的时候消费者是直接使用的RestTemplates调用服务提供者的HTTP接口,但是在实际开发中我们并不会直接使用RestTemplates ,而是会选择RPC 即服务远程调用

RPC是什么

RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC它假定某些协议的存在,例如TPC/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程序更加容易。

说通俗一点,就是RPC可以让我们调用远程方法像本地方法一样简单,客户端并不会感知到网络通信等等细节

RPC框架的通信与具体的协议无关,RPC可基于HTTP或者TCP协议,现在市面上比较成熟的Java生态中的RPC实现一般是这两种:

  1. 基于HTTP的OpenFeign

Netflix Feign 是 Netflix 公司发布的一种实现负载均衡和服务调用的开源组件。Spring Cloud 将其与 Netflix 中的其他开源服务组件(例如 Eureka、Ribbon 以及 Hystrix 等)一起整合进 Spring Cloud Netflix 模块中,整合后全称为 Spring Cloud Netflix Feign。

Feign 是一种声明式服务调用组件,它在 RestTemplate 的基础上做了进一步的封装。通过 Feign,我们只需要声明一个接口并通过注解进行简单的配置(类似于 Dao 接口上面的 Mapper 注解一样)即可实现对 HTTP 接口的绑定。

但是Feign已经停更了

OpenFeign 全称 Spring Cloud OpenFeign,它是 Spring 官方推出的一种声明式服务调用与负载均衡组件,它的出现就是为了替代进入停更维护状态的 Feign

OpenFeign 是 Spring Cloud 对 Feign 的二次封装,它具有 Feign 的所有功能,并在 Feign 的基础上增加了对 Spring MVC 注解的支持,例如 @RequestMapping、@GetMapping 和 @PostMapping 等

  1. 基于TCP自定义协议的Dubbo

我们今天的主角就是它,接下来我们来重点介绍它吧

Dubbo简介

Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。Dubbo 被设计为高度可扩展,用户可以方便的实现流量拦截、选址的各种定制逻辑。

Dubbo 比起OpenFegin 有自身的优势:

  1. 支持多种传输协议
  2. 支持更多的负载均衡算法
  3. 支持多种容错策略
  4. 开箱即用
  5. 高度可扩展
  6. 面向超大规模微服务集群设计

在分布式系统中,尤其是随着微服务架构的发展,应用的部署、发布、扩缩容变得极为频繁,作为 RPC 消费方,如何动态的发现服务提供方地址成为 RPC 通信的前置条件。Dubbo 提供了自动的地址发现机制,用于应对分布式场景下机器实例动态迁移的问题。如下图所示,通过引入注册中心来协调提供方与消费方的地址,提供者启动之后向注册中心注册自身地址,消费方通过拉取或订阅注册中心特定节点,动态的感知提供方地址列表的变化。

Spring Cloud集成Dubbo实现RPC调用

Dubbo3已经开始拥抱HTTP2,解决了Dubbo之前的互通性问题,以下内容源于官网:

Triple 是 Dubbo3 提出的基于 HTTP2 的开放协议,旨在解决 Dubbo2 私有协议带来的互通性问题。相比于原有 Dubbo2 协议,Triple 有以下优势:

  1. 原生和 gRPC 协议互通。打通 gRPC 生态,降低从 gRPC 至 Dubbo 的迁移成本。
  2. 增强多语言生态。避免因 CPP/C#/RUST 等语言的 Dubbo SDK 能力不足导致业务难以选型适配的问题。
  3. 网关友好。网关无需参与序列化,方便用户从传统的 HTTP 转泛化 Dubbo 调用网关升级至开源或云厂商的 Ingress 方案。
  4. 完善的异步和流式支持。带来从底层协议到上层业务的性能提升,易于构建全链路异步以及严格保证消息顺序的流式服务。

目前 Java 和 Go 的 Dubbo SDK 已全面支持 Triple 协议。 在阿里巴巴,Triple 协议广泛用于跨环境、跨语言、跨生态互通,已有数十万容器生产级使用。

快速开始

接下来我们来对之前的项目进行改造

准备服务提供者

  1. 我们先在父pom里面添加依赖,管理版本
        <dubbo.version>2.7.8</dubbo.version>

            <!--dubbo-->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-spring-boot-starter</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
  1. 在服务提供者user-service 里面添加依赖
        <!--降低版本,详见dubbo issues#7274,否则启动类型转换异常-->
        <dependency>
            <groupId>com.alibaba.spring</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>1.0.10</version>
        </dependency>
         <!--dubbo的依赖-->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
        </dependency>
         <!--这里遇到一个bug,调用dubbo接口会报java.lang.NoClassDefFoundError-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.2.4.Final</version>
        </dependency>
  1. 准备Dubbo服务

先创建实体类

package cuit.epoch.pymjl.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * @author Pymjl
 * @version 1.0
 * @date 2022/8/27 0:35
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password;
    private String email;
    private String phone;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

再创建接口

package cuit.epoch.pymjl.service;

import com.baomidou.mybatisplus.extension.service.IService;
import cuit.epoch.pymjl.entity.User;

/**
 * @author Pymjl
 * @version 1.0
 * @date 2022/8/27 0:56
 **/
public interface UserService extends IService<User> {
    /**
     * 注册
     */
    void register();

    /**
     * 得到用户
     *
     * @param id id
     * @return {@code User}
     */
    User get(Long id);
}

注意,dubbo用到的实体类以及接口需要写在通用模块common-utils 里面方便消费者调用,而真正的接口实现类是放在服务提供者的包里面的

实际的开发中,Dubbo服务一般分为client和core两个模块,client存放dubbo所用到的实体类以及接口,core存放接口的实现类(真正的业务处理),随后服务提供者需要将client打包让消费者引入,向其暴露接口

注意:所有传输的类都需要序列化(实现序列化接口)

然后编写对应的实现类

/**
 * @author Pymjl
 * @version 1.0
 * @date 2022/8/27 0:50
 **/
@DubboService
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);

    @Override
    public void register() {
        User user = new User();
        user.setUsername("pymjl@" + ATOMIC_INTEGER.incrementAndGet());
        user.setPassword("123456");
        user.setPhone("1231234214124");
        user.setEmail("pymjl@" + ATOMIC_INTEGER.incrementAndGet() + ".com");
        baseMapper.insert(user);
    }

    @Override
    public User get(Long id) {
        return baseMapper.selectById(id);
    }
}

一定记得要给主启动类加上@EnableDubbo 注解

最后,编写application.yml

dubbo:
  application:
    name: ${spring.application.name}
    logger: slf4j
  protocol:
    name: dubbo
    port: -1
  registry:
    address: nacos://192.168.199.128:8848
  config-center:
    address: nacos://192.168.199.128:8848
  metadata-report:
    address: nacos://192.168.199.128:8848
  consumer:
    validation: true
    timeout: 3000
    check: false
  scan:
    base-packages: cuit.epoch.pymjl.dubbo

准备消费者

一样的,先在消费者的pom里面导入上面的依赖,然后在主启动类上加上@EnableDubbo 注解

  1. 编写application.yml
dubbo:
  application:
    name: ${spring.application.name}
    logger: slf4j
  protocol:
    name: dubbo
    port: -1
  registry:
    address: nacos://192.168.199.128:8848
  config-center:
    address: nacos://192.168.199.128:8848
  metadata-report:
    address: nacos://192.168.199.128:8848
  consumer:
    validation: true
    timeout: 3000
    check: false
  scan:
    base-packages: cuit.epoch.pymjl.controller
  1. 编写controller
package cuit.epoch.pymjl.controller;

import cuit.epoch.pymjl.entity.User;
import cuit.epoch.pymjl.result.CommonResult;
import cuit.epoch.pymjl.result.ResultUtils;
import cuit.epoch.pymjl.service.UserService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

/**
 * @author Pymjl
 * @version 1.0
 * @date 2022/8/26 12:17
 **/
@RestController
@RequestMapping("/consumer")
public class TestController {
    private static final String SERVICE_URL = "http://UserService";

    @Resource
    RestTemplate restTemplate;

    @DubboReference(check = false)
    UserService userService;

    @GetMapping("/test")
    public CommonResult consumerTest() {
        return restTemplate.getForObject(SERVICE_URL + "/user/test", CommonResult.class);
    }

    @GetMapping("/register")
    public CommonResult<String> register() {
        userService.register();
        return ResultUtils.success();
    }

    @GetMapping("/get/{id}")
    public CommonResult<User> get(@PathVariable("id") Long id) {
        return ResultUtils.success(userService.get(id));
    }
}

开始测试

  1. 启动服务提供者

Spring Cloud集成Dubbo实现RPC调用

  1. 启动消费者

Spring Cloud集成Dubbo实现RPC调用

  1. 观察Nacos 控制台

Spring Cloud集成Dubbo实现RPC调用

  1. 访问测试

Spring Cloud集成Dubbo实现RPC调用

观察后台日志输出

Spring Cloud集成Dubbo实现RPC调用

Spring Cloud集成Dubbo实现RPC调用

至此,Spring Cloud集成Nacos、Dubbo就演示完毕了

项目源码:gitee github