Dubbo 学习记录

本文最后更新于 2022年3月1日 下午

Dubbo

为什么要使用分布式架构

单一应用架构
一种把系统中所有的功能、模块耦合在一个应用中的架构方式,一般只操作一个数据库。代表技术:Struts2,SpringMVC,Spring,MyBatis等。

  • 特点:打包成一个独立的单元(导成一个唯一的jar包或者是war包)。会以一个进程的方式来运行。
  • 优点:项目易于管理、部署简单。
  • 缺点:测试成本高、可伸缩性差、可靠性差、迭代困难、跨语言程度差、团队协作难

RPC架构
远程过程调用。一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。代表技术:Thrift ( Facebook开发的系统内部各语言之间协调通讯的RPC框架,带有强大的代码生成引擎,支持跨语言、多平台调用的。Apache官网有资料)、Hessian (基于HTTP协议的RPC框架,提供RMI功能,且采用二进制协议的轻量级框架)等等。

  • 特点:应用直接调用服务,服务之间是隔离的。
  • 缺点:服务过多时,管理成本高昂。服务治理,服务注册、发现,服务容错,服务跟踪,服务网关,IP暴露等都是此架构无法避免的问题。

微服务架构

一个大型的复杂软件应用,由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松相合的。每个微服务仅关注于完成一件任务并很好的完成该任务。微服务就是一个轻量级的服务治理方案。对比SOA架构,使用注册中心代替ESB服务总线。注册中心相比服务总线来说,更加轻量级。代表技术:SpringCloud,Dubbo。

  • 特点:系统是由多个服务构成,每个服务可以单独独立部署,每个服务之间是松帮合的。服务内部是高内聚的,外部是低耦合的。高内聚就是每个服务只关注完成一个功能。低耦合就是服务之间没有直接关联。
  • 优点:
    1. 测试容易:服务高内聚低耦合,每个服务可以独立测试。
    2. 可伸缩性强:服务相对独立,可随时增删服务实现系统服务变化,可针对某服务独立水平扩展。
    3. 可靠性强:服务出现问题,受影响的位置是当前服务,不会影响其他服务。应用健壮性有更好的保证。
    4. 跨语言程度会更加灵活:可针对服务特性使用不同的语言开发,尽可能发挥出每种语言的特性。
    5. 团队协作容易:团队专注自主研发的服务,对其他服务的了解可局限在服务的调用上。
    6. 系统迭代容易:当服务发生变更时,只需针对单一服务进行系统升级迭代。
  • 缺点:
    1. 运维成本过高,部署数量较多:服务过多导致运维成本成倍提升。
    2. 接口兼容多版本:因服务可独立升级迭代,所以会导致接口版本过多。
    3. 分布式系统的复杂性:系统分部,导致通讯成本提升,系统复杂度提升。
    4. 分布式事务:分布式系统会引出分布式事务的出现,现在有很多的分布式事务解决方案,分布式事务不是不可解决的,这不会影响微服务架构的应用。

RPC协议(Remote Procedure Call Protocol)

远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。RPC采用C/S模式。请求程序就是一个Client,而服务提供程序就是一个Server。首先,Client调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,Client调用进程接收答复信息,获得进程结果,然后调用执行继续进行。

RPC流程

其中,Client发送到Server需要经过序列化,接受Server的信息要反序列化,同样,Server接收Client的请求时需要反序列化,发送给Client需要序列化。

基于RMI的RPC实现(不推荐)

项目有rmi-api,rmi-server,rmi-client模块

rmi-api定义接口以及实体类:

rmi-server实现api接口,基于springboot框架:

rmi-client实现Controller,基于springboot:

rmi-server实现类:

1
2
3
4
5
6
7
8
9
10
11
@Service
public class UserServiceImpl implements UserService {
@Override
public User queryUserById(Integer id) throws RemoteException {
System.out.println("模拟查询,收到参数:id=" + id);
User user = new User();
user.setId(id);
user.setName("Azusa");
return user;
}
}

rmi-client Controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
UserService userService;

@GetMapping("/{id}")
public User getUser(@PathVariable Integer id){
try {
return userService.queryUserById(id);
} catch (RemoteException e) {
e.printStackTrace();
}
return null;
}
}

由于RPC需要跨进程即调用不同JVM的方法,故需要配置RMI:

rmi-server,为Client暴露服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class RmiServerConfig {

@Resource
private UserService userService;

@Bean
public RmiServiceExporter rmiServiceExporter(){ //暴露接口
RmiServiceExporter exporter = new RmiServiceExporter();
exporter.setRegistryPort(2002);
exporter.setServiceName("userService");
exporter.setService(userService);
exporter.setServiceInterface(UserService.class);
return exporter;
}
}

rmi-client订阅服务:

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class RmiClientConfig {

@Bean("userService")
public RmiProxyFactoryBean getUserService(){ //订阅服务
RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
rmiProxyFactoryBean.setServiceUrl("rmi://127.0.0.1:2002/userService");
rmiProxyFactoryBean.setServiceInterface(UserService.class);
return rmiProxyFactoryBean;
}
}

先启动rmi-server,再启动rmi-client,程序无异常,接口正常调用

Dubbo框架使用

Apache Dubbo提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。

主要流程如下:

调用关系说明:

  1. 服务容器负责启动,加载,运行服务提供者。
  2. 服务提供者在启动时,向注册中心注册自己提供的服务。
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台供消费者进行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时发送一次统计数据到监控中心。

相关配置和代码编写

与RMI项目一样,Dubbo项目也有三个模块,dubbo-api,dubbo-provider,dubbo-consumer,其中provider,consumer使用springboot框架。

除了springboot起步依赖,还需要映入dubbo:

pom.xml

1
2
3
4
5
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.15</version>
</dependency>

接口,实体,实现类,Controller均与上面类似,基于注解开发:

provider实现类:

1
2
3
4
5
6
7
8
9
10
11
@DubboService //注意是Dubbo的注解
public class UserServiceImpl implements UserService {
@Override
public User queryUserById(Integer id) {
System.out.println("Dubbo Provider... Received Param Id = " + id);
User user = new User();
user.setId(id);
user.setName("Azusa");
return user;
}
}

consumer Controller需要引入Service:

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class UserController {

@DubboReference(parameters = {"unicast", "false"}) //引用服务
private UserService userService;

@GetMapping("/user/{id}")
public User getUser(@PathVariable Integer id){
return userService.queryUserById(id);
}
}

Dubbo使用springboot的application.yml配置

provider的application.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 9001


dubbo:
application:
name: dubbo-provider
registry:
address: multicast://224.5.6.7:1234
protocol:
name: dubbo
port: 20880
scan:
base-packages: com.example.provider.service

consumer的application.yml:

1
2
3
4
5
6
7
8
server:
port: 9002

dubbo:
application:
name: dubbo-consumer
registry:
address: multicast://224.5.6.7:1234

consumer和provider均使用multicast注册中心,特点:不需要启动任何中间节点,只要广播地址一样,就可以互相发现。

注意provider和consumer均使用多播地址,其中consumer的注解

1
@DubboReference(parameters = {"unicast", "false"}) //引用服务

关闭了provider向consumer的单播。

调用http://localhost:9002/user/{id}接口,正常返回json。

使用Zookeeper注册中心

Zookeepr特点,树形结构,支持变更推送,结构图如下:

流程如下:

  • 服务提供者启动时:向/dubbo/com.foo.BarService/providers目录下写入自己的URL地址。
  • 服务消费者启动时:订阅/dubbo/com.foo.BarService/providers目录下的提供者URL地址。并向/dubbo/com.foo.BarService/consumers目录下写入自己的 URL 地址。
  • 监控中心启动时:订阅/dubbo/com.foo.Barservice目录下的所有提供者和消费者URL地址。

使用Docker配置Zookeeper集群以及监控中心

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
version: '3.1'
services:
zoo1:
image: zookeeper
restart: always
hostname: zoo1
ports:
- 2181:2181
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181

zoo2:
image: zookeeper
restart: always
hostname: zoo2
ports:
- 2182:2181
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181

zoo3:
image: zookeeper
restart: always
hostname: zoo3
ports:
- 2183:2181
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181

dubbo-admin:
image: apache/dubbo-admin
container_name: dubbo-admin
# 等待zoo3启动后再启动
depends_on:
- zoo3
ports:
- 8080:8080
environment:
- admin.registry.address=zookeeper://zoo3:2181
- admin.config-center=zookeeper://zoo3:2181
- admin.metadata-report.address=zookeeper://zoo3:2181

直接sudo docker-compose up -d 启动服务

使用sudo docker-compose stop关闭容器

使用sudo docker exec -it zookeeper_zoo3_1 /bin/bash进入第三个zookeepr环境命令行中。

使用./bin/zkCli.sh与zookeeper连接:

可以看到正常连接。

退出,使用./bin/zkServer.sh status查看zookeeper状态:

在之前的Dubbo中,只需要修改provider和consumer的application.yml的:

1
2
3
dubbo:
registry:
address: zookeeper://192.168.2.125:2183?backup=192.168.2.125:2181,192.168.2.125:2182

单机则不需要backup:

1
2
3
dubbo:
registry:
address: zookeeper://192.168.2.125:2183

访问接口:

Dubbo-Admin

docker-compose中已经配置好了,并设置了环境变量。

访问对应端口:


Dubbo 学习记录
https://nanami.run/2022/03/01/dubbo/
作者
Nanami
发布于
2022年3月1日
许可协议