成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Dubbo Cloud Native 實(shí)踐與思考

邱勇 / 750人閱讀

摘要:可簡單地認(rèn)為它是的擴(kuò)展,負(fù)載均衡自然成為不可或缺的特性。類似的特性在項(xiàng)目也有體現(xiàn),它是另一種高性能代理的方案,提供服務(wù)發(fā)現(xiàn)健康和負(fù)載均衡。

Dubbo Cloud Native 實(shí)踐與思考 分享簡介

Cloud Native 應(yīng)用架構(gòu)隨著云技術(shù)的發(fā)展受到業(yè)界特別重視和關(guān)注,尤其是 CNCF(Cloud Native Computing Foundation)項(xiàng)目蓬勃發(fā)展之際。Dubbo 作為服務(wù)治理的標(biāo)志性項(xiàng)目,自然緊跟業(yè)界的潮流,擁抱技術(shù)的變化。本次分享的議題包括介紹 Apache 孵化項(xiàng)目Dubbo Spring Boot Project 以及匯報 Dubbo 與 Cloud Native 整合過程中的一些實(shí)踐與思考,如適配 Spring Cloud 、服務(wù)發(fā)現(xiàn)、服務(wù)網(wǎng)關(guān)、服務(wù)跟蹤以及監(jiān)控等。

注:為了讀者的閱讀方便和習(xí)慣,本文字稿將在演講內(nèi)容的基礎(chǔ)上做出適當(dāng)?shù)恼{(diào)整。
自我介紹

小馬哥(微信:mercyblitz),一線互聯(lián)網(wǎng)技術(shù)專家,十余年 Java EE 從業(yè)經(jīng)驗(yàn),Dubbo 維護(hù)者、架構(gòu)師以及微服務(wù)布道師。目前主要負(fù)責(zé)集團(tuán)微服務(wù)技術(shù)實(shí)施、架構(gòu)衍進(jìn)、基礎(chǔ)設(shè)施構(gòu)建等。重點(diǎn)關(guān)注云計(jì)算、微服務(wù)以及軟件架構(gòu)等領(lǐng)域。通過 SUN Java(SCJP、SCWCD、SCBCD)以及 Oracle OCA 等的認(rèn)證。

「小馬哥 Java 星球」 https://t.zsxq.com/72rj2rr

SF 直播:Java 微服務(wù)實(shí)踐 - Spring Boot / Spring Cloud

主要議程

今天我非常榮幸地與大家一起討論關(guān)于 Dubbo Cloud Native 相關(guān)議題,本次議題緊扣“實(shí)踐與思考“兩個關(guān)鍵字,主要的議程包括:

Cloud Native 基礎(chǔ)設(shè)施

Cloud Native 架構(gòu)選型

Dubbo Cloud Native 準(zhǔn)備

Cloud Native 基礎(chǔ)設(shè)施

關(guān)于 Cloud Native 的定義,不同的云平臺可能給出的內(nèi)容存在差異。此處,我向大家介紹目前最熱門的 CNCF 的定義:

”CNCF Cloud Native Definition v1.0“ 中的描述:

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.

相對于其他學(xué)術(shù)流派,CNCF 的 Cloud Native 定義更為具體,偏向于軟件技術(shù)。這一點(diǎn)我們從文中的一些關(guān)鍵字能夠明顯地體會到,如關(guān)鍵字 "Containers(容器)"、"service meshes"、”microservices(微服務(wù))“等。通常,開發(fā)人員較為關(guān)注的 Cloud Native 基礎(chǔ)設(shè)施為:“服務(wù)發(fā)現(xiàn)”、“負(fù)載均衡”、“服務(wù)網(wǎng)關(guān)”、“分布式配置”、“服務(wù)熔斷”以及“跟蹤監(jiān)控”,如圖所示:

由于 PPT 格式的限制,此處我將“鏈路跟蹤”與“服務(wù)監(jiān)控” 并陳為“跟蹤監(jiān)控”。接下來,我們進(jìn)入“服務(wù)發(fā)現(xiàn)”的討論。

服務(wù)發(fā)現(xiàn)(Service Discovery )

隨著微服務(wù)架構(gòu)(MSA)受到不同規(guī)模企業(yè)的青睞,服務(wù)治理的實(shí)施逐漸被提上基礎(chǔ)設(shè)施改造的議程。盡管這些概念在 SOA 時代已經(jīng)提出,然而引起業(yè)界廣泛關(guān)注應(yīng)歸功于微服務(wù)。服務(wù)發(fā)現(xiàn)(Service Discovery )作為服務(wù)治理的核心特性,通常也將服務(wù)注冊(Service Registration)一并討論。無論是服務(wù)發(fā)現(xiàn),還是服務(wù)注冊,在具體落地實(shí)施時,它們必須面對技術(shù)選型的問題。在座的各位,包括我,大多數(shù)是 Java 程序員,自然關(guān)心 Java 的技術(shù)方案。目前,Java 社區(qū)最為津津樂道的方案莫過于 Spring Cloud,搭配 Netflix OSS 組件 Eureka,幫助 Spring Boot 應(yīng)用快速搭建服務(wù)發(fā)現(xiàn)體系。其中,Eureka Server 作為注冊中心服務(wù)器,Spring Boot 應(yīng)用整合 Eureka Client 向 Eureka Server 注冊。實(shí)際上,Spring Cloud 除了整合 Netflix Eureka 作為服務(wù)發(fā)現(xiàn)之外,還提供了 Apache Zookeeper 和 HachiCorp Consul 的實(shí)現(xiàn),所以這三種方案出現(xiàn)在當(dāng)前頁面:

其中還包括 Redis 和 Apache Curator,前者是 Dubbo 的服務(wù)發(fā)現(xiàn)實(shí)現(xiàn)方案之一,然而小馬哥并不建議使用 Redis 作為注冊中心,還是保持它緩存中間件的單純性較好。而 Curator 作為 Zookeeper Java 客戶端類庫,它不但可用在 Dubbo,而且其擴(kuò)展項(xiàng)目 Curator Service Discovery 也是 Spring Cloud 整合 Zookeeper 作為服務(wù)發(fā)現(xiàn)的關(guān)鍵基礎(chǔ)設(shè)施?;蛟S大家思考以上方案應(yīng)該如何選型的問題。

如何選擇
Eureka

當(dāng)服務(wù)發(fā)現(xiàn)選型時,Netflix Eureka 或許是在開發(fā)人員腦海中復(fù)現(xiàn)的首選方案。然而 Eureka 在阿里大規(guī)模實(shí)踐時,它的表現(xiàn)并不理想,當(dāng) Eureka 客戶端服務(wù)實(shí)例數(shù)量達(dá)到一定時,Eureka Server 時常會出現(xiàn)服務(wù)不可用的情況,主要的問題集中在更新(Update)機(jī)制、復(fù)制(Replication)機(jī)制以及內(nèi)存型存儲。由于時間的關(guān)系,此處我不加詳細(xì)說明,部分答案在 Eureka Wiki Eureka 2.0 Motivations 中也有描述:

Why Eureka 2.0?

Only support homogenous client views

Only supports scheduled updates

Replication algorithm limits scalability

注:以上具體內(nèi)容在分享現(xiàn)場并沒有具體提及,此處特意為讀者補(bǔ)充。

以上問題 Netflix 早在 2015 年已意識到,然而 Eureka 2.0 的發(fā)布遙遙無期。后來,我托朋友聯(lián)系上了 Netflix 的工程師,咨詢他們關(guān)于 Eureka 1 在自身生產(chǎn)環(huán)境的使用情況。他們的回復(fù)是部分場景在使用。這樣的答復(fù)值得玩味,再細(xì)問其覆蓋比重,對方三緘其口。這不得不讓我對 Eureka 的成熟度產(chǎn)生了質(zhì)疑,所以我不建議大家在數(shù)以千計(jì)的應(yīng)用實(shí)例場景中使用。

Consul

Consul 同樣作為 Spring Cloud 服務(wù)中心,基于 GO 語言開發(fā),其數(shù)據(jù)一致性采用 Raft 算法,低內(nèi)存,集群支持。曾一度成為我理想的替換 Eureka 的方案,不過本人并不具備 Consul 的大規(guī)模運(yùn)用,為此還特意請教永輝云創(chuàng)的架構(gòu)師翟永超(《Spring Cloud 微服務(wù)實(shí)戰(zhàn)》的作者)。他告知 Consul 表現(xiàn)不錯,并在跨 DC(數(shù)據(jù)中心)方面也比較穩(wěn)定:

他的答復(fù)讓我增強(qiáng)了 Consul 的信心,稍顯遺憾的是其 Consul 應(yīng)用節(jié)點(diǎn)略少。后來,我聽說 B 站的哥們自研服務(wù)發(fā)現(xiàn)中間件 discovery,他們應(yīng)該也對 Consul 做過調(diào)研和評估,他們的看法是:

Github 開源地址:https://github.com/Bilibili/d...

discovery 在 B 站 K8S 上的使用情況:

綜合兩家公司的評估,盡管沒有經(jīng)過本人實(shí)際操作,并且兩者沒有提供具體的數(shù)據(jù)指標(biāo),然而在一定程度上說明 Consul 作為注冊中心的實(shí)例節(jié)點(diǎn)規(guī)模大概在 2k 以內(nèi)。換言之,它比較適合中小型企業(yè)。

Zookeeper

Zookeeper 即可是 Spring Cloud 注冊中心,又能作為 Dubbo 注冊中心,與 Eureka 不同,它屬于 CP 分布式策略,而后者屬于 AP。兩者的共同點(diǎn)在于均屬于內(nèi)存型注冊中心,在大規(guī)模集群場景,也會遇到 Eureka 類似的問題。不過從運(yùn)維的角度,相較于 Eureka 而言,熟悉 Zookeeper 運(yùn)維朋友更多。在生態(tài)性方面,Zookeeper 周邊的生態(tài)更豐富,如 Zookeeper C API,盡管 Eureka 提供了語言無關(guān)性的 REST 接口。同時,Zookeeper 還從當(dāng)配置服務(wù)器的角色,降低了學(xué)習(xí)的成本。綜上結(jié)論,我推薦使用 Zookeeper 作為服務(wù)發(fā)現(xiàn)基礎(chǔ)設(shè)施,無論您選擇 Dubbo 方案,還是使用 Spring Cloud。盡管它在大規(guī)模集群時也出現(xiàn) Zookeeper 間歇性卡頓等問題。

負(fù)載均衡

負(fù)載均衡是第二個重要 Cloud Native 基礎(chǔ)設(shè)施,熟悉 Spring Cloud 的朋友一定對右側(cè)的蝴蝶結(jié)有印象,它就是 Netflix OSS 負(fù)載均衡組件 Ribbon,框架層面提供了多種負(fù)載均衡規(guī)則,如:

隨機(jī) - RandomRule

輪循 - RoundRobinRule

權(quán)重響應(yīng)時間 - WeightedResponseTimeRule

WeightedResponseTimeRule 之外,其他的 Ribbon 負(fù)載均衡實(shí)現(xiàn)均沒有提供權(quán)重因子,而權(quán)重因子對于藍(lán)綠發(fā)布、服務(wù)預(yù)熱等方面的幫助是至關(guān)重要的。因此,權(quán)重因子在 Dubbo “隨機(jī)“、”輪詢“ 以及 ”最少活躍調(diào)用數(shù)“ 負(fù)載均衡算法中均體現(xiàn)。

以上討論的兩種框架均屬于 Java 實(shí)現(xiàn),而中間的 Kong 則是更為通用的實(shí)現(xiàn),通常它作為 API 服務(wù)網(wǎng)關(guān),后面我們將繼續(xù)討論??珊唵蔚卣J(rèn)為它是 Nginx + Lua 的擴(kuò)展,負(fù)載均衡自然成為不可或缺的特性。其默認(rèn)的負(fù)載均衡算法為具備權(quán)重的輪詢(weighted-round-robin),同時一致性 Hash 算法作為可選方案。

服務(wù)網(wǎng)關(guān)

談及服務(wù)網(wǎng)關(guān),Java 工程師最容易想到的是 Spring Cloud Zuul。Zuul 是 Netflix 基于 Servlet API 開發(fā)的 Web 服務(wù)代理組件,在 Spring Cloud 使用場景中,它與 Eureka 和 Ribbon 整合,打造具備服務(wù)動態(tài)更新和負(fù)載均衡能力的服務(wù)網(wǎng)關(guān)。

最近,隨著 Spring Cloud Finchley 的發(fā)布,Spring Cloud Zuul 的替代方案 Spring Cloud Gateway 孕育而生,不過官方的描述還是比較謙虛謹(jǐn)慎,并沒有一刀切地引導(dǎo)開發(fā)人員從 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.

兩者不同點(diǎn)在于,Zuul 運(yùn)行在 Servlet 容器中,而 Gateway 并不像 Spring WebFlux 能夠兼容 Servlet 3.1 運(yùn)行時,而是必須依賴 Netty 的運(yùn)行時,以及整合 Reactive 框架 Reactor,實(shí)現(xiàn)異步非阻塞網(wǎng)關(guān)。由于近期對于 Spring 5 WebFlux 能夠大幅提升應(yīng)用性能的觀點(diǎn)甚囂塵上,實(shí)際上,沒有任何直接性能基準(zhǔn)測試證明 WebFlux 能夠加快程序執(zhí)行速度,或許大家認(rèn)為我的觀點(diǎn)與主流格格不入,可是我要告訴大家的是,這個問題我在同事間驗(yàn)證過很多次,大多數(shù)情況,Reactive 并不沒有提升性能。就連 Spring 官方也承認(rèn)這個觀點(diǎn):

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...

同時,這里提供一篇 Spring 5 WebFlux: Performance tests 的文章,在結(jié)尾部分給出了結(jié)論,作者坦言在速度上沒有明顯的提升,甚至從結(jié)果來看,速度稍微更糟糕:

No improvement in speed was observed with our reactive apps (the Gatling results are even slightly worse).

以上測試工程和結(jié)論是由開源項(xiàng)目 JHipster 的工程師給出,具備一定的客觀性和可信度。

資源地址:https://blog.ippon.tech/sprin...

換言之,基于 Reactor 開發(fā)的 Gateway 在性能可能并沒有明顯的提升。因此,Zuul 和 Gateway 的性能對比則演變?yōu)?Servlet 容器和 Netty Web 容器的比較,感興趣的朋友可以去網(wǎng)上尋找一些比較數(shù)據(jù),兩者的性能在伯仲間。

當(dāng)然,我和在座的各位一樣,對 Java 的實(shí)現(xiàn)方案自然是情有獨(dú)鐘。然而我想說的是,身為 Java 工程師,眼中難免有 Java,但是眼中不要只有 Java。Nginx 作為當(dāng)年著名 “C10K” 問題的解決方案,無論從連接數(shù)量,還是資源消耗方面均優(yōu)于 Java 實(shí)現(xiàn)。作為技術(shù)人,應(yīng)該具有更為寬廣的胸懷,接納非我族類的氣魄,該放手的時候就放手。Nginx 作為服務(wù)網(wǎng)關(guān)不失為一種好的方案,然而它的動態(tài)性略為不足,需要結(jié)合 Lua 腳本輔助完成,因此,OpenResty 和 Kong 這類方案脫穎而出。如果就 HTTP API 網(wǎng)關(guān)而言,個人認(rèn)為 Kong 的方案更佳,因?yàn)樗峁┩暾慕鉀Q方案,包括前面討論的負(fù)載均衡(權(quán)重)、服務(wù)熔斷以及服務(wù)發(fā)現(xiàn)等特性。類似的特性在 CNCF 項(xiàng)目 Envoy 也有體現(xiàn),它是另一種高性能代理的方案,提供服務(wù)發(fā)現(xiàn)、健康和負(fù)載均衡。在協(xié)議上,天然支持 HTTP 和 HTTP/2,而通訊協(xié)議支持 gRPC,建議大家予以高度關(guān)注。

值得一提的是,HTTP API 網(wǎng)關(guān)通常需要支持 sidecar,換言之,支撐網(wǎng)關(guān)服務(wù)的基礎(chǔ)設(shè)施必須提供服務(wù)發(fā)現(xiàn)的能力,就功能性而言,Zuul 和 Gateway 自身并不具備這樣的特性,需要搭配 Eureka 這樣組件,它們更像服務(wù)路由器的角色。

分布式配置

左邊和中間的四種技術(shù)均為 Spring Cloud 分布式配置的底層存儲,其中 Git 為版本式配置,而 JDBC 是從 Spring Cloud Edgware 版本開始支持,提供更為通用和動態(tài)的配置源。這里我們又見到 Zookeeper 的聲影,從簡化運(yùn)維的角度,可以利用 Zookeeper 即承擔(dān)服務(wù)發(fā)現(xiàn),也作為分布式配置的基礎(chǔ)設(shè)施。而最右邊的 etcd 是最近非常火的 Kubernetes 分布式配置的 key-value 存儲,提供快速、簡單、安全和可高的解決方案。

服務(wù)熔斷

服務(wù)熔斷也非常讓開發(fā)人員聯(lián)想到 Spring Cloud Hystrix 技術(shù),不過 Hystrix 并非與 Spring Cloud 強(qiáng)耦合,當(dāng)然 Dubbo 也能結(jié)合 Netflix Hystrix 框架提供服務(wù)熔斷的能力,后面部分將介紹 Dubbo 與 Hystrix 整合,提升 Dubbo 服務(wù)熔斷的能力。確切地說,Dubbo 所提供的能力是集群容錯,包括 Failover 等模式。 Kong 也天然地支持服務(wù)熔斷的能力,所以它作為 API 網(wǎng)關(guān)的特性是全面的。

鏈路跟蹤

以上鏈路跟蹤的基礎(chǔ)設(shè)施從左至右,分別為 Zipkin、OpenTracing 以及 Jaeger,三者的靈感均來自于 Google 論文 Dapper。相對而言,Java 程序員可能更為熟悉 Zipkin,因?yàn)樗?Spring Cloud Sleuth 首選方案,提供客戶端上報以及服務(wù)端聚合和 Dashboard 等功能。而 OpenTracing 和 Jaeger 是 CNCF 孵化項(xiàng)目,前者屬于開放的標(biāo)準(zhǔn),提供多語言的適配實(shí)現(xiàn),后者則由 Uber(優(yōu)步)公司開發(fā)并開源的鏈路跟蹤項(xiàng)目,功能上與 Zipkin 類似,不過它基于 GO 語言開發(fā),同時也提供 Java 客戶端。

OpenTracing 官網(wǎng):http://opentracing.io/
jaeger 官網(wǎng):https://www.jaegertracing.io/
服務(wù)監(jiān)控

服務(wù)監(jiān)控與鏈路跟蹤有所區(qū)別,主要用于監(jiān)控應(yīng)用系統(tǒng)或業(yè)務(wù)的指標(biāo)數(shù)據(jù),可能是健康閾值,如 CPU 或 內(nèi)存使用率,也可以是業(yè)務(wù)指標(biāo),如最近一小時的用戶登錄量。通常采用 Metrics 方式暴露,可使用客戶端推送或服務(wù)端拉取的方式傳輸 Metrics 信息到數(shù)據(jù)中心。通常 Metrics 數(shù)據(jù)與時間是存在對應(yīng)關(guān)系,因此,基本上采用時序型數(shù)據(jù)庫來存儲,如圖中的 OpenTSDB。通常,Java 微服務(wù)應(yīng)用會選擇 Spring Boot 框架作為基礎(chǔ)設(shè)施,如我之前設(shè)計(jì)的監(jiān)控架構(gòu)就采用了 Spring Boot + OpenTSDB ,后端存儲基于 HBase。當(dāng)時 Spring Boot Actuator Metrics 僅為簡單的 Key Value 形式,自然 OpenTSDB 是理想的選擇。隨著 Spring Boot 2.0 開始支持 Micrometer 之后,使得 Spring Boot 的應(yīng)用能夠整合更多的 Micrometer 適配方案,其中名氣較大的就是圖中間的 Prometheus,它同樣也是 CNCF 的孵化項(xiàng)目。

當(dāng)然服務(wù)監(jiān)控不只是 Metrics 方式,我所知道國內(nèi)不少的公司采用了日志收集的方案,并搭配 ELK(Elasticsearch, Logstash, Kibana) 架構(gòu),減少運(yùn)維成本。假設(shè)您沒有使用該方案,或者僅使用了 Elasticsearch 的話,無論哪種方案,圖形化界面的監(jiān)控是必不可少的,因此我推薦 Grafana,該項(xiàng)目能夠支持多種數(shù)據(jù)源,包括前文提到的 OpenTSDB、Prometheus 以及 ElasticSearch 等。由此,從數(shù)據(jù)采集、上報、聚合以及展示的特性上,這些基礎(chǔ)設(shè)施幫助 Cloud Native 應(yīng)用構(gòu)建服務(wù)監(jiān)控的閉環(huán)。

本議程介紹了一些 Cloud Native 技術(shù)設(shè)施,接下里我們繼續(xù)討論 Cloud Native 架構(gòu)選型。

Cloud Native 架構(gòu)選型 CNCF 架構(gòu)體系

CNCF 體系作為目前最熱門的架構(gòu)選型之一,基本上圍繞著 Kubernetes 為中心而構(gòu)建。個人認(rèn)為,Java 業(yè)界和 CNCF 體系并沒有達(dá)成共識,如服務(wù)網(wǎng)關(guān),CNCF 主打 Envoy,而 Java 主要的方案為 Zuul 和 Spring Cloud Gateway。因此,個人建議是密切的關(guān)注 CNCF 的發(fā)展,不過個別孵化項(xiàng)目可以先行,如 Prometheus 和 Jaeger 等。 至于 CNCF 與 Java 生態(tài)的整合和落地,還得有待時日。

Spring Cloud 架構(gòu)體系

實(shí)際上,這個圖片并非 Spring Cloud 組件架構(gòu),而是將其整合在 Pivotal Cloud Foundry (PCF) 架構(gòu)中?;旧?,Spring Cloud 功能組件均有所體現(xiàn),包括 Eureka、Hystrix、Ribbon 等。不過值得注意的是,Spring Cloud Stream 是一套較為完整和抽象的流式編程框架,屏蔽了底層傳輸介質(zhì)(不僅是消息服務(wù)),如 Kafka、RabbitMQ 等。除此之外,其他的組件可圈可點(diǎn),如 Eureka 在大規(guī)模運(yùn)用中的卡頓問題、Ribbon 缺少權(quán)重、Zuul 連接數(shù)限制和資源消耗、服務(wù)調(diào)用受限于 Feign REST 協(xié)議限制等。如果在小規(guī)模場景使用,以上限制或問題不明顯,可以說 Spring Cloud 完全能夠適任。

不過,差不多兩年前,我曾在不同的公開場合講過:”Spring Boot 易學(xué)難精,Spring Cloud 能用但不成熟“。當(dāng)時很多人覺得我“離經(jīng)叛道”,然而這句話并非空穴來風(fēng),是我這幾年來 Java 微服務(wù)架構(gòu)實(shí)施的心得。這兩年來,深受 Spring Cloud “折磨”的小伙伴逐漸覺醒,慢慢地開始回到 Dubbo 等技術(shù)方案。如 Martin Fowler 在為“微服務(wù)”下定義時,提到通訊協(xié)議要用輕量級的 REST。假設(shè)微服務(wù)要做到服務(wù)無關(guān)的話,那么 Web Services 協(xié)議也是可以,盡管它看起來比較重,不過 Web Services 的結(jié)構(gòu)化和強(qiáng)類型,可以省去不少的運(yùn)行時校驗(yàn)邏輯。在我看來,微服務(wù)更大程度應(yīng)該體現(xiàn)在服務(wù)粒度上,誠如 Netflix 前架構(gòu)師 Adrian Cockcroft 說言:“Fine grain SOA”(微服務(wù)就是細(xì)粒度的 SOA),就這一點(diǎn)而言,比較容易地和業(yè)界達(dá)成共識。當(dāng)我們把 Martin 的話視如圭臬時,我們是否要思考它是否經(jīng)得起工程檢驗(yàn)。這里,我沒有興趣貶低他人,來抬高自己(Dubbo),從而引導(dǎo)讓大家放棄 Spring Cloud,而是我們需要給 Spring Cloud 時間,包括未來 Dubbo 也會向 Spring Cloud 靠攏并整合。在阿里的內(nèi)部,基于 Nacos(馬上開源的項(xiàng)目)和 Apache RocketMQ,實(shí)現(xiàn)了 Spring Cloud Service Discovery、Config 以及 Stream 等整合和適配,一旦時機(jī)成熟,可能會開源與大家共建。

既然談到了 Dubbo,下面我們再來討論 Dubbo 的架構(gòu)體系。

Dubbo 架構(gòu)體系

編程模型方面,不但支持傳統(tǒng)的 Spring XML 配合方式,已經(jīng)實(shí)現(xiàn)注解驅(qū)動以及外部化配置,并且全面支持最新的 Spring Boot 2.0,在不久的未來,大家會看到 Dubbo 與 Spring Cloud 的整合,使開發(fā)人員無縫地銜接已有的 Spring Cloud 應(yīng)用。

Dubbo Spring Boot 項(xiàng)目地址:https://github.com/apache/inc...

注冊中心方面,Dubbo 將整合 Eureka、etcd 以及 Consul 基礎(chǔ)設(shè)施,深度與業(yè)界熱門方案整合。

熔斷機(jī)制方面,Dubbo 會在近期發(fā)布 Hystrix 整合實(shí)現(xiàn),將編程友好性做得最大化。

通訊協(xié)議方面,Dubbo 將會支持 gRPC、Thrift 等熱門通訊協(xié)議。

至于序列化協(xié)議,自然首先考慮的是 Protobuf,因其高層 gRPC 搭配 HTTP/2 快成或已經(jīng)成為下一代通訊協(xié)議的事實(shí)標(biāo)準(zhǔn),使得任何人無法忽視它們的存在。當(dāng)然其他協(xié)議也會陸續(xù)支持。

其他方面,我這里就不一一介紹,總之,現(xiàn)在 Dubbo 已不再只是一個單一的 PRC 框架,而是要擁抱業(yè)界,形成完整的生態(tài)體系,與業(yè)界形成最大公約數(shù)。

Dubbo Cloud Native 準(zhǔn)備

在 Dubbo 架構(gòu)體系時,我們曾提到編程模型的變化。從 Dubbo 2.5.8 開始,注解驅(qū)動和外部化配置均已得到支持。同時,Dubbo 已經(jīng)合并 Dubbox 代碼,Java JAX-RS 標(biāo)準(zhǔn)得到了支持,目前業(yè)界事實(shí)的 REST 標(biāo)準(zhǔn) Spring Web MVC 正在同步開發(fā)。Reactive 的支持也在同步進(jìn)行,小馬哥還得友好地提醒一下各位,對于 Reactive 的期望不應(yīng)該過分的關(guān)注性能的提升。

Dubbo 注解驅(qū)動(Annotation-Driven)

在 Dubbo 2.5.7 之前的版本 ,Dubbo 提供了兩個核心注解 @Service 以及 @Reference,分別用于Dubbo 服務(wù)提供和 Dubbo 服務(wù)引用。

其中,@Service 作為 XML 元素 的替代注解,與 Spring Framework @org.springframework.stereotype.Service 類似,用于服務(wù)提供方 Dubbo 服務(wù)暴露。與之相對應(yīng)的 @Reference,則是替代 元素,類似于 Spring 中的 @Autowired

2.5.7 之前的Dubbo,與早期的 Spring Framework 2.5 存在類似的不足,即注解支持不夠充分。注解需要和 XML 配置文件配合使用,如下所示:




    
    
    

不僅如此,當(dāng)時的版本存在“ @Service Bean 不支持 Spring AOP” 以及 “@Reference 不支持字段繼承性” 等問題。

2.5.7 開始,Dubbo 開始引入組件掃描 Annotation @DubboComponentScan,借鑒了 Spring Boot 1.3 引入的 @ServletComponentScan。

在職責(zé)上,@DubboComponentScan 相對于 Spring Boot @ServletComponentScan 更為繁重,原因在于處理 Dubbo @Service 類暴露 Dubbo 服務(wù)外,還有幫助 Spring Bean @Reference字段或者方法注入 Dubbo 服務(wù)代理。

在場景上,Spring Framework @ComponentScan 組件掃描邏輯更為復(fù)雜。而在 @DubboComponentScan 只需關(guān)注 @Service@Reference 處理。

注:更多 Dubbo 注解驅(qū)動的詳情,請參考《Dubbo 注解驅(qū)動(Annotation-Driven)》
@DubboComponentScan 服務(wù)端示例

假設(shè),服務(wù)提供方和服務(wù)消費(fèi)分均依賴服務(wù)接口DemoService:

package com.alibaba.dubbo.demo;

public interface DemoService {

    String sayHello(String name);

}

服務(wù)提供方實(shí)現(xiàn)DemoService - AnnotationDemoService

同時標(biāo)注 Dubbo @Service

package com.alibaba.dubbo.demo.provider;

import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.dubbo.demo.DemoService;

/**
 * Annotation {@link DemoService} 實(shí)現(xiàn)
 *
 * @author Mercy
 */
@Service
public class AnnotationDemoService implements DemoService {

    @Override
    public String sayHello(String name) {
        return "Hello , " + name;
    }

}

服務(wù)端 @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;

/**
 * 服務(wù)提供方配置
 *
 * @author Mercy
 */
@Configuration
@DubboComponentScan("com.alibaba.dubbo.demo.provider") // 掃描 Dubbo 組件
public class ProviderConfiguration {

    /**
     * 當(dāng)前應(yīng)用配置
     */
    @Bean("dubbo-annotation-provider")
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("dubbo-annotation-provider");
        return applicationConfig;
    }

    /**
     * 當(dāng)前連接注冊中心配置
     */
    @Bean("my-registry")
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("N/A");
        return registryConfig;
    }

    /**
     * 當(dāng)前連接注冊中心配置
     */
    @Bean("dubbo")
    public ProtocolConfig protocolConfig() {
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setName("dubbo");
        protocolConfig.setPort(12345);
        return protocolConfig;
    }
}

服務(wù)提供方引導(dǎo)類

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;

/**
 * 服務(wù)提供方引導(dǎo)類
 *
 * @author Mercy
 */
public class ProviderBootstrap {

    public static void main(String[] args) {
        // 創(chuàng)建 Annotation 配置上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 注冊配置 Bean
        context.register(ProviderConfiguration.class);
        // 啟動上下文
        context.refresh();
        // 獲取 DemoService Bean
        DemoService demoService = context.getBean(DemoService.class);
        // 執(zhí)行 sayHello 方法
        String message = demoService.sayHello("World");
        // 控制臺輸出信息
        System.out.println(message);
    }
    
}
@DubboComponentScan 客戶端示例

消費(fèi)服務(wù) DemoService

package com.alibaba.dubbo.demo.consumer;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.demo.DemoService;

/**
 * Annotation 驅(qū)動 {@link DemoService} 消費(fèi)方
 *
 * @author Mercy
 */
public class AnnotationDemoServiceConsumer {

    @Reference(url = "dubbo://127.0.0.1:12345")
    private DemoService demoService;

    public String doSayHell(String name) {
        return demoService.sayHello(name);
    }
}

消費(fèi)端 @Configuration Class

與服務(wù)提供方配置類似,服務(wù)消費(fèi)方也許 Dubbo 相關(guān)配置 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;

/**
 * 服務(wù)消費(fèi)方配置
 *
 * @author Mercy
 */
@Configuration
@DubboComponentScan
public class ConsumerConfiguration {

    /**
     * 當(dāng)前應(yīng)用配置
     */
    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("dubbo-annotation-consumer");
        return applicationConfig;
    }

    /**
     * 當(dāng)前連接注冊中心配置
     */
    @Bean
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("N/A");
        return registryConfig;
    }

    /**
     * 注冊 AnnotationDemoServiceConsumer,@DubboComponentScan 將處理其中 @Reference 字段。
     * 如果 AnnotationDemoServiceConsumer 非 Spring Bean 的話,
     * 即使 @DubboComponentScan 指定 package 也不會進(jìn)行處理,與 Spring @Autowired 同理
     */
    @Bean
    public AnnotationDemoServiceConsumer annotationDemoServiceConsumer() {
        return new AnnotationDemoServiceConsumer();
    }

}

服務(wù)消費(fèi)方引導(dǎo)類

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;

/**
 * 服務(wù)消費(fèi)端引導(dǎo)類
 *
 * @author Mercy
 */
public class ConsumerBootstrap {

    public static void main(String[] args) {
        // 啟動服務(wù)提供方上下文
        startProviderContext();
        // 啟動并且返回服務(wù)消費(fèi)方上下文
        ApplicationContext consumerContext = startConsumerContext();
        // 獲取 AnnotationDemoServiceConsumer Bean
        AnnotationDemoServiceConsumer consumer = consumerContext.getBean(AnnotationDemoServiceConsumer.class);
        // 執(zhí)行 doSayHello 方法
        String message = consumer.doSayHello("World");
        // 輸出執(zhí)行結(jié)果
        System.out.println(message);
    }

    /**
     * 啟動并且返回服務(wù)消費(fèi)方上下文
     *
     * @return AnnotationConfigApplicationContext
     */
    private static ApplicationContext startConsumerContext() {
        // 創(chuàng)建服務(wù)消費(fèi)方 Annotation 配置上下文
        AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();
        // 注冊服務(wù)消費(fèi)方配置 Bean
        consumerContext.register(ConsumerConfiguration.class);
        // 啟動服務(wù)消費(fèi)方上下文
        consumerContext.refresh();
        // 返回服務(wù)消費(fèi)方 Annotation 配置上下文
        return consumerContext;
    }

    /**
     * 啟動服務(wù)提供方上下文
     */
    private static void startProviderContext() {
        // 創(chuàng)建 Annotation 配置上下文
        AnnotationConfigApplicationContext providerContext = new AnnotationConfigApplicationContext();
        // 注冊配置 Bean
        providerContext.register(ProviderConfiguration.class);
        // 啟動服務(wù)提供方上下文
        providerContext.refresh();
    }

}
Dubbo 外部化配置(Externalized Configuration)

在Dubbo 注解驅(qū)動例子中,無論是服務(wù)提供方,還是服務(wù)消費(fèi)方,均需要轉(zhuǎn)配相關(guān)配置Bean:

    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("dubbo-annotation-consumer");
        return applicationConfig;
    }

雖然實(shí)現(xiàn)類似于ProviderConfigurationConsumerConfiguration 這樣的 Spring @Configuration Bean 成本并不高,不過通過 Java Code 的方式定義配置 Bean,或多或少是一種 Hard Code(硬編碼)的行為,缺少彈性。

盡管在 Spring 應(yīng)用中,可以通過 @Value 或者 Environment 的方式獲取外部配置,其代碼簡潔性以及類型轉(zhuǎn)換靈活性存在明顯的不足。因此,Spring Boot 提出了外部化配置(External Configuration)的感念,即通過程序以外的配置源,動態(tài)地綁定指定類型。

隨著 Spring Boot / Spring Cloud 應(yīng)用的流行,開發(fā)人員逐漸地接受并且使用 Spring Boot 外部化配置(External Configuration),即通過 application.properties 或者 bootstrap.properties 裝配配置 Bean。

下列表格記錄了 Dubbo 內(nèi)置配置類:

配置類 標(biāo)簽 用途 解釋
ProtocolConfig 協(xié)議配置 用于配置提供服務(wù)的協(xié)議信息,協(xié)議由提供方指定,消費(fèi)方被動接受
ApplicationConfig 應(yīng)用配置 用于配置當(dāng)前應(yīng)用信息,不管該應(yīng)用是提供者還是消費(fèi)者
ModuleConfig 模塊配置 用于配置當(dāng)前模塊信息,可選
RegistryConfig 注冊中心配置 用于配置連接注冊中心相關(guān)信息
MonitorConfig 監(jiān)控中心配置 用于配置連接監(jiān)控中心相關(guān)信息,可選
ProviderConfig 提供方配置 當(dāng) ProtocolConfig 和 ServiceConfig 某屬性沒有配置時,采用此缺省值,可選
ConsumerConfig 消費(fèi)方配置 當(dāng) ReferenceConfig 某屬性沒有配置時,采用此缺省值,可選
MethodConfig 方法配置 用于 ServiceConfig 和 ReferenceConfig 指定方法級的配置信息
ArgumentConfig 參數(shù)配置 用于指定方法參數(shù)配置

通過申明對應(yīng)的 Spring 擴(kuò)展標(biāo)簽,在 Spring 應(yīng)用上下文中將自動生成相應(yīng)的配置 Bean。

在 Dubbo 官方用戶手冊的“屬性配置”章節(jié)中,dubbo.properties 配置屬性能夠映射到 ApplicationConfig 、ProtocolConfig 以及 RegistryConfig 的字段。從某種意義上來說,dubbo.properties 也是 Dubbo 的外部化配置。

注:更多外部化配置的詳情,請參考《Dubbo 外部化配置(Externalized Configuration)》
現(xiàn)場演示環(huán)節(jié)
本環(huán)境為分享后部分,現(xiàn)在編碼 + 演示環(huán)境,當(dāng)前文字僅提供代碼實(shí)現(xiàn)。
Dubbo 整合 Hystrix 示例
本示例出現(xiàn)在分享議程的代碼演示,將其放置此處,方便閱讀理解
Dubbo 客戶端實(shí)現(xiàn)

實(shí)現(xiàn) HystrixCommand

public class ResultHystrixCommand extends HystrixCommand {

    private final Invoker invoker;

    private final Invocation invocation;

    public ResultHystrixCommand(Invoker invoker, Invocation invocation) {
        super(HystrixCommandGroupKey.Factory.asKey(
                "ResultHystrixCommand"),
                100); // 設(shè)置超時時間
        // 關(guān)聯(lián) Dubbo Invoker 和 Invocation
        this.invoker = invoker;
        this.invocation = invocation;
    }

    @Override
    protected Result run() throws Exception {
        // 遠(yuǎn)程方法調(diào)用執(zhí)行
        return invoker.invoke(invocation);
    }
}

當(dāng)目標(biāo)方法執(zhí)行時間超過 100 ms 時,觸發(fā)熔斷,并拋出 new UnsupportedOperationException("No fallback available.")

Dubbo Filter 整合 ResultHystrixCommand

@Activate(group = Constants.CONSUMER, value = "hystrix") // 命名當(dāng)前 Filter 為 "hystrix"
public class HystrixFilter implements Filter {

    @Override
    public Result invoke(Invoker invoker, Invocation invocation) throws RpcException {
        return new ResultHystrixCommand(invoker, invocation).execute();
    }
}

創(chuàng)建并配置 Filter SPI 配置文件

在相對于 ClassPath 資源 META-INF/dubbo/ 下創(chuàng)建 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 實(shí)現(xiàn)
    )
    private DemoService demoService;

    @RequestMapping("/sayHello")
    public String sayHello(@RequestParam String name) {
        return demoService.sayHello(name);
    }

}
Dubbo 服務(wù)端實(shí)現(xiàn)

服務(wù)提供者實(shí)現(xiàn)

@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() { // 隨機(jī)等待 < 200 ms,當(dāng)時間超過 100 ms 時,觸發(fā)客戶端熔斷
        long time = random.nextInt(200);
        System.out.println("To hold " + time + " ms!");
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

}
測試客戶端 REST 服務(wù)

依次啟動服務(wù)端 和客戶端 Spring Boot 應(yīng)用,

執(zhí)行 curl 命令

mercyblitz$ curl http://localhost:8080/sayHello?name=Hello

測試結(jié)果

{"timestamp":"2018-06-23T01:33:58.682+0000","status":500,"error":"Internal Server Error","message":"ResultHystrixCommand timed-out and no fallback available.","path":"/sayHello"}

運(yùn)行結(jié)果說明服務(wù)端方法執(zhí)行超過 100 ms,引起客戶端熔斷。

(EOF)

參考資源

Dubbo 官網(wǎng):https://dubbo.apache.org/

Dubbo 工程:https://github.com/apache/inc...

Dubbo Spring Boot 工程:https://github.com/apache/inc...

CNCF Landscape:https://landscape.cncf.io/

Spring Cloud 官網(wǎng):https://projects.spring.io/sp...

Kong 社區(qū)官網(wǎng):https://konghq.com/kong-commu...

Opentracing 官網(wǎng):http://opentracing.io/

Jaeger 官網(wǎng):https://www.jaegertracing.io/

Prometheus 官網(wǎng):https://prometheus.io/

OpenTsdb 官網(wǎng):http://opentsdb.net/

Grafana 官網(wǎng):https://grafana.com/

小馬哥 Github:https://github.com/mercyblitz

社區(qū)交流 小馬哥 Java 星球

QQ 交流群

小馬哥微信公眾號

小馬哥個人微信

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/25243.html

相關(guān)文章

  • Dubbo Cloud Native 之路的實(shí)踐思考

    摘要:可簡單地認(rèn)為它是的擴(kuò)展,負(fù)載均衡自然成為不可或缺的特性。是基于開發(fā)的服務(wù)代理組件,在使用場景中,它與和整合,打造具備服務(wù)動態(tài)更新和負(fù)載均衡能力的服務(wù)網(wǎng)關(guān)。類似的特性在項(xiàng)目也有體現(xiàn),它是另一種高性能代理的方案,提供服務(wù)發(fā)現(xiàn)健康和負(fù)載均衡。 摘要: Cloud Native 應(yīng)用架構(gòu)隨著云技術(shù)的發(fā)展受到業(yè)界特別重視和關(guān)注,尤其是 CNCF(Cloud Native Computing Fo...

    niceforbear 評論0 收藏0
  • Service Mesh 時代,Dubbo 架構(gòu)該怎么跟進(jìn)?

    摘要:原文鏈接時代,架構(gòu)該怎么跟進(jìn),來自于微信公眾號次靈均閣作為核心開發(fā)者,請先簡單介紹下自己答大家好,我是小馬哥,一名學(xué)習(xí)當(dāng)爸爸的父親,勸退師,項(xiàng)目架構(gòu)師,編程思想的作者。因此,需求的來源不再已阿里為絕對主導(dǎo),社區(qū)共建和共制的發(fā)展模式已成事實(shí)。 原文鏈接:Service Mesh 時代,Dubbo 架構(gòu)該怎么跟進(jìn)?,來自于微信公眾號:次靈均閣 作為 Duboo 核心開發(fā)者,請先簡單介紹下...

    robin 評論0 收藏0
  • Service Mesh 時代,Dubbo 架構(gòu)該怎么跟進(jìn)?

    摘要:原文鏈接時代,架構(gòu)該怎么跟進(jìn),來自于微信公眾號次靈均閣作為核心開發(fā)者,請先簡單介紹下自己答大家好,我是小馬哥,一名學(xué)習(xí)當(dāng)爸爸的父親,勸退師,項(xiàng)目架構(gòu)師,編程思想的作者。因此,需求的來源不再已阿里為絕對主導(dǎo),社區(qū)共建和共制的發(fā)展模式已成事實(shí)。 原文鏈接:Service Mesh 時代,Dubbo 架構(gòu)該怎么跟進(jìn)?,來自于微信公眾號:次靈均閣 作為 Duboo 核心開發(fā)者,請先簡單介紹下...

    李文鵬 評論0 收藏0
  • Dubbo Spring Cloud 重塑微服務(wù)治理

    摘要:在服務(wù)治理方面,相較于而言,并不成熟。遺憾的是,往往被部分開發(fā)者片面地視作服務(wù)治理的框架,而非微服務(wù)基礎(chǔ)設(shè)施。因此,建議開發(fā)人員將或者遷移為服務(wù)。因此,下一步需要將其配置服務(wù)遠(yuǎn)程。當(dāng)服務(wù)提供方啟動后,下一步實(shí)現(xiàn)一個服務(wù)消費(fèi)方。 原文鏈接:Dubbo Spring Cloud 重塑微服務(wù)治理,來自于微信公眾號:次靈均閣 摘要 在 Java 微服務(wù)生態(tài)中,Spring Cloud1 成為...

    wh469012917 評論0 收藏0
  • 【推薦】最新200篇:技術(shù)文章整理

    摘要:作為面試官,我是如何甄別應(yīng)聘者的包裝程度語言和等其他語言的對比分析和主從復(fù)制的原理詳解和持久化的原理是什么面試中經(jīng)常被問到的持久化與恢復(fù)實(shí)現(xiàn)故障恢復(fù)自動化詳解哨兵技術(shù)查漏補(bǔ)缺最易錯過的技術(shù)要點(diǎn)大掃盲意外宕機(jī)不難解決,但你真的懂?dāng)?shù)據(jù)恢復(fù)嗎每秒 作為面試官,我是如何甄別應(yīng)聘者的包裝程度Go語言和Java、python等其他語言的對比分析 Redis和MySQL Redis:主從復(fù)制的原理詳...

    BicycleWarrior 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<