前言

大家好,今天开始给大家分享 — Dubbo 专题之 Dubbo 延迟服务暴露。在前一个章节中我们介绍了 Dubbo ​​Stub​​​ 和 ​​Mock​​​,我们例举了常见的使用场景并且进行了源码解析来分析其实现原理,同时我们知道 Dubbo ​​Stub​​​ 和 ​​Mock​​ 都是基于对调用代理对象的包装实现的,这样可以为我们在调用服务时做一些前置或后置处理工作。有的小伙伴可能会遇到这样的场景:在我们的应用服务中有很多的本地缓存或者分布式缓存,这些缓存有可能需要加载一段时间那么在这个缓存加载过程中我们希望接口不要对外提供服务,那有没有一种机制让我们对缓存加载完成后再暴露服务的机制呢?那么在本章节我们会通过介绍 Dubbo 延迟暴露服务来解决这个问题。那什么是延迟服务暴露?以及延迟服务暴露的实现原理是什么呢?下面就让我们快速开始吧!

1. 延迟服务暴露简介

在当前我们使用的​​Dubbo2.7.x​​​版本中服务暴露时机是 ​​Spring​​​ 容器启动完成并广播出事件 ​​ApplicationContextEvent​​​ 时才进行对服务暴露。那这里所谓的延迟暴露也就是在接收到 ​​ApplicationContextEvent​​​ 事件后开始一个指定时间的延迟直到延迟时间到才开始对服务进行暴露。延迟服务暴露的核心就是一个延迟调度器,当延迟时间到就开始进行服务暴露。其配置参数为:​​delay="5000"​​,这里的时间单位为毫秒。

**Tips:**其他的 Dubbo 版本的服务暴露可能有一些出入,读者以最新版本为主。

2. 使用方式

延迟服务暴露可以通过 XML 或者注解的方式进行配置且指定的延迟时间单位为毫秒。

2.1 XML 配置方式

<!--延迟1秒暴露Dubbo服务-->
<dubbo:service interface="com.muke.dubbocourse.protocol.api.BookFacade" ref="bookFacade" delay="1000" />

如果配置​​delay="-1"​​​则表示在 ​​Spring​​ 容器初始化完后再暴露服务。

2.2 注解配置方式

@DubboService(delay = 1000)
public class BookFacadeImpl implements BookFacade {

}

使用​​@DubboService​​​注解或​​@Service​​​注解。​​delay = 1000​​表示延迟1秒后才进行服务暴露。

3. 使用场景

通过上面的延迟服务暴露的简介我们可以了解到:延迟服务暴露其实就是针对需要暴露的服务配置一个固定的延迟时间,延迟时间一到立即开始服务的暴露。那么我们基于上面的服务暴露的时效性我们简单的介绍一些工作中常使用的场景:

  1. 缓存预热:当我们的暴露服务需要依赖一些静态数据,这些静态数据是通过加载数据库或者文件然后缓存到应用内存中。此时我们可以通过在服务延迟暴露的时间段内进行缓存预加载。
  2. 依赖资源:假设我们对外提供的服务需要依赖另外一个服务,而另外一个服务的暴露时间比较缓慢,那么我们就可以把当前对外暴露的服务进行延迟暴露,这样就可以减少当调用依赖服务时出现超时异常的情况。

4. 示例演示

我们同样以获取图书列表为例来进行演示。项目结构如下:

Dubbo 延迟服务暴露_dubbo

因为延迟服务暴露是配置服务提供端,所有我们这里只看服务提供者的 XML 配置​​dubbo-provider-xml.xml​​:

<?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="demo-provider" metadata-type="remote"/>

<!--注册中心-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>

<bean id="bookFacade" class="com.muke.dubbocourse.delayexport.provider.BookFacadeImpl"/>

<!--延迟1秒暴露Dubbo服务-->
<dubbo:service interface="com.muke.dubbocourse.common.api.BookFacade" ref="bookFacade" delay="1000"/>

</beans>

上面的 XML 配置中主要配置了​​delay="1000"​​​表示等 ​​Spring​​​ 容器启动完成后服务延迟​​1​​秒钟后才开始暴露服务。其注解配置也是类似就不再演示。

5. 实现原理

下面我们简单的通过服务暴露的源码进行分析。我们都知道当 Spring 容器启动完成会发出 ​​ApplicationContextEvent​​​ 事件,我们可以看到这个​​org.apache.dubbo.config.spring.context .DubboBootstrapApplicationListener# onApplicationContextEvent​​方法核心代码:

public void onApplicationContextEvent(ApplicationContextEvent event) {
if (event instanceof ContextRefreshedEvent) {
//Spring 容器启动完成
onContextRefreshedEvent((ContextRefreshedEvent) event);
} else if (event instanceof ContextClosedEvent) {
//Spring 容器关闭
onContextClosedEvent((ContextClosedEvent) event);
}
}

当接收到​​ContextRefreshedEvent​​​事件后会最终会调用​​org.apache.dubbo.config.bootstrap. DubboBootstrap#start​​方法启动服务注册核心代码如下:

public DubboBootstrap start() {
//判断服务是否启动 防止重复暴露服务 注意:这里是原子操作
if (started.compareAndSet(false, true)) {
initialize();
//..
// 暴露Dubbo服务
exportServices();

//...
}
return this;
}

上面的代码先进行原子操作去设置启动标识防止重复暴露服务,其​​exportServices​​代码如下:

/***
*
* dubbo服务导出
*
* @author liyong
* @date 16:23 2020-03-01
* @param
* @exception
* @return void
**/
private void exportServices() {
//循环所有需要服务暴露的配置
configManager.getServices().forEach(sc -> {
// TODO, compatible with ServiceConfig.export()
ServiceConfig serviceConfig = (ServiceConfig) sc;
serviceConfig.setBootstrap(this);

//是否异步导出
if (exportAsync) {
//获取线程池
ExecutorService executor = executorRepository.getServiceExporterExecutor();
Future<?> future = executor.submit(() -> {
//异步服务暴露
sc.export();
});
asyncExportingFutures.add(future);
} else {
//同步服务暴露导出
sc.export();
exportedServices.add(sc);
}
});
}

上面的代码是循环对所有的 Dubbo 服务进行暴露,注意这里有一个​​exportAsync​​​标识来判断是否异步暴露服务(异步暴露服务是指在另外的线程执行不阻塞当前线程)。下面我们看到主要的服务暴露代码​​org.apache.dubbo.config.ServiceConfig#export​​方法:

/**
*
* 服务暴露
*
* @author liyong
* @date 10:55 PM 2020/11/18
* @param
* @exception
* @return void
**/
public synchronized void export() {

//...

//是否延迟暴露判断
if (shouldDelay()) {
//延迟暴露
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
//服务暴露
doExport();
}
}

上面即是我们延迟服务暴露的核心代码 ​​DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS)​​使用延时调度器进行服务暴露。

6. 小结

在本小节中我们主要学习了 Dubbo 中延迟服务暴露以及使用方式,同时也分析了延迟服务暴露实现的原理,其本质上是通过延迟调度器进行服务暴露,其中延迟调度器是关键所在。

本节课程的重点如下:

  1. 理解 Dubbo 延迟服务暴露
  2. 了解了延迟服务暴露的使用方式
  3. 了解延迟服务暴露使用场景
  4. 了解 延迟服务暴露实现原理

作者

个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。关注公众号:青年IT男 获取最新技术文章推送!

微信公众号:

Dubbo 延迟服务暴露_spring_02