建立基於Dubbo框架的SpringBoot微服務

Dubbo 是原阿里巴巴 B2B 平臺技術部傾力打造的一款開源且優秀的面向 Java 平臺的服務框架,歷經三次大的迭代,從最早盲從 OSGi 的坑裡跳出來,再到序列化協議和通訊框架的定型,最終才完善和成型,可以說,國內開源服務化框架中無出其右者。

但是,Dubbo 研發的年代處於 Spring 框架的 XSD 時代,所以,使用上還是會更多以 XML 形式定義服務和訪問服務,但好在 SpringBoot 框架在 IoC 容器的配置方式上“不挑食”,所以,我們還是可以讓 Dubbo 和 SpringBoot 框架友好相處。

直接使用 Dubbo 開發微服務了,為什麼還要用 SpringBoot?,這是一個很好的問題。

使用 SpringBoot 開發基於 Dubbo 框架的微服務的原因就是,我們將 Dubbo 微服務以 SpringBoot 形式標準化了。如此一來,我們既可以享受 SpringBoot 框架和周邊的一系列研發支援,還可以用統一的形式釋出、部署和運維。

微服務的一個特點就是數量多,一個人應對 1000 個相同操作介面的微服務和一個人應對 1000 個不同操作介面的微服務相比來說,相信你會選擇前者。

2015 年到 2016年,美元升值預期持續升溫,大家想必對匯率也比較關注,所以,我們不妨實現一個基於央行匯率的查詢服務。

1。 定義匯率查詢服務介面

我們新建 Maven 工程 currency-rates-api:

4。0。0 com。keevol。springboot currency-rates-api 1。0-SNAPSHOT jar currency-rates-api http://maven。apache。org 1。8 1。8 UTF-8 org。apache。maven。plugins maven-compiler-plugin ${java_source_version} ${java_target_version} ${file_encoding} org。apache。maven。plugins maven-source-plugin attach-sources jar org。apache。maven。plugins maven-javadoc-plugin ${file_encoding} ${file_encoding} -Xdoclint:none attach-javadocs jar

然後定義匯率查詢服務介面:

public interface CurrencyRateService { ExchangeRate quote(CurrencyPair currencyPair) throws IOException;}

之後透過 mvn install 或者 mvn deploy 將 currency-rates-api 釋出到本地或者遠端 maven 倉庫。

2。 實現匯率查詢服務

新建 Maven 工程 currency-rates-service:

4。0。0 com。keevol。springboot currency-rates-service 1。0-SNAPSHOT jar currency-rates-service http://maven。apache。org 1。8 1。8 UTF-8 4。1。6。RELEASE com。keevol。springboot currency-rates-api 1。0-SNAPSHOT com。alibaba dubbo 2。5。3 org。springframework spring org。springframework spring-context ${spring_version} commons-logging commons-logging org。springframework spring-context-support ${spring_version} org。springframework spring-jdbc ${spring_version}

主要關注針對 currency-rates-api 以及 Dubbo 框架的依賴定義。

專案建立完成後,我們著手實現服務介面:

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 可以根據情況給出相應的實現,比如透過直接對接銀行的系統獲取匯率或者透過爬取官網資料獲得資料都可以,這裡不做過多解釋。

服務實現之後,我們需要透過 Dubbo 框架暴露出去給外部使用,所以,我們透過 Dubbo 的服務描述檔案(即標準的 Spring 框架 XML 形式的配置檔案)完成最終服務的對外開放:

<?xml version=“1。0” encoding=“UTF-8”?>

因為只是原型示例,所以,我們沒有定義更加嚴謹的服務註冊方式(registry=“N/A”),生產環境下,大家還是需要選擇合適的註冊服務,比如 Zookeeper。

使用 Dubbo 框架的服務不依賴傳統 J2EE 的容器對外提供服務,而是以獨立程序的形式對外服務,所以,我們還需要提供一個 Bootstrap 類用於啟動我們的 Dubbo 服務:

public class Bootstrap { public static void main(String[] args) throws IOException { ApplicationContext context = new ClassPathXmlApplicationContext(“spring/services。xml”); ((AbstractApplicationContext) context)。registerShutdownHook(); System。in。read(); }}

至此,一個獨立部署執行的 Dubbo 服務宣告完成。

3。 沒有 SpringBoot 什麼事兒

不管是否使用 SpringBoot,基於 Dubbo 框架的服務從其服務的定義,到服務的實現,都是常規而無法省略的工作,但是:

服務完成後,啟動 main 函數里的邏輯貌似每次都要編寫一樣的?

服務完成後,以什麼樣的形式釋出並部署?zip 包,還是 jar 包,亦或 war 包,甚至其他形式?

考慮到這些,就會涉及 SpringBoot,一旦將 Dubbo 服務以 SpringBoot 的形式封裝,Dubbo 服務就可以既享受 SpringBoot 的開發便捷性,又能以統一的形式釋出和部署(比如可執行的 jar 形式)。

雖然我們無法省略和簡化服務的定義和實現這些步驟,但 Dubbo 服務的 main 函式邏輯實際上是可以固化下來並且複用的,否則,每個人給出的 Dubbo 服務實現的啟動類都可能不一樣,進而導致運維操作不一樣。

在上面我們給出的 main 函式實現中,為了阻止 Dubbo 服務的程序退出(主執行緒執行完畢,無其他非 daemon 執行緒存活)。

我們使用了 IO 操作來達成這一目的(System。in。read()),但實際上,這不是一個很好的做法。更好的做法是,我們設定一個服務是否關閉的開關,只有當外部呼叫相應管理介面將服務關閉之後,再關閉當前的 Dubbo 服務,所以,基於此思路,我們可以實現一個 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 預設以 JMX 的 MBean 暴露為管理介面,所以,Dubbo 服務的 main 函式可以規範為:

ApplicationContext context = new ClassPathXmlApplicationContext(“spring/services。xml”);((AbstractApplicationContext) context)。registerShutdownHook();ShutdownLatch latch = new ShutdownLatch(“your_domain_for_mbeans”);latch。await();

為了簡化基於 SpringBoot 的 Dubbo 服務開發,我們可以將針對 Dubbo 框架的依賴以及對服務啟動類的規範封裝為一個 spring-boot-starter-dubbo 這樣的自動配置模組,此後,要開發一個基於 SpringBoot 的 Dubbo 服務,只要依賴這一自動配置模組即可。

所以,我們新建 Maven 專案 spring-boot-starter-dubbo:

<?xml version=“1。0” encoding=“UTF-8”?> 4。0。0 com。keevol。springboot spring-boot-starter-dubbo 0。0。1-SNAPSHOT jar spring-boot-starter-dubbo Demo project for Spring Boot org。springframework。boot spring-boot-starter-parent 1。3。1。RELEASE <!—— lookup parent from repository ——> 1。8 1。8 UTF-8 4。1。6。RELEASE org。springframework。boot spring-boot-starter com。alibaba dubbo 2。5。3 org。springframework spring org。springframework。boot spring-boot-maven-plugin

專案的 pom。xml 中,我們可以重點關注針對 spring-boot-starter 和 Dubbo 框架的依賴。

所有的 SpringBoot 應用啟動都是基於標準的 SpringApplication。run 完成的,為了在啟動類執行完成後可以阻止 Dubbo 服務程序的推出,我們需要在 SpringBoot 啟動類的 main 函式最後呼叫 ShutdownLatch。await()。

但是如果要求每個開發者在自己的 SpringBoot 啟動類中呼叫這段程式碼,顯然這並沒有給開發者的工作帶來任何簡化,所以,我們選擇使用 CommandLineRunner 來完成針對 ShutdownLatch。await() 的呼叫工作。

教程前面已經介紹過 CommandLineRunner,任何註冊到 SpringBoot 應用的 CommandLineRunner 都將在 SpringApplication。run 執行完成後再執行,恰好符合我們當前場景需要,程式碼如下所示:

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 註冊到 SpringBoot 應用的容器之中,所以,定義一個 JavaConfig 類如下:

@Configuration@Orderpublic 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 標註了該配置類以保證 DubboServiceLatchCommandLineRunner 在最後執行,以避免阻塞其他邏輯的執行。

萬里長征走到了最後一步,要實現一個 SpringBoot 的自動配置模組,我們需要將 DubboAutoConfiguration 配置類透過 META-INF/spring。factories 配置檔案註冊併發布:

org。springframework。boot。autoconfigure。EnableAutoConfiguration=com。keevol。springboot。dubbo。autoconfigure。DubboAutoConfiguration

至此,我們只要透過 mvn install 或者 mvn deploy 將 spring-boot-starter-dubbo 釋出到本地或者遠端 Maven 倉庫,以後開發 Dubbo 服務就只需要在服務的專案依賴中新增如下依賴:

com。keevol。springboot spring-boot-starter-dubbo 1。0。0

然後以標準的 SpringApplication 載入 Dubbo 服務的配置並啟動就可以了:

@SpringBootApplicationpublic class DubboWithSpringbootApplication { public static void main(String[] args) { SpringApplication application = new SpringApplication(Dubb - oWithSpringbootApplication。class, “classpath*:/spring/**/*。xml”); application。run(args); }}

當然,規範 Dubbo 依賴以及服務的啟動邏輯只是使用 SpringBoot 獲得的好處之一,最主要的,我們提供了一種基於 SpringBoot 的標準化的 Dubbo 服務開發和釋出實踐,這對於海量微服務的開發和運維來說是很重要的。

最後

有需要Spring Boot影片教程的小夥伴們注意啦:

點贊+關注+轉發+私信關鍵詞【boot】即可免費領取!