Dubbo架构与实战
1、Dubbo 架构概述
1.1 什么是 Dubbo
Apache Dubbo是一款高性能的Java RPC框架。其前身是阿里巴巴公司开源的一个高性能、轻量级的开源Java RPC框架,可以和Spring框架无缝集成。
1.2 Dubbo 解决的问题
在大规模服务化之前,应用可能只是通过 RMI 或 Hessian 等工具,简单的暴露和引用远程服务,通过配置服务的URL地址进行调用,通过 F5 等硬件进行负载均衡。
当服务越来越多时,服务 URL 配置管理变得非常困难,F5 硬件负载均衡器的单点压力也越来越大。 此时需要一个服务注册中心,动态地注册和发现服务,使服务的位置透明。并通过在消费方获取服务提供方地址列表,实现软负载均衡和 Failover,降低对 F5 硬件负载均衡器的依赖,也能减少部分成本。
当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。 这时,需要自动画出应用间的依赖关系图,以帮助架构师理清关系。
接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器? 为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标。其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阈值,记录此时的访问量,再以此访问量乘以机器数反推总容量。
1.3 Dubbo 架构
下图为 Dubbo 的架构图
节点角色说明:
节点 | 角色说明 |
---|---|
Provider |
暴露服务的服务提供方 |
Consumer |
调用远程服务的服务消费方 |
Registry |
服务注册与发现的注册中心 |
Monitor |
统计服务的调用次数和调用时间的监控中心 |
Container |
服务运行容器 |
调用关系说明:
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
图中箭头解释:
init:表示初始化,服务启动的时候会执行的操作
async:代表是异步操作
sync:表示是同步操作
1.4 Dubbo 的特性
连通性:
- 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
- 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
- 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
- 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
- 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
- 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
- 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
- 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
健壮性:
- 监控中心宕掉不影响使用,只是丢失部分采样数据
- 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
- 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
- 服务提供者无状态,任意一台宕掉后,不影响使用
- 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
伸缩性:
- 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
- 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者
升级性:
当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。下图是未来可能的一种架构:
节点角色说明:
节点 | 角色说明 |
---|---|
Deployer |
自动部署服务的本地代理 |
Repository |
仓库用于存储服务应用发布包 |
Scheduler |
调度中心基于访问压力自动增减服务提供者 |
Admin |
统一管理控制台 |
Registry |
服务注册与发现的注册中心 |
Monitor |
统计服务的调用次数和调用时间的监控中心 |
2、Dubbo 开发实战
2.1 实战案例介绍
在Dubbo中所有的服务调用都是基于接口去进行双方交互的。双方协定好Dubbo调用中的接口,提供者来提供实现类并且注册到注册中心上。
调用方则只需要引入该接口,并且同样注册到相同的注册中心上(消费者)。即可利用注册中心来实现集群感知功能,之后消费者即可对提供者进行调用。
我们所有的项目都是基于Maven去进行创建,这样相互在引用的时候只需要以依赖的形式进行展现就可以了。并且这里我们会通过maven的父工程来统一依赖的版本。程序实现分为以下几步骤:
- 建立maven工程 并且 创建API模块: 用于规范双方接口协定。
- 提供provider模块,引入API模块,并且对其中的服务进行实现。将其注册到注册中心上,对外来统一提供服务。
- 提供consumer模块,引入API模块,并且引入与提供者相同的注册中心。再进行服务调用。
2.2 开发过程
接口协定
1.定义 maven
<groupId>com.lagou</groupId>
<artifactId>service-api</artifactId>
<version>1.0-SNAPSHOT</version>
2.定义接口,这里为了方便,只是写一个基本的方法。
public interface HelloService {
String sayHello(String name);
}
创建接口提供者
1.引入 API 模块
<dependency>
<groupId>com.lagou</groupId>
<artifactId>service-api</artifactId>
<version>${project.version}</version>
</dependency>
2.引入Dubbo相关依赖,这里为了方便,使用注解方式。
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-netty4</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-hessian2</artifactId>
</dependency>
3.编写实现类,注意这里也使用了Dubbo中的@Service 注解来声明他是一个服务的提供者。
@Service
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello: " + name;
}
}
4.编写配置文件,用于配置dubbo。比如这里我就叫dubbo-provider.properties ,放入到 resources 目录下。
dubbo.application.name=dubbo-demo-annotation-provider // 当前提供者的名称
dubbo.protocol.name=dubbo // 对外提供的时候使用的协议
dubbo.protocol.port=20880 // 该服务对外暴露的端口是什么
5.编写启动的main 函数。这里面做的比较简单,主要要注意注解方式中的注册中心这里是使用的本机2181端口来作为注册中心。
public class DubboPureMain {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
context.start();
System.in.read();
}
@Configuration
@EnableDubbo(scanBasePackages = "com.lagou.service.impl")
@PropertySource("classpath:/dubbo-provider.properties")
static class ProviderConfiguration {
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
return registryConfig;
}
}
}
引入消费者模块
1.引入 API 模块
<dependency>
<groupId>com.lagou</groupId>
<artifactId>service-api</artifactId>
<version>${project.version}</version>
</dependency>
2.引入Dubbo依赖 ,同服务提供者。
3.编写服务,用于真实的引用dubbo接口并使用。因为这里是示例,所以比较简单一些。这里面@Reference 中所指向的就是真实的第三方服务接口。
@Component
public class ConsumerComponent {
@Reference
private HelloService helloService;
public String sayHello(String name) {
return helloService.sayHello(name);
}
}
4.编写消费者的配置文件。这里比较简单,主要就是指定了当前消费者的名称和注册中心的位置。通过这个注册中心地址,消费者就会注册到这里并且也可以根据这个注册中心找到真正的提供者列表。
dubbo.application.name=service-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
5.编写启动类,这其中就会当用户在控制台输入了一次换行后,则会发起一次请求。
public class AnnotationConsumerMain {
public static void main(String[] args) throws IOException, InterruptedException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
context.start();
ConsumerComponent service = context.getBean(ConsumerComponent.class);
while (true) {
System.in.read();
try {
String hello = service.sayHello("world");
System.out.println("result :" + hello);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Configuration
@EnableDubbo(scanBasePackages = "com.lagou.service")
@PropertySource("classpath:/dubbo-consumer.properties")
@ComponentScan(value = {"com.lagou.bean.consumer"})
static class ConsumerConfiguration {
}
}
2.3 配置方式介绍
下面我们来使用不同的方式来对Dubbo进行配置。每种配置方式各有不同,一般可以分为以下几个:
- 注解: 基于注解可以快速的将程序配置,无需多余的配置信息,包含提供者和消费者。但是这种方式有一个弊端,有些时候配置信息并不是特别好找,无法快速定位。
- XML: 一般这种方式我们会和 Spring 做结合,相关 的Service和Reference均使用Spring集成后的。通过这样的方式可以很方便的通过几个文件进行管理整个集群配置。可以快速定位也可以快速更改。
- 基于代码方式: 基于代码方式的对上述配置进行配置。这个使用的比较少,这种方式更适用于自己公司对其框架与Dubbo做深度集成时才会使用。
2.4 XML方式
我们一般XML会结合Spring应用去进行使用,将Service的注册和引用方式都交给Spring去管理。下面我们还是针对于上面的demo进行实现。
这里我们针对于api模块不做处理,还是使用原先的接口。从提供者和消费者做讲解。这了我们直接通过spring的方式去做讲解。
provider模块
1.引入api依赖和实现类编写都和上面注解配置一样
2.编写dubbo-provider.xml 文件,用于对dubbo进行文件统一配置。并且对刚才的配置进行引入。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
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="dubbo-demo-annotation-provider"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:protocol name="dubbo"/>
<bean id="helloService" class="com.lagou.service.impl.HelloServiceImpl"/>
<dubbo:service interface="com.lagou.service.HelloService" ref="helloService"/>
</beans>
3.编写启动类
public class ProviderApplication {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("spring-*.xml");
context.start();
System.in.read();
}
}
consumer 模块
1.引入api依赖和实现类编写都和上面注解配置一样
2.编写dubbo-consumer.xml 文件,用于对dubbo进行文件统一配置。并且对刚才的配置进行引入。
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
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="demo-consumer"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:reference id="helloService" interface="com.lagou.service.HelloService">
</dubbo:reference>
</beans>
3.引入启动模块。因为引用了Spring框架,所以再上一步的helloService会被当做一个bean注入到真实的环境中。在我们生产级别使用的时候,我们可以通过Spring中的包扫描机制,通过@Autowired 这种机制来进行依赖注入。
public class XMLConsumerMain {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-consumer.xml");
context.start();
HelloService demoService = context.getBean(HelloService.class);
System.out.println(demoService.sayHello("world"));
}
}
3、Dubbo 管理控制台 dubbo-admin
3.1 作用
主要包含:服务管理 、 路由规则、动态配置、服务降级、访问控制、权重调整、负载均衡等管理功能如我们在开发时,需要知道Zookeeper注册中心都注册了哪些服务,有哪些消费者来消费这些服务。我们可以通过部署一个管理中心来实现。其实管理中心就是一个web应用,原来是war(2.6版本以前)包需要部署到tomcat即可。现在是jar包可以直接通过java命令运行。
3.2 安装步骤
- 从git 上下载项目 https://github.com/apache/dubbo-admin
- 修改项目下的 dubbo.properties文件
注意dubbo.registry.address对应的值需要对应当前使用的Zookeeper的ip地址和端口号
• dubbo.registry.address=zookeeper://zk所在机器ip:zk端口
• dubbo.admin.root.password=root
• dubbo.admin.guest.password=root - 切换到项目所在的路径,使用mvn 打包
mvn clean package -Dmaven.test.skip=true - java 命令运行
java -jar 对应的 jar 包,浏览器输入对应的地址运访问即可
4、 Dubbo 配置项说明
4.1 dubbo:application
对应 org.apache.dubbo.config.ApplicationConfig, 代表当前应用的信息
- name: 当前应用程序的名称,在dubbo-admin中我们也可以看到,代表这个应用名称。我们在真正使用时也会根据这个参数来进行聚合应用请求。
- owner: 当前应用程序的负责人,可以通过这个负责人找到其相关的应用列表,用于快速定位到责任人。
- qosEnable : 是否启动 QoS 默认true。
- qosPort : 启动QoS绑定的端口,默认22222。
- qosAcceptForeignIp: 是否允许远程访问,默认是false。
4.2 dubbo:registry
org.apache.dubbo.config.RegistryConfig, 代表该模块所使用的注册中心。一个模块中的服务可以将其注册到多个注册中心上,也可以注册到一个上。后面再service和reference也会引入这个注册中心。
- id : 当当前服务中provider或者consumer中存在多个注册中心时,则使用需要增加该配置。在一些公司,会通过业务线的不同选择不同的注册中心,所以一般都会配置该值。
- address : 当前注册中心的访问地址。
- protocol : 当前注册中心所使用的协议是什么。也可以直接在address 中写入,比如使用 zookeeper,就可以写成zookeeper://xx.xx.xx.xx:2181
- timeout : 当与注册中心不再同一个机房时,大多会把该参数延长。
4.3 dubbo:protocol
org.apache.dubbo.config.ProtocolConfig, 指定服务在进行数据传输所使用的协议。
- id : 在大公司,可能因为各个部门技术栈不同,所以可能会选择使用不同的协议进行交互。这里在多个协议使用时,需要指定。
- name : 指定协议名称。默认使用dubbo 。
4.4 dubbo:service
org.apache.dubbo.config.ServiceConfig, 用于指定当前需要对外暴露的服务信息,后面也会具体讲解。和dubbo:reference 大致相同。
- interface : 指定当前需要进行对外暴露的接口是什么。
- ref : 具体实现对象的引用,一般我们在生产级别都是使用Spring去进行Bean托管的,所以这里面一般也指的是Spring中的BeanId。
- version : 对外暴露的版本号。不同的版本号,消费者在消费的时候只会根据固定的版本号进行消费。
4.5 dubbo:reference
org.apache.dubbo.config.ReferenceConfig, 消费者的配置,这里只做简单说明,后面会具体讲解。
- id : 指定该Bean在注册到Spring中的id。
- interface: 服务接口名
- version : 指定当前服务版本,与服务提供者的版本一致。
- registry : 指定所具体使用的注册中心地址。这里面也就是使用上面在dubbo:registry 中所声明的id。
4.6 dubbo:method
org.apache.dubbo.config.MethodConfig, 用于在制定的dubbo:service 或者dubbo:reference 中的更具体一个层级,指定具体方法级别在进行RPC操作时候的配置,可以理解为对这上面层级中的配置针对于具体方法的特殊处理。
- name : 指定方法名称,用于对这个方法名称的RPC调用进行特殊配置。
- async: 是否异步 默认false
4.7 dubbo:service和dubbo:reference详解
这两个在dubbo中是我们最为常用的部分,其中有一些我们必然会接触到的属性。并且这里会讲到一些设置上的使用方案。
- mock: 用于在方法调用出现错误时,当做服务降级来统一对外返回结果。
- timeout: 用于指定当前方法或者接口中所有方法的超时时间。我们一般都会根据提供者的时长来具体规定。比如我们在进行第三方服务依赖时可能会对接口的时长做放宽,防止第三方服务不稳定导致服务受损。
- check: 用于在启动时,检查生产者是否有该服务。我们一般都会将这个值设置为false,不让其进行检查。因为如果出现模块之间循环引用的话,那么则可能会出现相互依赖,都进行check的话,那么这两个服务永远也启动不起来。
- retries: 用于指定当前服务在执行时出现错误或者超时时的重试机制。
- executes: 用于在提供者做配置,来确保最大的并行度。
注意提供者是否有幂等,否则可能出现数据一致性问题,注意提供者是否有类似缓存机制,如出现大面积错误时,可能因为不停重试导致雪崩。