Dubbo(二)_入门

发布时间 2023-07-29 23:49:15作者: Stitches

什么是 dubbo

dubbo 最新版本为 3.x,Apache Dubbo 是一款易用、高性能的 web 和 rpc 框架,同时为构建企业级微服务提供服务发现、流量治理、可观测、认证鉴权等能力。 Dubbo3 替代了阿里运行多年的 HSF 框架,依托于 Dubbo 实现自己的微服务解决方案(DNS),即 Dubbo、Nacos(服务注册与管理)、Sentinel(服务降级、流量治理)。

架构的发展

水平扩展:

部署多个 tomcat服务器,client 请求时通过 loadbalance nginx 实现的特定负载均衡算法把请求发到指定服务器。这样进一步提高了系统的稳定性、可用性。但是存在数据一致性问题。

垂直扩展:

把一个单体架构划分为多个子系统,每个子系统部署在自己的 tomcat 中,多个子系统独立部署共享存储数据。这样某个子系统出现问题不会影响其它子系统的运行,便于系统维护。

RPC架构:

RPC架构是由垂直架构发展而来的,垂直架构中每个进程运行在独立的子系统中,RPC考虑的是子系统间的模块功能调用问题,Service 调用 Service 的问题。RPC 避免了服务的冗余开发,其它需要服务调用时子系统直接调用即可。RPC的核心在于如何与其它进程建立通信,需要考虑通信的方式(HTTP、TCP),采用的协议、数据序列化方式(protobuf、thrift)。

企业服务总线:

ESB,不同语言开发的系统需要调用其它语言开发的服务,这种异构系统如何进行 RPC 调用呢?因为不同进程间通过网络进行服务调用,可以在中间设计一个数据转换层(Grpc、Protobuf、Thrift IDL)将 请求参数/响应结果 进行转换,这样就实现了不同编程语言间服务的调用。

ESB 进程间通信的方式有两种,同步和异步。其中同步调用就是 RPC的调用方式,向某个服务发请请求,然后阻塞等待请求返回,适用于实时性高的系统;异步调用则可以利用 MQ消息队列,将消息放入到队列延缓处理过程。

Dubbo开发

Dubbo代码结构

代码中包含以下角色:

  • provider:功能提供者;
  • consumer:功能调用者(消费者);
  • commons-api:通用内容;
  • registry:注册中心;

软件版本

模块开发

项目整体为微服务结构,主模块 dubbo_learn 下包含 3个子模块(dubbo-01-api 用作服务 api接口定义及公共模块定义、dubbo-02-provider 用作服务提供方实现了具体的服务、dubbo-03-consumer用作消费方);

父项目依赖配置:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.32</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.10</version>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>3.2.0</version>
</dependency>

dubbo-01-api模块:

//定义了实体类 User、服务接口 UserService
package com.xjx.entity;

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private String name;
    private String password;
}



package com.xjx.service;

public interface UserService {
    public boolean login(String name, String password);
}

dubbo-02-provider模块:

provider 模块引入了 api 模块的依赖,并实现了 api模块的接口,它需要结合 Spring 将服务注册为 Bean对象并将服务发布出去,这里采用 xml 方式注册 Bean。

// 服务接口的具体实现
public class UserServiceImpl implements UserService{
    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login name " + name + " password " + password);
        return false;
    }
}

// 服务持续提供,解析配置文件并阻塞运行
public class ProviderMain {
    public static void main(String[] args) throws InterruptedException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-provider.xml");
        context.start();

        //阻塞启动
        new CountDownLatch(1).await();
    }
}

配置文件定义为 applicationContext-provider.xml,需要定义好 rpc 的通信协议及端口、对外提供服务的名称:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <!-- provider名称 -->
    <dubbo:application name="dubbo-02-provider"/>

    <!-- 定义rpc通信协议 -->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!-- bean对象注册 -->
    <bean id="userService" class="com.xjx.service.UserServiceImpl"/>

    <!-- 服务发布 -->
    <dubbo:service interface="com.xjx.service.UserService" ref="userService"/>
</beans>

dubbo-03-consumer模块:

服务消费者方需要引入服务接口 api模块,并在配置文件中定义远端服务获取的相关配置。

public class ClientApplication {
    public static void main(String[] args) throws InterruptedException {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext-consumer.xml");
        
        //服务调用
        UserService service = (UserService) applicationContext.getBean("userService");
        service.login("xxxx", "123456");

        new CountDownLatch(1).await();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <dubbo:application name="dubbo-03-consumer"/>

    <!-- 远端服务获取配置 interface:服务接口  id:待调用的服务ID  url:访问路径-->
    <dubbo:reference interface="com.xjx.service.UserService" id="userService" url="dubbo://192.168.220.1:20880/com.xjx.service.UserService"/>
</beans>

运行结果:

provider 启动后会打印服务的调用地址:

consumer 启动后会成功调用服务,但报错,错误原因是 provider的 qos进程和 consumer 的 qos进程端口冲突,Qos=Quality of Service,qos 是 Dubbo的在线运维命令,可以对服务进行动态的配置,控制及查询,新版本 Dubbo 重构了 telnet,端口默认为 22222。

解决方案如下:

<!-- qos服务配置 -->
<dubbo:parameter key="qos.enable" value="true"/>  <!-- 是否开启在线运维命令 -->
<dubbo:parameter key="qos.accept.foreign.ip" value="false"/>  <!-- 不允许其它机器访问 -->
<dubbo:parameter key="qos.port" value="33333"/>  <!-- 修改port -->
# springboot 配置
dubbo.application.qos.enable=false
dubbo.application.qos.port=33333
dubbo.application.qos.accept.foreign.ip=false

Dubbo 程序细节补充:

  1. provider 在配置基于 Dubbo协议时,不要显式指定协议端口,用 -1 替代。
<dubbo:protocol name="dubbo" port="-1"/>
  1. 入门程序浅析:
  • 为什么 Provider 提供了 UserService 接口的实现,而在另一个 JVM 中的Consumer 可以调用?Consumer 中调用的到底是什么? 实际上 Consumer 没有直接调用远端的 UserServiceImpl,而是通过调用远端 UserServiceImpl 的代理对象 Proxy来实现。
  • 代理的核心工作是什么? 通过代理对 consumer 屏蔽网络通信的过程(通信方式、协议、序列化)来实现数据传递。

基于 SpringBoot 使用 Dubbo

思路分析

思路:深度封装,将公共配置放到 application.yaml 中,将个性配置应用到注解进行设置。

  1. Provider 的配置处理
  • dubbo 的公共配置,包括 dubbo应用名称、dubbo协议名和端口号;
  • 服务接口实现的 Bean对象、向外提供服务的 dubbo:service 对象;
  1. Consumer 的配置处理
  • dubbo 的公共配置,应用名、qos 应用;
  • 声明需要的服务;

项目开发

  1. 构建多模块 SpringBoot 应用;
  2. 引入依赖 jar 包:
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>3.2.0</version>
</dependency>
  1. 构建 Provider 模块

application.yaml 文件配置:

spring:
  application:
    name: DUBBO-04-BOOT-PROVIDER
dubbo:
  protocol:
    name: dubbo
    port: -1

核心启动类上标记 @EnableDubbo 注解;

对外服务类 Service

@DubboService
public class UserServiceImpl implements UserService {
    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login name " + name + " password " + password);
        return false;
    }
}
  1. 构建 consumer 模块

application.yaml 共有配置:

spring:
  application:
    name: DUBBO-05-BOOT-CONSUMER
dubbo:
  application:
    qos-enable: false

需要使用服务时注入配置:

@SpringBootTest
class Dubbo05BootConsumerApplicationTests {
    @DubboReference(url = "dubbo://192.168.220.1:20880/com.xjx.service.UserService")
    private UserService userService;

    @Test
    void contextLoads() {
    }

    @Test
    void test1() {
        boolean ret = userService.login("xxxx", "1234532");
        System.out.println("ret= " + ret);
    }
}

核心启动类上建议也加入 @EnableDubbo

SpringBoot 项目细节

1、@EnableDubbo 注解的作用?

@EnableDuubo 注解用于扫描 @DubboService 注解内容,并把对应的对象实例化,发布为 RPC 服务。

@EnableDubbo 默认扫描范围为启动类及其子包下的类。但是如果 @DubboService 注解修饰的类没有放到对应路径下,还希望能够扫描到,需要在核心启动类上添加注解 @DubboComponentScan(basePackages = {"xxxxxx"}) 显式指定能够扫描到的路径。

除了使用 @DubboComponentScan 注解外,还可以在 yaml 文件中配置扫描 dubbo.scan.base-packages 来扫描指定路径下的服务 Bean对象,发布为 RPC服务。

2、@DubboService 注解的作用?

@DubboService 注解修饰类型,SpringBoot 会创建这个类型的对象并发布为 Dubbo服务。

@DubboService 等同于 @Component(@Service) @Bean 注解的创建对象的作用,它会创建对象并将 Bean对象注册到容器中,通过在 AbstractApplicationContext 类的 refresh() 方法打断点调试查看 SingletonObjects 对象可以验证:

Dubbo 序列化

1、常见的 Dubbo 序列化方式

  • Hessian:Dubbo 协议中默认的序列化实现方案;
  • Java Serialization:JDK序列化方式;
  • Dubbo 序列化:阿里序列化方案,不成熟生成环境不建议使用;
  • Json 序列化:目前有两种实现方案,一种是阿里的 fastjson 库,一种是 dubbo 自己实现的简单 json;
  • Kryo 序列化:Java序列化方式,后续替换 Hessian,是一种非常成熟的序列化实现,已经被广泛使用;
  • FST:Java序列化方式,后续替换 Hessian,但是缺乏足够成熟的使用场景;
  • ProtoBuf、Thrift IDL 等跨语言二进制序列化方案,允许在多种语言之间交换数据。

docker+zookeeper+dubbo 测试

docker 安装zookeeper 并启动的坑:https://blog.csdn.net/weixin_44808225/article/details/123329529

zookeeper 相关使用:https://juejin.cn/post/7103406988079398942

docker 安装 zookeeper:3.7.1:

# 拉取 zookeeper:3.7.1 镜像
docker pull zookeeper:3.7.1

# 启动 zookeeper 并挂载向数据/日志/配置目录
docker run -d --name zookeeper -p 2181:2181 -v /opt/docker/zookeeper/data:/data -v /opt/docker/zookeeper/conf/zoo.cfg:/conf/zoo.cfg -v /opt/docker/zookeeper/logs:/datalog --restart always  zookeeper:3.7.1