Cloud Native 應用架構隨着雲技術的發展受到業界特別重視和關注,尤爲是 CNCF(Cloud Native Computing Foundation)項目蓬勃發展之際。Dubbo 做爲服務治理的標誌性項目,天然緊跟業界的潮流,擁抱技術的變化。本次分享的議題包括介紹 Apache 孵化項目Dubbo Spring Boot Project 以及彙報 Dubbo 與 Cloud Native 整合過程當中的一些實踐與思考,如適配 Spring Cloud 、服務發現、服務網關、服務跟蹤以及監控等。html
注:爲了讀者的閱讀方便和習慣,本文字稿將在演講內容的基礎上作出適當的調整。react
小馬哥(微信:mercyblitz),一線互聯網中間件技術專家,十餘年 Java EE 從業經驗,Dubbo 維護者、架構師以及微服務佈道師。目前主要負責阿里巴巴集團微服務技術實施、架構衍進、基礎設施構建等。重點關注雲計算、微服務以及軟件架構等領域。經過 SUN Java(SCJP、SCWCD、SCBCD)以及 Oracle OCA 等的認證。git
Github:https://github.com/mercyblitz程序員
今天我很是榮幸地與你們一塊兒討論關於 Dubbo Cloud Native 相關議題,本次議題緊扣「實踐與思考「兩個關鍵字,主要的議程包括:github
Cloud Native 基礎設施web
Cloud Native 架構選型算法
Dubbo Cloud Native 準備spring
關於 Cloud Native 的定義,不一樣的雲平臺可能給出的內容存在差別。此處,我向你們介紹目前最熱門的 CNCF 的定義:數據庫
」CNCF Cloud Native Definition v1.0「 中的描述:apache
Cloud native technologies empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach.
相對於其餘學術流派,CNCF 的 Cloud Native 定義更爲具體,偏向於軟件技術。這一點咱們從文中的一些關鍵字可以明顯地體會到,如關鍵字 "Containers(容器)"、"service meshes"、」microservices(微服務)「等。一般,開發人員較爲關注的 Cloud Native 基礎設施爲:「服務發現」、「負載均衡」、「服務網關」、「分佈式配置」、「服務熔斷」以及「跟蹤監控」,如圖所示:
因爲 PPT 格式的限制,此處我將「鏈路跟蹤」與「服務監控」 並陳爲「跟蹤監控」。接下來,咱們進入「服務發現」的討論。
隨着微服務架構(MSA)受到不一樣規模企業的青睞,服務治理的實施逐漸被提上基礎設施改造的議程。儘管這些概念在 SOA 時代已經提出,然而引發業界普遍關注應歸功於微服務。服務發現(Service Discovery )做爲服務治理的核心特性,一般也將服務註冊(Service Registration)一併討論。不管是服務發現,仍是服務註冊,在具體落地實施時,它們必須面對技術選型的問題。在座的各位,包括我,大多數是 Java 程序員,天然關心 Java 的技術方案。目前,Java 社區最爲津津樂道的方案莫過於 Spring Cloud,搭配 Netflix OSS 組件 Eureka,幫助 Spring Boot 應用快速搭建服務發現體系。其中,Eureka Server 做爲註冊中心服務器,Spring Boot 應用整合 Eureka Client 向 Eureka Server 註冊。實際上,Spring Cloud 除了整合 Netflix Eureka 做爲服務發現以外,還提供了 Apache Zookeeper 和 HachiCorp Consul 的實現,因此這三種方案出如今當前頁面:
其中還包括 Redis 和 Apache Curator,前者是 Dubbo 的服務發現實現方案之一,然而小馬哥並不建議使用 Redis 做爲註冊中心,仍是保持它緩存中間件的單純性較好。而 Curator 做爲 Zookeeper Java 客戶端類庫,它不但可用在 Dubbo,並且其擴展項目 Curator Service Discovery 也是 Spring Cloud 整合 Zookeeper 做爲服務發現的關鍵基礎設施。或許你們思考以上方案應該如何選型的問題。
當服務發現選型時,Netflix Eureka 或許是在開發人員腦海中復現的首選方案。然而 Eureka 在阿里大規模實踐時,它的表現並不理想,當 Eureka 客戶端服務實例數量達到必定時,Eureka Server 時常會出現服務不可用的狀況,主要的問題集中在更新(Update)機制、複製(Replication)機制以及內存型存儲。因爲時間的關係,此處我不加詳細說明,部分答案在 Eureka Wiki Eureka 2.0 Motivations 中也有描述:
Why Eureka 2.0?
Only support homogenous client views
Only supports scheduled updates
Replication algorithm limits scalability
注:以上具體內容在分享現場並無具體說起,此處特地爲讀者補充。
以上問題 Netflix 早在 2015 年已意識到,然而 Eureka 2.0 的發佈遙遙無期。後來,我託朋友聯繫上了 Netflix 的工程師,諮詢他們關於 Eureka 1 在自身生產環境的使用狀況。他們的回覆是部分場景在使用。這樣的答覆值得玩味,再細問其覆蓋比重,對方三緘其口。這不得不讓我對 Eureka 的成熟度產生了質疑,因此我不建議你們在數以千計的應用實例場景中使用。
Consul 一樣做爲 Spring Cloud 服務中心,基於 GO 語言開發,其數據一致性採用 Raft 算法,低內存,集羣支持。曾一度成爲我理想的替換 Eureka 的方案,不過本人並不具有 Consul 的大規模運用,爲此還特地請教永輝雲創的架構師翟永超(《Spring Cloud 微服務實戰》的做者)。他告知 Consul 表現不錯,並在跨 DC(數據中心)方面也比較穩定:
他的答覆讓我加強了 Consul 的信心,稍顯遺憾的是其 Consul 應用節點略少。後來,我據說 B 站的哥們自研服務發現中間件 discovery,他們應該也對 Consul 作過調研和評估,他們的見解是:
Github 開源地址:https://github.com/Bilibili/discovery/
discovery 在 B 站 K8S 上的使用狀況:
綜合兩家公司的評估,儘管沒有通過本人實際操做,而且二者沒有提供具體的數據指標,然而在必定程度上說明 Consul 做爲註冊中心的實例節點規模大概在 2k 之內。換言之,它比較適合中小型企業。
Zookeeper 便可是 Spring Cloud 註冊中心,又能做爲 Dubbo 註冊中心,與 Eureka 不一樣,它屬於 CP 分佈式策略,然後者屬於 AP。二者的共同點在於均屬於內存型註冊中心,在大規模集羣場景,也會遇到 Eureka 相似的問題。不過從運維的角度,相較於 Eureka 而言,熟悉 Zookeeper 運維朋友更多。在生態性方面,Zookeeper 周邊的生態更豐富,如 Zookeeper C API,儘管 Eureka 提供了語言無關性的 REST 接口。同時,Zookeeper 還從當配置服務器的角色,下降了學習的成本。綜上結論,我推薦使用 Zookeeper 做爲服務發現基礎設施,不管您選擇 Dubbo 方案,仍是使用 Spring Cloud。儘管它在大規模集羣時也出現 Zookeeper 間歇性卡頓等問題。
負載均衡是第二個重要 Cloud Native 基礎設施,熟悉 Spring Cloud 的朋友必定對右側的蝴蝶結有印象,它就是 Netflix OSS 負載均衡組件 Ribbon,框架層面提供了多種負載均衡規則,如:
隨機 - RandomRule
輪循 - RoundRobinRule
權重響應時間 - WeightedResponseTimeRule
WeightedResponseTimeRule
以外,其餘的 Ribbon 負載均衡實現均沒有提供權重因子,而權重因子對於藍綠髮布、服務預熱等方面的幫助是相當重要的。所以,權重因子在 Dubbo 「隨機「、」輪詢「 以及 」最少活躍調用數「 負載均衡算法中均體現。
以上討論的兩種框架均屬於 Java 實現,而中間的 Kong 則是更爲通用的實現,一般它做爲 API 服務網關,後面咱們將繼續討論。可簡單地認爲它是 Nginx + Lua 的擴展,負載均衡天然成爲不可或缺的特性。其默認的負載均衡算法爲具有權重的輪詢(weighted-round-robin),同時一致性 Hash 算法做爲可選方案。
談及服務網關,Java 工程師最容易想到的是 Spring Cloud Zuul。Zuul 是 Netflix 基於 Servlet API 開發的 Web 服務代理組件,在 Spring Cloud 使用場景中,它與 Eureka 和 Ribbon 整合,打造具有服務動態更新和負載均衡能力的服務網關。
最近,隨着 Spring Cloud Finchley 的發佈,Spring Cloud Zuul 的替代方案 Spring Cloud Gateway 孕育而生,不過官方的描述仍是比較謙虛謹慎,並無一刀切地引導開發人員從 Zuul 遷移到 Gateway 上來:
API Gateway built on top of the Spring Ecosystem, including: Spring 5, Spring Boot 2 and Project Reactor. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency.
二者不一樣點在於,Zuul 運行在 Servlet 容器中,而 Gateway 並不像 Spring WebFlux 可以兼容 Servlet 3.1 運行時,而是必須依賴 Netty 的運行時,以及整合 Reactive 框架 Reactor,實現異步非阻塞網關。因爲近期對於 Spring 5 WebFlux 可以大幅提高應用性能的觀點甚囂塵上,實際上,沒有任何直接性能基準測試證實 WebFlux 可以加快程序執行速度,或許你們認爲個人觀點與主流各個不入,但是我要告訴你們的是,這個問題我在同事間驗證過不少次,大多數狀況,Reactive 幾乎沒有提高性能。就連 Spring 官方也認可這個觀點:
1.1.7. Performance vs scale
Performance has many characteristics and meanings. Reactive and non-blocking generally do not make applications run faster. They can, in some cases, for example if using the
WebClient
to execute remote calls in parallel. On the whole it requires more work to do things the non-blocking way and that can increase slightly the required processing time.資源地址:https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/web-reactive.html#webflux-performance
同時,這裏提供一篇 Spring 5 WebFlux: Performance tests 的文章,在結尾部分給出告終論,做者坦言在速度上沒有明顯的提高,甚至從結果來看,速度稍微更糟糕:
No improvement in speed was observed with our reactive apps (the Gatling results are even slightly worse).
以上測試工程和結論是由開源項目 JHipster 的工程師給出,具有必定的客觀性和可信度。
資源地址:https://blog.ippon.tech/spring-5-webflux-performance-tests/
換言之,基於 Reactor 開發的 Gateway 在性能可能並無明顯的提高。所以,Zuul 和 Gateway 的性能對比則演變爲 Servlet 容器和 Netty Web 容器的比較,感興趣的朋友能夠去網上尋找一些比較數據,二者的性能在伯仲間。
固然,我和在座的各位同樣,對 Java 的實現方案天然是情有獨鍾。然而我想說的是,身爲 Java 工程師,眼中不免有 Java,可是眼中不要只有 Java。Nginx 做爲當年著名 「C10K」 問題的解決方案,不管從鏈接數量,仍是資源消耗方面均優於 Java 實現。做爲技術人,應該具備更爲寬廣的胸懷,接納非我族類的氣魄,該放手的時候就放手。Nginx 做爲服務網關不失爲一種好的方案,然而它的動態性略爲不足,須要結合 Lua 腳本輔助完成,所以,OpenResty 和 Kong 這類方案脫穎而出。若是就 HTTP API 網關而言,我的認爲 Kong 的方案更佳,由於它提供完整的解決方案,包括前面討論的負載均衡(權重)、服務熔斷以及服務發現等特性。相似的特性在 CNCF 項目 Envoy 也有體現,它是另外一種高性能代理的方案,提供服務發現、健康和負載均衡。在協議上,自然支持 HTTP 和 HTTP/2,而通信協議支持 gRPC,建議你們予以高度關注。
值得一提的是,HTTP API 網關一般須要支持 sidecar,換言之,支撐網關服務的基礎設施必須提供服務發現的能力,就功能性而言,Zuul 和 Gateway 自身並不具有這樣的特性,須要搭配 Eureka 這樣組件,它們更像服務路由器的角色。
左邊和中間的四種技術均爲 Spring Cloud 分佈式配置的底層存儲,其中 Git 爲版本式配置,而 JDBC 是從 Spring Cloud Edgware 版本開始支持,提供更爲通用和動態的配置源。這裏咱們又見到 Zookeeper 的聲影,從簡化運維的角度,能夠利用 Zookeeper 即承擔服務發現,也做爲分佈式配置的基礎設施。而最右邊的 etcd 是最近很是火的 Kubernetes 分佈式配置的 key-value 存儲,提供快速、簡單、安全和可高的解決方案。
服務熔斷也很是讓開發人員聯想到 Spring Cloud Hystrix 技術,不過 Hystrix 並不是與 Spring Cloud 強耦合,固然 Dubbo 也能結合 Netflix Hystrix 框架提供服務熔斷的能力,後面部分將介紹 Dubbo 與 Hystrix 整合,提高 Dubbo 服務熔斷的能力。確切地說,Dubbo 所提供的能力是集羣容錯,包括 Failover 等模式。 Kong 也自然地支持服務熔斷的能力,因此它做爲 API 網關的特性是全面的。
以上鍊路跟蹤的基礎設施從左至右,分別爲 Zipkin、OpenTracing 以及 Jaeger,三者的靈感均來自於 Google 論文 Dapper。相對而言,Java 程序員可能更爲熟悉 Zipkin,由於它是 Spring Cloud Sleuth 首選方案,提供客戶端上報以及服務端聚合和 Dashboard 等功能。而 OpenTracing 和 Jaeger 是 CNCF 孵化項目,前者屬於開放的標準,提供多語言的適配實現,後者則由 Uber(優步)公司開發並開源的鏈路跟蹤項目,功能上與 Zipkin 相似,不過它基於 GO 語言開發,同時也提供 Java 客戶端。
OpenTracing 官網:http://opentracing.io/ jaeger 官網:https://www.jaegertracing.io/
服務監控與鏈路跟蹤有所區別,主要用於監控應用系統或業務的指標數據,多是健康閾值,如 CPU 或 內存使用率,也能夠是業務指標,如最近一小時的用戶登陸量。一般採用 Metrics 方式暴露,可以使用客戶端推送或服務端拉取的方式傳輸 Metrics 信息到數據中心。一般 Metrics 數據與時間是存在對應關係,所以,基本上採用時序型數據庫來存儲,如圖中的 OpenTSDB。一般,Java 微服務應用會選擇 Spring Boot 框架做爲基礎設施,如我以前設計的監控架構就採用了 Spring Boot + OpenTSDB ,後端存儲基於 HBase。當時 Spring Boot Actuator Metrics 僅爲簡單的 Key Value 形式,天然 OpenTSDB 是理想的選擇。隨着 Spring Boot 2.0 開始支持 Micrometer 以後,使得 Spring Boot 的應用可以整合更多的 Micrometer 適配方案,其中名氣較大的就是圖中間的 Prometheus,它一樣也是 CNCF 的孵化項目。
固然服務監控不僅是 Metrics 方式,我所知道國內很多的公司採用了日誌收集的方案,並搭配 ELK(Elasticsearch, Logstash, Kibana) 架構,減小運維成本。假設您沒有使用該方案,或者僅使用了 Elasticsearch 的話,不管哪一種方案,圖形化界面的監控是必不可少的,所以我推薦 Grafana,該項目可以支持多種數據源,包括前文提到的 OpenTSDB、Prometheus 以及 ElasticSearch 等。由此,從數據採集、上報、聚合以及展現的特性上,這些基礎設施幫助 Cloud Native 應用構建服務監控的閉環。
本議程介紹了一些 Cloud Native 技術設施,接下里咱們繼續討論 Cloud Native 架構選型。
CNCF 體系做爲目前最熱門的架構選型之一,基本上圍繞着 Kubernetes 爲中心而構建。我的認爲,Java 業界和 CNCF 體系並無達成共識,如服務網關,CNCF 主打 Envoy,而 Java 主要的方案爲 Zuul 和 Spring Cloud Gateway。所以,我的建議是密切的關注 CNCF 的發展,不過個別孵化項目能夠先行,如 Prometheus 和 Jaeger 等。 至於 CNCF 與 Java 生態的整合和落地,還得有待時日。
實際上,這個圖片並不是 Spring Cloud 組件架構,而是將其整合在 Pivotal Cloud Foundry (PCF) 架構中。基本上,Spring Cloud 功能組件均有所體現,包括 Eureka、Hystrix、Ribbon 等。不過值得注意的是,Spring Cloud Stream 是一套較爲完整和抽象的流式編程框架,屏蔽了底層傳輸介質(不只是消息服務),如 Kafka、RabbitMQ 等。除此以外,其餘的組件可圈可點,如 Eureka 在大規模運用中的卡頓問題、Ribbon 缺乏權重、Zuul 鏈接數限制和資源消耗、服務調用受限於 Feign REST 協議限制等。若是在小規模場景使用,以上限制或問題不明顯,能夠說 Spring Cloud 徹底可以適任。
不過,差很少兩年前,我曾在不一樣的公開場合講過:」Spring Boot 易學難精,Spring Cloud 能用但不成熟「。當時不少人以爲我「離經叛道」,然而這句話並不是空穴來風,是我這幾年來 Java 微服務架構實施的心得。這兩年來,深受 Spring Cloud 「折磨」的小夥伴逐漸覺醒,慢慢地開始回到 Dubbo 等技術方案。如 Martin Fowler 在爲「微服務」下定義時,提到通信協議要用輕量級的 REST。假設微服務要作到服務無關的話,那麼 Web Services 協議也是能夠,儘管它看起來比較重,不過 Web Services 的結構化和強類型,能夠省去很多的運行時校驗邏輯。在我看來,微服務更大程度應該體如今服務粒度上,誠如 Netflix 前架構師 Adrian Cockcroft 說言:「Fine grain SOA」(微服務就是細粒度的 SOA),就這一點而言,比較容易地和業界達成共識。當咱們把 Martin 的話視如圭臬時,咱們是否要思考它是否經得起工程檢驗。這裏,我沒有興趣貶低他人,來擡高本身(Dubbo),從而引導讓你們放棄 Spring Cloud,而是咱們須要給 Spring Cloud 時間,包括將來 Dubbo 也會向 Spring Cloud 靠攏並整合。在阿里的內部,基於 Nacos(立刻開源的項目)和 Apache RocketMQ,實現了 Spring Cloud Service Discovery、Config 以及 Stream 等整合和適配,一旦時機成熟,可能會開源與你們共建。
既然談到了 Dubbo,下面咱們再來討論 Dubbo 的架構體系。
編程模型方面,不但支持傳統的 Spring XML 配合方式,已經實現註解驅動以及外部化配置,而且全面支持最新的 Spring Boot 2.0,在不久的將來,你們會看到 Dubbo 與 Spring Cloud 的整合,使開發人員無縫地銜接已有的 Spring Cloud 應用。
Dubbo Spring Boot 項目地址:https://github.com/apache/incubator-dubbo-spring-boot-project
註冊中心方面,Dubbo 將整合 Eureka、etcd 以及 Consul 基礎設施,深度與業界熱門方案整合。
熔斷機制方面,Dubbo 會在近期發佈 Hystrix 整合實現,將編程友好性作得最大化。
通信協議方面,Dubbo 將會支持 gRPC、Thrift 等熱門通信協議。
至於序列化協議,天然首先考慮的是 Protobuf,因其高層 gRPC 搭配 HTTP/2 快成或已經成爲下一代通信協議的事實標準,使得任何人沒法忽視它們的存在。固然其餘協議也會陸續支持。
其餘方面,我這裏就不一一介紹,總之,如今 Dubbo 已再也不只是一個單一的 PRC 框架,而是要擁抱業界,造成完整的生態體系,與業界造成最大公約數。
在 Dubbo 架構體系時,咱們曾提到編程模型的變化。從 Dubbo 2.5.8
開始,註解驅動和外部化配置均已獲得支持。同時,Dubbo 已經合併 Dubbox 代碼,Java JAX-RS 標準獲得了支持,目前業界事實的 REST 標準 Spring Web MVC 正在同步開發。Reactive 的支持也在同步進行,小馬哥還得友好地提醒一下各位,對於 Reactive 的指望不該該過度的關注性能的提高。
在 Dubbo 2.5.7
以前的版本 ,Dubbo 提供了兩個核心註解 @Service
以及 @Reference
,分別用於Dubbo 服務提供和 Dubbo 服務引用。
其中, @Service
做爲 XML 元素 <dubbo:service>
的替代註解,與 Spring Framework @org.springframework.stereotype.Service
相似,用於服務提供方 Dubbo 服務暴露。與之相對應的 @Reference
,則是替代 <dubbo:reference>
元素,相似於 Spring 中的 @Autowired
。
2.5.7
以前的Dubbo,與早期的 Spring Framework 2.5 存在相似的不足,即註解支持不夠充分。註解須要和 XML 配置文件配合使用,以下所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/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-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="annotation-provider"/>
<dubbo:registry address="127.0.0.1:4548"/>
<dubbo:annotation package="com.alibaba.dubbo.config.spring.annotation.provider"/>
</beans>
不只如此,當時的版本存在「 @Service
Bean 不支持 Spring AOP」 以及 「 @Reference
不支持字段繼承性」 等問題。
從 2.5.7
開始,Dubbo 開始引入組件掃描 Annotation @DubboComponentScan
,借鑑了 Spring Boot 1.3 引入的 @ServletComponentScan
。
在職責上, @DubboComponentScan
相對於 Spring Boot @ServletComponentScan
更爲繁重,緣由在於處理 Dubbo @Service
類暴露 Dubbo 服務外,還有幫助 Spring Bean @Reference
字段或者方法注入 Dubbo 服務代理。
在場景上,Spring Framework @ComponentScan
組件掃描邏輯更爲複雜。而在 @DubboComponentScan
只需關注 @Service
和 @Reference
處理。
注:更多 Dubbo 註解驅動的詳情,請參考《Dubbo 註解驅動(Annotation-Driven)》
@DubboComponentScan
服務端示例假設,服務提供方和服務消費分均依賴服務接口 DemoService
:
package com.alibaba.dubbo.demo;
public interface DemoService {
String sayHello(String name);
}
服務提供方實現 DemoService
- AnnotationDemoService
同時標註 Dubbo @Service
:
package com.alibaba.dubbo.demo.provider;
import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.dubbo.demo.DemoService;
/**
* Annotation {@link DemoService} 實現
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Service
public class AnnotationDemoService implements DemoService {
@Override
public String sayHello(String name) {
return "Hello , " + name;
}
}
服務端 @Configuration
Class
package com.alibaba.dubbo.demo.config;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 服務提供方配置
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Configuration
@DubboComponentScan("com.alibaba.dubbo.demo.provider") // 掃描 Dubbo 組件
public class ProviderConfiguration {
/**
* 當前應用配置
*/
@Bean("dubbo-annotation-provider")
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-annotation-provider");
return applicationConfig;
}
/**
* 當前鏈接註冊中心配置
*/
@Bean("my-registry")
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("N/A");
return registryConfig;
}
/**
* 當前鏈接註冊中心配置
*/
@Bean("dubbo")
public ProtocolConfig protocolConfig() {
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(12345);
return protocolConfig;
}
}
服務提供方引導類
package com.alibaba.dubbo.demo.bootstrap;
import com.alibaba.dubbo.demo.DemoService;
import com.alibaba.dubbo.demo.config.ProviderConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 服務提供方引導類
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class ProviderBootstrap {
public static void main(String[] args) {
// 建立 Annotation 配置上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 註冊配置 Bean
context.register(ProviderConfiguration.class);
// 啓動上下文
context.refresh();
// 獲取 DemoService Bean
DemoService demoService = context.getBean(DemoService.class);
// 執行 sayHello 方法
String message = demoService.sayHello("World");
// 控制檯輸出信息
System.out.println(message);
}
}
@DubboComponentScan
客戶端示例消費服務 DemoService
package com.alibaba.dubbo.demo.consumer;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.demo.DemoService;
/**
* Annotation 驅動 {@link DemoService} 消費方
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class AnnotationDemoServiceConsumer {
@Reference(url = "dubbo://127.0.0.1:12345")
private DemoService demoService;
public String doSayHell(String name) {
return demoService.sayHello(name);
}
}
消費端 @Configuration
Class
與服務提供方配置相似,服務消費方也許 Dubbo 相關配置 Bean - ConsumerConfiguration
package com.alibaba.dubbo.demo.config;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;
import com.alibaba.dubbo.demo.consumer.AnnotationDemoServiceConsumer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 服務消費方配置
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
@Configuration
@DubboComponentScan
public class ConsumerConfiguration {
/**
* 當前應用配置
*/
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-annotation-consumer");
return applicationConfig;
}
/**
* 當前鏈接註冊中心配置
*/
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("N/A");
return registryConfig;
}
/**
* 註冊 AnnotationDemoServiceConsumer,@DubboComponentScan 將處理其中 @Reference 字段。
* 若是 AnnotationDemoServiceConsumer 非 Spring Bean 的話,
* 即便 @DubboComponentScan 指定 package 也不會進行處理,與 Spring @Autowired 同理
*/
@Bean
public AnnotationDemoServiceConsumer annotationDemoServiceConsumer() {
return new AnnotationDemoServiceConsumer();
}
}
服務消費方引導類
package com.alibaba.dubbo.demo.bootstrap;
import com.alibaba.dubbo.demo.config.ConsumerConfiguration;
import com.alibaba.dubbo.demo.config.ProviderConfiguration;
import com.alibaba.dubbo.demo.consumer.AnnotationDemoServiceConsumer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 服務消費端引導類
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
*/
public class ConsumerBootstrap {
public static void main(String[] args) {
// 啓動服務提供方上下文
startProviderContext();
// 啓動而且返回服務消費方上下文
ApplicationContext consumerContext = startConsumerContext();
// 獲取 AnnotationDemoServiceConsumer Bean
AnnotationDemoServiceConsumer consumer = consumerContext.getBean(AnnotationDemoServiceConsumer.class);
// 執行 doSayHello 方法
String message = consumer.doSayHello("World");
// 輸出執行結果
System.out.println(message);
}
/**
* 啓動而且返回服務消費方上下文
*
* @return AnnotationConfigApplicationContext
*/
private static ApplicationContext startConsumerContext() {
// 建立服務消費方 Annotation 配置上下文
AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();
// 註冊服務消費方配置 Bean
consumerContext.register(ConsumerConfiguration.class);
// 啓動服務消費方上下文
consumerContext.refresh();
// 返回服務消費方 Annotation 配置上下文
return consumerContext;
}
/**
* 啓動服務提供方上下文
*/
private static void startProviderContext() {
// 建立 Annotation 配置上下文
AnnotationConfigApplicationContext providerContext = new AnnotationConfigApplicationContext();
// 註冊配置 Bean
providerContext.register(ProviderConfiguration.class);
// 啓動服務提供方上下文
providerContext.refresh();
}
}
在Dubbo 註解驅動例子中,不管是服務提供方,仍是服務消費方,均須要轉配相關配置Bean:
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-annotation-consumer");
return applicationConfig;
}
雖然實現相似於 ProviderConfiguration
和 ConsumerConfiguration
這樣的 Spring @Configuration
Bean 成本並不高,不過經過 Java Code 的方式定義配置 Bean,或多或少是一種 Hard Code(硬編碼)的行爲,缺乏彈性。
儘管在 Spring 應用中,能夠經過 @Value
或者 Environment
的方式獲取外部配置,其代碼簡潔性以及類型轉換靈活性存在明顯的不足。所以,Spring Boot 提出了外部化配置(External Configuration)的感念,即經過程序之外的配置源,動態地綁定指定類型。
隨着 Spring Boot / Spring Cloud 應用的流行,開發人員逐漸地接受而且使用 Spring Boot 外部化配置(External Configuration),即經過 application.properties
或者 bootstrap.properties
裝配配置 Bean。
下列表格記錄了 Dubbo 內置配置類:
配置類 | 標籤 | 用途 | 解釋 |
---|---|---|---|
ProtocolConfig |
<dubbo:protocol/> |
協議配置 | 用於配置提供服務的協議信息,協議由提供方指定,消費方被動接受 |
ApplicationConfig |
<dubbo:application/> |
應用配置 | 用於配置當前應用信息,無論該應用是提供者仍是消費者 |
ModuleConfig |
<dubbo:module/> |
模塊配置 | 用於配置當前模塊信息,可選 |
RegistryConfig |
<dubbo:registry/> |
註冊中心配置 | 用於配置鏈接註冊中心相關信息 |
MonitorConfig |
<dubbo:monitor/> |
監控中心配置 | 用於配置鏈接監控中心相關信息,可選 |
ProviderConfig |
<dubbo:provider/> |
提供方配置 | 當 ProtocolConfig 和 ServiceConfig 某屬性沒有配置時,採用此缺省值,可選 |
ConsumerConfig |
<dubbo:consumer/> |
消費方配置 | 當 ReferenceConfig 某屬性沒有配置時,採用此缺省值,可選 |
MethodConfig |
<dubbo:method/> |
方法配置 | 用於 ServiceConfig 和 ReferenceConfig 指定方法級的配置信息 |
ArgumentConfig |
<dubbo:argument/> |
參數配置 | 用於指定方法參數配置 |
經過申明對應的 Spring 擴展標籤,在 Spring 應用上下文中將自動生成相應的配置 Bean。
在 Dubbo 官方用戶手冊的「屬性配置」章節中, dubbo.properties
配置屬性可以映射到ApplicationConfig
、 ProtocolConfig
以及 RegistryConfig
的字段。從某種意義上來講, dubbo.properties
也是 Dubbo 的外部化配置。
注:更多外部化配置的詳情,請參考《Dubbo 外部化配置(Externalized Configuration)》
本環境爲分享後部分,如今編碼 + 演示環境,當前文字僅提供代碼實現。
本示例出如今分享議程的代碼演示,將其放置此處,方便閱讀理解
實現 HystrixCommand
public class ResultHystrixCommand extends HystrixCommand<Result> {
private final Invoker<?> invoker;
private final Invocation invocation;
public ResultHystrixCommand(Invoker<?> invoker, Invocation invocation) {
super(HystrixCommandGroupKey.Factory.asKey(
"ResultHystrixCommand"),
100); // 設置超時時間
// 關聯 Dubbo Invoker 和 Invocation
this.invoker = invoker;
this.invocation = invocation;
}
@Override
protected Result run() throws Exception {
// 遠程方法調用執行
return invoker.invoke(invocation);
}
}
當目標方法執行時間超過 100 ms 時,觸發熔斷,並拋出 newUnsupportedOperationException("No fallback available.")
。
Dubbo Filter
整合 ResultHystrixCommand
@Activate(group = Constants.CONSUMER, value = "hystrix") // 命名當前 Filter 爲 "hystrix"
public class HystrixFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
return new ResultHystrixCommand(invoker, invocation).execute();
}
}
建立並配置 Filter
SPI 配置文件
在相對於 ClassPath 資源 META-INF/dubbo/
下建立 com.alibaba.dubbo.rpc.Filter
,並配置以下:
hystrix=com.alibaba.boot.dubbo.demo.consumer.filter.HystrixFilter
配置 @Reference
filter
屬性
@RestController
public class DemoConsumerController {
@Reference(
version = "${demo.service.version}",
application = "${dubbo.application.id}",
url = "dubbo://localhost:12345",
filter = "hystrix" // 指向 HystrixFilter 實現
)
private DemoService demoService;
@RequestMapping("/sayHello")
public String sayHello(@RequestParam String name) {
return demoService.sayHello(name);
}
}
服務提供者實現
@Service(
version = "${demo.service.version}",
application = "${dubbo.application.id}",
protocol = "dubbo",
registry = "${dubbo.registry.id}"
)
public class DefaultDemoService implements DemoService {
private final Random random = new Random();
public String sayHello(String name) {
hold();
return "Say : Hello, " + name + " (from Spring Boot)";
}
private void hold() { // 隨機等待 < 200 ms,當時間超過 100 ms 時,觸發客戶端熔斷
long time = random.nextInt(200);
System.out.println("To hold " + time + " ms!");
try {
Thread.sleep(time);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
依次啓動服務端 和客戶端 Spring Boot 應用,
執行 curl
命令
mercyblitz$ curl http://localhost:8080/sayHello?name=Hello
測試結果
{"timestamp":"2018-06-23T01:33:58.682+0000","status":500,"error":"Internal Server Error","message":"ResultHystrixCommand timed-out and no fallback available.","path":"/sayHello"}
運行結果說明服務端方法執行超過 100 ms,引發客戶端熔斷。
(EOF)
Dubbo 官網:https://dubbo.apache.org/
Dubbo 工程:https://github.com/apache/incubator-dubbo
Dubbo Spring Boot 工程:https://github.com/apache/incubator-dubbo-spring-boot-project
CNCF Landscape:https://landscape.cncf.io/
Spring Cloud 官網:https://projects.spring.io/spring-cloud/
Kong 社區官網:https://konghq.com/kong-community-edition/
Opentracing 官網:http://opentracing.io/
Jaeger 官網:https://www.jaegertracing.io/
Prometheus 官網:https://prometheus.io/
OpenTsdb 官網:http://opentsdb.net/
Grafana 官網:https://grafana.com/
小馬哥 Github:https://github.com/mercyblitz