Dubbo微服务基础,实战教程 (一文通透!!!)
Apache Dubbo是一款RPC服务开发框架,那何为RPC呢?全称为Remote Procedure Call,翻译过来就是远程过程调用。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。Dubbo 被设计为高度可扩展,用户可以方便的实现流量拦截、选址的各种定制逻辑。
目录
一、什么是Dubbo
阿里巴巴开发的云原生微服务架构框架,类似于springcloud,两者之间各有优势。那什么又是云原生?很早之前就已经提出了云原生的思想,在计算机领域中,思想重要,技术的变革,一定是思想先行。云原生是一种构建和运行应用程序的方法,是一套技术体系和方法论。
云原生(CloudNative)是一个组合词,Cloud+Native。Cloud表示应用程序位于云中,而不是传统的数据中心;Native表示应用程序从设计之初即考虑到云的环境,原生为云而设计,在云上以最佳姿势运行,充分利用和发挥云平台的弹性+分布式优势。
Dubbo开发相较于Springcloud具有一些优势:
-
开箱即用
-
易用性高,如 Java 版本的面向接口代理特性能实现本地透明调用
-
功能丰富,基于原生库或轻量扩展即可实现绝大多数的微服务治理能力
-
-
面向超大规模微服务集群设计
-
极致性能,高性能的 RPC 通信协议设计与实现
-
横向可扩展,轻松支持百万规模集群实例的地址发现与流量治理
-
-
高度可扩展
-
调用过程中对流量及协议的拦截扩展,如 Filter、Router、LB 等
-
微服务治理组件扩展,如 Registry、Config Center、Metadata Center 等
-
-
企业级微服务治理能力
-
国内共有云厂商支持的事实标准服务框架
-
1.1 Dubbo的一些概念
1.1.2 RPC通信
在Dubbo3中,RPC通信主要是使用Triple协议,Triple协议构建于HTTP/2协议上,兼容gRPC(gRPC协议是Google开发的基于HTPP/2和protobuf的RPC协议框架),提供提供 Request Response、Request Streaming、Response Streaming、Bi-directional Streaming 等通信模型;从 Triple 协议开始,Dubbo 还支持基于 IDL 的服务定义。
此外,Dubbo 还集成了业界主流的大部分协议,使得用户可以在 Dubbo 框架范围内使用这些通信协议,为用户提供了统一的编程模型与服务治理模型,这些协议包括 rest、hessian2、jsonrpc、thrift 等,注意不同语言 SDK 实现支持的范围会有一些差异。
1.1.3 服务发现
服务发现,是消费端自动发现服务地址列表的能力,是微服务框架需要具备的关键能力,借助于自动化的服务发现,微服务之间在无需感知对端部署位置与IP地址的情况下实现通信。
实现的方式有多种,一种是Dubbo提供的Client-Based服务发现机制,同时也需要第三方注册中心来协调服务发现过程,比如Nacos/Zookeeper等。
基本原理图:

1.1.4 流量治理
Dubbo2开始,Dubbo就提供了丰富的服务治理规则,包括路由规则/动态配置等。一方面 Dubbo3 正在通过对接 xDS 对接到时下流行的 Mesh 产品如 Istio 中所使用的以 VirtualService、DestinationRule 为代表的治理规则,另一方面 Dubbo 正寻求设计一套自有规则以实现在不通部署场景下的流量治理,以及灵活的治理能力。
1.1.5 Dubbo Mesh
Mesh网络可以理解为WiFi路由器覆盖不到一些死角,可以在死角覆盖得到的地方,放一台中继WIFI,形成MESH网络,就可以覆盖到死角。
Dubbo Mesh 的目标是提供适应 Dubbo 体系的完整 Mesh 解决方案,包含定制化控制面(Control Plane)、定制化数据面解决方案。Dubbo 控制面基于业界主流 Istio 扩展,支持更丰富的流量治理规则、Dubbo应用级服务发现模型等,Dubbo 数据面可以采用 Envoy Sidecar,即实现 Dubbo SDK + Envoy 的部署方案,也可以采用 Dubbo Proxyless 模式,直接实现 Dubbo 与控制面的通信。

1.2 Dubbo 架构图

dubbo-rpc
整体上来看,Dubbo首先是一款RPC框架,用户在使用 Dubbo 时首先需要定义好 Dubbo 服务;其次,是在将 Dubbo 服务部署上线之后,依赖 Dubbo 的应用层通信协议实现数据交换,Dubbo 所传输的数据都要经过序列化,而这里的序列化协议是完全可扩展的。使用 Dubbo 的第一步就是定义 Dubbo 服务,服务在 Dubbo 中的定义就是完成业务功能的一组方法的集合,可以选择使用与某种语言绑定的方式定义,如在 Java 中 Dubbo 服务就是有一组方法的 Interface 接口,也可以使用语言中立的 Protobuf Buffers IDL 定义服务。
定义好服务之后,服务端(Provider)需要提供服务的具体实现,并将其声明为 Dubbo 服务,而站在服务消费方(Consumer)的视角,通过调用 Dubbo 框架提供的 API 可以获得一个服务代理(stub)对象,然后就可以像使用本地服务一样对服务方法发起调用了。
在消费端对服务方法发起调用后,Dubbo 框架负责将请求发送到部署在远端机器上的服务提供方,提供方收到请求后会调用服务的实现类,之后将处理结果返回给消费端,这样就完成了一次完整的服务调用。如图中的 Request、Response 数据流程所示。
需要注意一点,在Dubbo中,服务指的是RPC粒度的,和读者印象中的泛指的功能型服务不是同一概念。
在分布式系统中,服务越来越多,应用之间的部署越来越繁杂,用户作为RPC的消费方,如何动态知道服务提供方地址,因此Dubbo引入了注册中心来协调提供方和消费方的地址。提供者启动之后向注册中心注册自身地址,消费方通过拉取或订阅注册中心特定节点,动态的感知提供方地址列表的变化。
注册中心可以使用:
-
如 Nacos、Zookeeper、Consul、Etcd 等。
-
将服务的组织与注册交给底层容器平台,如 Kubernetes,这可以理解为更加云原生的使用方式。

Dubbo的部署架构图:
Dubbo微服务组件包含三个中心组件:
-
注册中心,协调Consumer与Provider之间的地址与发现
-
配置中心
-
存储Dubbo启动阶段的全局配置,保证配置的跨环境共享与全局一致性
-
负责服务治理规则(路由规则/动态配置等)的存储与推送
-
-
元数据中心
-
接收Provider上报的服务接口元数据,为Admin等控制台提供运维能力(如服务测试/接口文档等)
-
也作为注册中心的额外扩展,作为服务发现机制的补充,提供额外的接口/方法级别配置信息的同步能力
-

部署架构图
1.2.1 注册中心
不依赖于配置中心和元数据中心,主要承载的作用是服务注册和服务发现的职责,Dubbo支持接口级别和应用级别的两种粒度的服务发现和服务注册。
如果Dubbo仅仅是使用RPC直连服务,可以不部署注册中心。在接口或者应用级别,可以选择部署对应的注册中心。在Dubbo + Mesh 的场景下,随着 Dubbo 服务注册能力的弱化,Dubbo内的注册中心也不再是必选项,其职责开始被控制面取代,如果采用了Dubbo + Mesh的部署方式,无论是ThinSDK的mesh方式还是Proxyless的mesh方式,都不再需要独立部署注册中心。

1.2.2 metadata(元数据中心)
-
对于一个原先采用老版本Dubbo搭建的应用服务,在迁移到Dubbo 3时,Dubbo 3 会需要一个元数据中心来维护RPC服务与应用的映射关系(即接口与应用的映射关系),因为如果采用了应用级别的服务发现和服务注册,在注册中心中将采用“应用 —— 实例列表”结构的数据组织形式,不再是以往的“接口 —— 实例列表”结构的数据组织形式,而以往用接口级别的服务注册和服务发现的应用服务在迁移到应用级别时,得不到接口与应用之间的对应关系,从而无法从注册中心得到实例列表信息,所以Dubbo为了兼容这种场景,在Provider端启动时,会往元数据中心存储接口与应用的映射关系。
-
为了让注册中心更加聚焦与地址的发现和推送能力,减轻注册中心的负担,元数据中心承载了所有的服务元数据、大量接口/方法级别配置信息等,无论是接口粒度还是应用粒度的服务发现和注册,元数据中心都起到了重要的作用。
如果有以上两种需求,都可以选择部署元数据中心,并通过Dubbo的配置来集成该元数据中心。
元数据中心并不依赖于注册中心和配置中心,用户可以自由选择是否集成和部署元数据中心,如下图所示:

1.2.3 配置中心
整个部署架构中,整个集群内实例(无论是Provider还是Consumer)都会共享该配置中心集群中的配置。

三大中心不一定是Dubbo应用服务必须使用的,但是如果集成了三大部署中心,还是会遇到可用性问题,比如多个注册中心,多个配置中心,多个元数据中心,系统该如何运行。
Dubbo对三大中心都支持了Multiple模式:
-
多注册中心:Dubbo 支持多注册中心,即一个接口或者一个应用可以被注册到多个注册中心中,比如可以注册到ZK集群和Nacos集群中,Consumer也能够从多个注册中心中进行订阅相关服务的地址信息,从而进行服务发现。通过支持多注册中心的方式来保证其中一个注册中心集群出现不可用时能够切换到另一个注册中心集群,保证能够正常提供服务以及发起服务调用。这也能够满足注册中心在部署上适应各类高可用的部署架构模式。
-
多配置中心:Dubbo支持多配置中心,来保证其中一个配置中心集群出现不可用时能够切换到另一个配置中心集群,保证能够正常从配置中心获取全局的配置、路由规则等信息。这也能够满足配置中心在部署上适应各类高可用的部署架构模式。
-
多元数据中心:Dubbo 支持多元数据中心:用于应对容灾等情况导致某个元数据中心集群不可用,此时可以切换到另一个元数据中心集群,保证元数据中心能够正常提供有关服务元数据的管理能力。
如遇到多个注册中心的话,部署架构如下:

机房A,机房B,两个Registry,Provider A会通过Dubbo SDK把地址注册到Registry A,Consumer A会和注册中心A进行一个互相订阅地址。机房B原理和机房A原理一样。机房AB中的注册中心会进行数据同步,这样机房A的consumer A也可以看作和机房B的Registry进行订阅地址同步。
1.3 Dubbo可扩展性
为什么要在系统中强调可扩展性呢?一个很大的原因是,比如一个系统设计好之后,后期又需要加入一些功能代码或者插件,如何最小化改变原有代码加入功能代码或者插件,这个就是扩展性,Dubbo在架构时期也一直重视可扩展性。
在市面上,有成熟的可扩展技术,比如SPring的IOC容器,或者是Factory,Dubbo在设计之初,不想强依赖于Spring的IOC,又不想大费周章开发出来一个IOC容器,因此使用了最简单的Factory。
Dubbo扩展加载流程如图:

主要步骤为 4 个:
-
读取并解析配置文件
-
缓存所有扩展实现
-
基于用户执行的扩展名,实例化对应的扩展实现
-
进行扩展实例属性的 IOC 注入以及实例化扩展的包装类,实现 AOP 特性
二、Dubbo实战
接下来通过三种方式入门Dubbo。首先会通过代码直接展示dubbo的直连和注册中心实现方式,接着使用Spring和SpringBoot的方式分别展示如何使用Dubbo。
在写dubbo相关代码前,我们首先要定义一个公共的客户端服务,这个服务里存放的是service接口。服务提供者引入这个工程,写实现类,提供dubbo接口;服务消费者引入这个工程,通过这个工程的service接口调用。
因此新建这样一个模块,命名为dubbo-client,整体代码结构如下,只需要写一个service接口即可:

项目结构
User类:
@Data
public class User implements Serializable {
private static final long serialVersionUID = -9206514891359830486L;
private Long id;
private String name;
private String sex;
}
UserService:
public interface UserService {
User getUser(Long id);
}
2.1 直接代码
接下来通过直接代码的方式生成一个dubbo服务,并且用另外一个类去调用这个dubbo服务:
2.1.1 引入依赖
核心依赖就两个,一个dubbo的依赖,另外一个上面的公共接口方法
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.4.1</version>
</dependency>
<dependency>
<artifactId>dubbo-client</artifactId>
<groupId>com.javayz</groupId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
2.1.2 编写服务提供者
服务提供者主要配置以下几个属性:
1、application:设置应用的名称等信息
2、protocol :设置服务的协议
3、register:设置服务的连接方式
4、service:将需要暴露的服务注册出来
public class DubboProvider {
public static void main(String[] args) throws IOException {
//暴露UserService服务
//1、application
ApplicationConfig applicationConfig=new ApplicationConfig("sample-provider");
//2、protocol -dubbo协议
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20880);
//3、register
//直连的方式,不暴露到注册中心
RegistryConfig registryConfig=new RegistryConfig(RegistryConfig.NO_AVAILABLE);
//4、service
ServiceConfig serviceConfig=new ServiceConfig();
serviceConfig.setInterface(UserService.class);
serviceConfig.setRef(new UserServiceImpl());
//5、将application、protocol、register注册到service
serviceConfig.setRegistry(registryConfig);
serviceConfig.setProtocol(protocolConfig);
serviceConfig.setApplication(applicationConfig);
serviceConfig.export();
System.out.println("服务已经暴露");
System.in.read();
}
}
2.1.3 编写服务消费者
消费者的实现主要就三步:
1、配置application:设置应用的名称等信息
2、配置reference:主要配置要引用的信息
3、获取到接口,调用服务。
public class DubboConsumer {
public static void main(String[] args) {
//1、application
ApplicationConfig applicationConfig=new ApplicationConfig("sample-consumer");
//2、配置reference
ReferenceConfig referenceConfig=new ReferenceConfig();
referenceConfig.setApplication(applicationConfig);
referenceConfig.setInterface(UserService.class);
referenceConfig.setUrl("dubbo://172.18.2.49:20880/com.javayz.client.service.UserService?anyhost=true&application=sample&bind.ip=172.18.2.49&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.javayz.client.service.UserService&methods=getUser&pid=5936&release=2.7.4.1&side=provider×tamp=1618036935244");
UserService userService = (UserService) referenceConfig.get();
User user = userService.getUser(1L);
System.out.println(user);
}
}
先启动提供者,再启动消费者,如果user信息打印出来了就说明调用成功。
这里的Register使用的是直连的方式,我们也可以使用注册中心,这里以zookeeper为例。首先在项目中引入zookeeper相关依赖:
<!-- zk客户端依赖:curator -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.13.0</version>
</dependency>
服务提供者修改一处地方,将RegistryConfig修改为zookeeper的连接方式
//register
//直连的方式,不暴露到注册中心
//RegistryConfig registryConfig=new RegistryConfig(RegistryConfig.NO_AVAILABLE);
//通过注册中心暴露dubbo
RegistryConfig registryConfig=new RegistryConfig("zookeeper://192.168.78.128:2181");
消费者同样修改一处位置,将referenceConfig中的setUrl方法替换为zookeeper:
RegistryConfig registryConfig=new RegistryConfig("zookeeper://192.168.78.128:2181");
ReferenceConfig referenceConfig=new ReferenceConfig();
referenceConfig.setRegistry(registryConfig);
referenceConfig.setApplication(applicationConfig);
referenceConfig.setInterface(UserService.class);
//referenceConfig.setUrl("dubbo://172.18.2.49:20880/com.javayz.client.service.UserService?anyhost=true&application=sample&bind.ip=172.18.2.49&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.javayz.client.service.UserService&methods=getUser&pid=5936&release=2.7.4.1&side=provider×tamp=1618036935244");
2.2 通过Spring
通过Spring的方式只不过是把上面写在Java中的代码拿到配置文件中去,并把接口注入到Bean容器中,在resource文件夹下新建两个配置文件:provider.xml
<?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://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="sample-provider" />
<!-- 使用zookeeper广播注册中心暴露服务地址 -->
<dubbo:registry address="zookeeper://192.168.78.128:2181" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.javayz.client.service.UserService" ref="userService" />
<!-- 和本地bean一样实现服务 -->
<bean id="userService" class="com.javayz.example1.service.impl.UserServiceImpl" />
</beans>
consumer.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="sample-consumer" />
<dubbo:registry address="zookeeper://192.168.78.128:2181" />
<dubbo:reference id="userService" interface="com.javayz.client.service.UserService" />
</beans>
这里的配置文件和上方的代码均一一对应。接着是服务的提供者和消费者:SpringDubboProvider
public class SpringDubboProvider {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("provider.xml");
System.out.println("服务已经暴露");
System.in.read();
}
}
SpringDubboConsumer
public class SpringDubboConsumer {
public static void main(String[] args) {
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("consumer.xml");
UserService bean = context.getBean(UserService.class);
System.out.println(bean.getUser(1L));
}
}
2.3 通过SpringBoot的方式
新建两个SpringBoot项目,一个是服务提供者,一个是服务消费者,引入dubbo的核心依赖
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.4.1</version>
</dependency>
这里的配置都写在application.properties中,首先是服务提供者:
dubbo.application.name=dubbo-provider
dubbo.registry.address=zookeeper://192.168.78.128:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
服务提供者需要写服务的实现类,这里需要注意@Service注解采用的是dubbo包下:
import com.javayz.client.entity.User;
import com.javayz.client.service.UserService;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;
@Service
@Component
public class UserServiceImpl implements UserService {
@Override
public User getUser(Long id) {
User user=new User();
user.setId(id);
user.setName("javayz");
user.setSex("man");
return user;
}
}
接着在启动类上添加一个@EnableDubbo注解即可。
服务的消费者同样是先写一下配置文件:
server.port=8081
dubbo.application.name=dubbo-consumer
dubbo.registry.address=zookeeper://192.168.78.128:2181
接着通过@Reference注解将service对象引进来
@SpringBootApplication
public class SpringbootconsumerApplication {
@Reference
UserService userService;
public static void main(String[] args) {
SpringApplication.run(SpringbootconsumerApplication.class, args);
}
@Bean
public ApplicationRunner getBean(){
return args -> {
System.out.println(userService.getUser(1L));
};
}
}
三、Dubbo的常用配置
<dubbo:application/> 用于配置当前应用信息
<dubbo:register/> 用于配置连接注册相关信息
<dubbo:protocol/> 用于配置提供服务的协议信息,提供者指定协议,消费者被动接受
<dubbo:service/> 用于暴露一个服务,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心
<dubbo:provider/> 当ProtocolConfig和ServiceConfig某属性没有配置时,采用此缺省值
<dubbo:consumer/> 当ReferenceConfig某属性没有配置时,采用此缺省值
<dubbo:reference/> 用于创建一个远程服务代理
更加具体的配置信息我在官网中找到了,大家可参考:
https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/
四、企业中如何通过ubbo实现分布式调用
在企业中,如果消费者直接通过RPC去调用提供者,理论上需要把提供者的整个Jar包引入到项目中。但是这样的话服务提供这种的其他无关代码也会被引入其中,导致代码污染。
因此实际开发过程中,服务提供者和调用者之间会增加一层Client模块。这个Client中主要写的是Service的接口定义,接口的返回实例对象以及接口的请求实例对象。简单来讲,所有的定义都在Client中完成。
使用时,服务提供者引入这个Client,然后写实现方法,服务消费者引入这个Client,然后通过dubbo直接调用即可。
另外企业开发中,可能会出现多个接口实现,这种情况下可以给Service设定group、version等进行区分。
五、总结
Dubbo的基本使用就这些,Dubbo毕竟只是一个RPC的工具,我们可以用它很方便地暴露、消费服务。但是两个小时也只是会上手使用,它内部的一些配置,一些理念以及最重要的原理都是需要我们自己去深耕的。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)