Dubbo 是原阿里巴巴 B2B 平臺(tái)技術(shù)部傾力打造的一款開源且優(yōu)秀的面向 Java 平臺(tái)的服務(wù)框架,歷經(jīng)三次大的迭代筛严,從最早盲從 OSGi 的坑里跳出來茄袖,再到序列化協(xié)議和通信框架的定型,最終才完善和成型,可以說摘刑,國內(nèi)開源服務(wù)化框架中無出其右者。
但是刻坊,Dubbo 研發(fā)的年代處于 Spring 框架的 XSD 時(shí)代枷恕,所以,使用上還是會(huì)更多以 XML 形式定義服務(wù)和訪問服務(wù)谭胚,但好在 SpringBoot 框架在 IoC 容器的配置方式上“不挑食”徐块,所以,我們還是可以讓 Dubbo 和 SpringBoot 框架友好相處灾而。
直接使用 Dubbo 開發(fā)微服務(wù)了胡控,為什么還要用 SpringBoot?旁趟,這是一個(gè)很好的問題昼激。
使用 SpringBoot 開發(fā)基于 Dubbo 框架的微服務(wù)的原因就是,我們將 Dubbo 微服務(wù)以 SpringBoot 形式標(biāo)準(zhǔn)化了锡搜。如此一來橙困,我們既可以享受 SpringBoot 框架和周邊的一系列研發(fā)支持,還可以用統(tǒng)一的形式發(fā)布耕餐、部署和運(yùn)維凡傅。
微服務(wù)的一個(gè)特點(diǎn)就是數(shù)量多,一個(gè)人應(yīng)對(duì) 1000 個(gè)相同操作接口的微服務(wù)和一個(gè)人應(yīng)對(duì) 1000 個(gè)不同操作接口的微服務(wù)相比來說肠缔,相信你會(huì)選擇前者夏跷。
2015 年到 2016年哼转,美元升值預(yù)期持續(xù)升溫,大家想必對(duì)匯率也比較關(guān)注拓春,所以释簿,我們不妨實(shí)現(xiàn)一個(gè)基于央行匯率的查詢服務(wù)。
1. 定義匯率查詢服務(wù)接口
我們新建 Maven 工程 currency-rates-api:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.keevol.springboot</groupId>
<artifactId>currency-rates-api</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>currency-rates-api</name>
<url>http://maven.apache.org</url>
<properties>
<java_source_version>1.8</java_source_version>
<java_target_version>1.8</java_target_version>
<file_encoding>UTF-8</file_encoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java_source_version}</source>
<target>${java_target_version}</target>
<encoding>${file_encoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<charset>${file_encoding}</charset>
<encoding>${file_encoding}</encoding>
<additionalparam>-Xdoclint:none</additionalparam>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
然后定義匯率查詢服務(wù)接口:
public interface CurrencyRateService {
ExchangeRate quote(CurrencyPair currencyPair) throws IOException;
}
之后通過 mvn install 或者 mvn deploy 將 currency-rates-api 發(fā)布到本地或者遠(yuǎn)程 maven 倉庫硼莽。
2. 實(shí)現(xiàn)匯率查詢服務(wù)
新建 Maven 工程 currency-rates-service:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.keevol.springboot</groupId>
<artifactId>currency-rates-service</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>currency-rates-service</name>
<url>http://maven.apache.org</url>
<properties>
<java_source_version>1.8</java_source_version>
<java_target_version>1.8</java_target_version>
<file_encoding>UTF-8</file_encoding>
<spring_version>4.1.6.RELEASE</spring_version>
</properties>
<dependencies>
<dependency>
<groupId>com.keevol.springboot</groupId>
<artifactId>currency-rates-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.3</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring_version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring_version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring_version}</version>
</dependency>
</dependencies>
</project>
主要關(guān)注針對(duì) currency-rates-api 以及 Dubbo 框架的依賴定義。
項(xiàng)目創(chuàng)建完成后煮纵,我們著手實(shí)現(xiàn)服務(wù)接口:
public class CurrencyRateServiceImpl implements CurrencyRateService {
private CurrencyRateRepository rateRepository;
public ExchangeRate quote(CurrencyPair currencyPair) throws IOException {
return rateRepository.get(currencyPair);
}
public CurrencyRateRepository getRateRepository() {
return rateRepository;
}
public void setRateRepository(CurrencyRateRepository rateRepository) {
this.rateRepository = rateRepository;
}
}
其中懂鸵,CurrencyRateRepository 可以根據(jù)情況給出相應(yīng)的實(shí)現(xiàn),比如通過直接對(duì)接銀行的系統(tǒng)獲取匯率或者通過爬取官網(wǎng)數(shù)據(jù)獲得數(shù)據(jù)都可以行疏,這里不做過多解釋匆光。
服務(wù)實(shí)現(xiàn)之后,我們需要通過 Dubbo 框架暴露出去給外部使用酿联,所以终息,我們通過 Dubbo 的服務(wù)描述文件(即標(biāo)準(zhǔn)的 Spring 框架 XML 形式的配置文件)完成最終服務(wù)的對(duì)外開放:
<?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:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
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://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="${dubbo.application.name}"
owner="${dubbo.application.owner}" />
<dubbo:protocol
name="${dubbo.application.protocol.name}"
port="${dubbo.application.protocol.port}" />
<dubbo:service
interface="com.keevol.springboot.services.currency.rates.CurrencyRateService"
ref="serviceImpl" registry="N/A" />
<bean id="serviceImpl"
class="com.keevol.springboot.services.currency.rates.CurrencyRateServiceImpl" />
</beans>
因?yàn)橹皇窃褪纠哉耆茫覀儧]有定義更加嚴(yán)謹(jǐn)?shù)姆?wù)注冊(cè)方式(registry="N/A")周崭,生產(chǎn)環(huán)境下,大家還是需要選擇合適的注冊(cè)服務(wù)喳张,比如 Zookeeper续镇。
使用 Dubbo 框架的服務(wù)不依賴傳統(tǒng) J2EE 的容器對(duì)外提供服務(wù),而是以獨(dú)立進(jìn)程的形式對(duì)外服務(wù)销部,所以摸航,我們還需要提供一個(gè) Bootstrap 類用于啟動(dòng)我們的 Dubbo 服務(wù):
public class Bootstrap {
public static void main(String[] args) throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("spring/services.xml");
((AbstractApplicationContext) context).registerShutdownHook();
System.in.read();
}
}
至此,一個(gè)獨(dú)立部署運(yùn)行的 Dubbo 服務(wù)宣告完成舅桩。
3. 沒有 SpringBoot 什么事兒
不管是否使用 SpringBoot酱虎,基于 Dubbo 框架的服務(wù)從其服務(wù)的定義,到服務(wù)的實(shí)現(xiàn)擂涛,都是常規(guī)而無法省略的工作读串,但是:
- 服務(wù)完成后,啟動(dòng) main 函數(shù)里的邏輯貌似每次都要編寫一樣的歼指?
- 服務(wù)完成后爹土,以什么樣的形式發(fā)布并部署?zip 包踩身,還是 jar 包胀茵,亦或 war 包,甚至其他形式挟阻?
考慮到這些琼娘,就會(huì)涉及 SpringBoot旁赊,一旦將 Dubbo 服務(wù)以 SpringBoot 的形式封裝,Dubbo 服務(wù)就可以既享受 SpringBoot 的開發(fā)便捷性昨稼,又能以統(tǒng)一的形式發(fā)布和部署(比如可執(zhí)行的 jar 形式)侣诺。
雖然我們無法省略和簡化服務(wù)的定義和實(shí)現(xiàn)這些步驟,但 Dubbo 服務(wù)的 main 函數(shù)邏輯實(shí)際上是可以固化下來并且復(fù)用的熄浓,否則情臭,每個(gè)人給出的 Dubbo 服務(wù)實(shí)現(xiàn)的啟動(dòng)類都可能不一樣,進(jìn)而導(dǎo)致運(yùn)維操作不一樣赌蔑。
在上面我們給出的 main 函數(shù)實(shí)現(xiàn)中俯在,為了阻止 Dubbo 服務(wù)的進(jìn)程退出(主線程執(zhí)行完畢,無其他非 daemon 線程存活)娃惯。
我們使用了 IO 操作來達(dá)成這一目的(System.in.read())跷乐,但實(shí)際上,這不是一個(gè)很好的做法趾浅。更好的做法是愕提,我們?cè)O(shè)置一個(gè)服務(wù)是否關(guān)閉的開關(guān),只有當(dāng)外部調(diào)用相應(yīng)管理接口將服務(wù)關(guān)閉之后皿哨,再關(guān)閉當(dāng)前的 Dubbo 服務(wù)浅侨,所以,基于此思路往史,我們可以實(shí)現(xiàn)一個(gè) ShutdownLatch:
public interface ShutdownLatchMBean {
String shutdown();
}
public class ShutdownLatch implements ShutdownLatchMBean {
protected AtomicBoolean running = new AtomicBoolean(false);
public long checkIntervalInSeconds = 10;
private String domain = "com.wacai.lifecycles";
public ShutdownLatch() {
}
public ShutdownLatch(String domain) {
this.domain = domain;
}
public void await() throws Exception {
if (running.compareAndSet(false, true)) {
MBeanServer mBeanServer = ManagementFactory.get - PlatformMBeanServer();
mBeanServer.registerMBean(this, new ObjectName(domain, "name", "ShutdownLatch"));
while (running.get()) {
TimeUnit.SECONDS.sleep(checkIntervalInSeconds);
}
}
}
@Override
public String shutdown() {
if (running.compareAndSet(true, false)) {
return "shutdown signal sent, shutting down..";
} else {
return "shutdown signal had been sent, no need again and again and again...";
}
}
public static void main(String[] args) throws Exception {
ShutdownLatch latch = new ShutdownLatch("your_domain_for_mbeans");
latch.await();
}
}
ShutdownLatch 默認(rèn)以 JMX 的 MBean 暴露為管理接口仗颈,所以,Dubbo 服務(wù)的 main 函數(shù)可以規(guī)范為:
ApplicationContext context = new ClassPathXmlApplicationContext("spring/services.xml");
((AbstractApplicationContext) context).registerShutdownHook();
ShutdownLatch latch = new ShutdownLatch("your_domain_for_mbeans");
latch.await();
為了簡化基于 SpringBoot 的 Dubbo 服務(wù)開發(fā)椎例,我們可以將針對(duì) Dubbo 框架的依賴以及對(duì)服務(wù)啟動(dòng)類的規(guī)范封裝為一個(gè) spring-boot-starter-dubbo 這樣的自動(dòng)配置模塊挨决,此后,要開發(fā)一個(gè)基于 SpringBoot 的 Dubbo 服務(wù)订歪,只要依賴這一自動(dòng)配置模塊即可脖祈。
所以,我們新建 Maven 項(xiàng)目 spring-boot-starter-dubbo:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.keevol.springboot</groupId>
<artifactId>spring-boot-starter-dubbo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-starter-dubbo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<java_source_version>1.8</java_source_version>
<java_target_version>1.8</java_target_version>
<file_encoding>UTF-8</file_encoding>
<spring_version>4.1.6.RELEASE</spring_version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.3</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
項(xiàng)目的 pom.xml 中刷晋,我們可以重點(diǎn)關(guān)注針對(duì) spring-boot-starter 和 Dubbo 框架的依賴盖高。
所有的 SpringBoot 應(yīng)用啟動(dòng)都是基于標(biāo)準(zhǔn)的 SpringApplication.run 完成的,為了在啟動(dòng)類執(zhí)行完成后可以阻止 Dubbo 服務(wù)進(jìn)程的推出眼虱,我們需要在 SpringBoot 啟動(dòng)類的 main 函數(shù)最后調(diào)用 ShutdownLatch.await()喻奥。
但是如果要求每個(gè)開發(fā)者在自己的 SpringBoot 啟動(dòng)類中調(diào)用這段代碼,顯然這并沒有給開發(fā)者的工作帶來任何簡化捏悬,所以撞蚕,我們選擇使用 CommandLineRunner 來完成針對(duì) ShutdownLatch.await() 的調(diào)用工作。
教程前面已經(jīng)介紹過 CommandLineRunner过牙,任何注冊(cè)到 SpringBoot 應(yīng)用的 CommandLineRunner 都將在 SpringApplication.run 執(zhí)行完成后再執(zhí)行甥厦,恰好符合我們當(dāng)前場(chǎng)景需要纺铭,代碼如下所示:
public class DubboServiceLatchCommandLineRunner implements CommandLineRunner {
private String domain = "com.keevol.services.management";
@Override
public void run(String... args) throws Exception {
ShutdownLatch latch = new ShutdownLatch(getDomain());
latch.await();
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
}
然后我們需要將 DubboServiceLatchCommandLineRunner 注冊(cè)到 SpringBoot 應(yīng)用的容器之中,所以刀疙,定義一個(gè) JavaConfig 類如下:
@Configuration
@Order
public class DubboAutoConfiguration {
protected Logger logger = LoggerFactory.getLogger(DubboAutoConf - iguration.class);
@Value("${shutdown.latch.domain.name: com.keevol.services.management}")
private String shutdownLatchDomainName;
@Bean
@ConditionalOnClass(name = "com.alibaba.dubbo.rpc.Exporter")
public DubboServiceLatchCommandLineRunner configureDubboService-LatchCommandLineRunner() {
logger.debug("DubboAutoConfiguration enabled by adding Dubbo-ServiceLatchCommandLineRunner.");
DubboServiceLatchCommandLineRunner runner = new DubboServi-ceLatchCommandLineRunner();
runner.setDomain(shutdownLatchDomainName);
return runner;
}
}
我們使用 @Order 標(biāo)注了該配置類以保證 DubboServiceLatchCommandLineRunner 在最后執(zhí)行舶赔,以避免阻塞其他邏輯的執(zhí)行。
萬里長征走到了最后一步谦秧,要實(shí)現(xiàn)一個(gè) SpringBoot 的自動(dòng)配置模塊竟纳,我們需要將 DubboAutoConfiguration 配置類通過 META-INF/spring.factories 配置文件注冊(cè)并發(fā)布:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.keevol.springboot.dubbo.autoconfigure.DubboAutoConfiguration
至此,我們只要通過 mvn install 或者 mvn deploy 將 spring-boot-starter-dubbo 發(fā)布到本地或者遠(yuǎn)程 Maven 倉庫油够,以后開發(fā) Dubbo 服務(wù)就只需要在服務(wù)的項(xiàng)目依賴中添加如下依賴:
<dependency>
<groupId>com.keevol.springboot</groupId>
<artifactId>spring-boot-starter-dubbo</artifactId>
<version>1.0.0</version>
</dependency>
然后以標(biāo)準(zhǔn)的 SpringApplication 加載 Dubbo 服務(wù)的配置并啟動(dòng)就可以了:
@SpringBootApplication
public class DubboWithSpringbootApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Dubb - oWithSpringbootApplication.class,
"classpath*:/spring/**/*.xml");
application.run(args);
}
}
當(dāng)然蚁袭,規(guī)范 Dubbo 依賴以及服務(wù)的啟動(dòng)邏輯只是使用 SpringBoot 獲得的好處之一,最主要的石咬,我們提供了一種基于 SpringBoot 的標(biāo)準(zhǔn)化的 Dubbo 服務(wù)開發(fā)和發(fā)布實(shí)踐,這對(duì)于海量微服務(wù)的開發(fā)和運(yùn)維來說是很重要的卖哎。