基于 Docker 的微服務(wù)架構(gòu)實(shí)踐

責(zé)任編輯:ycao

2018-04-20 14:17:42

摘自:構(gòu)架之家

基于 Docker 的容器技術(shù)是在2015年的時(shí)候開始接觸的,兩年多的時(shí)間,作為一名 Docker 的 DevOps,也見證了 Docker 的技術(shù)體系的快速發(fā)展。本文主要是結(jié)合在公司搭建的微服務(wù)架構(gòu)的實(shí)踐過程,做一個(gè)簡單的總結(jié)。

前言

基于 Docker 的容器技術(shù)是在2015年的時(shí)候開始接觸的,兩年多的時(shí)間,作為一名 Docker 的 DevOps,也見證了 Docker 的技術(shù)體系的快速發(fā)展。本文主要是結(jié)合在公司搭建的微服務(wù)架構(gòu)的實(shí)踐過程,做一個(gè)簡單的總結(jié)。希望給在創(chuàng)業(yè)初期探索如何布局服務(wù)架構(gòu)體系的 DevOps,或者想初步了解企業(yè)級(jí)架構(gòu)的同學(xué)們一些參考。

Microservice 和 Docker

對(duì)于創(chuàng)業(yè)公司的技術(shù)布局,很多聲音基本上是,創(chuàng)業(yè)公司就是要快速上線快速試錯(cuò)。用單應(yīng)用或者前后臺(tái)應(yīng)用分離的方式快速集成,快速開發(fā),快速發(fā)布。但其實(shí)這種結(jié)果造成的隱性成本會(huì)更高。

當(dāng)業(yè)務(wù)發(fā)展起來,開發(fā)人員多了之后,就會(huì)面臨龐大系統(tǒng)的部署效率,開發(fā)協(xié)同效率問題。然后通過服務(wù)的拆分,數(shù)據(jù)的讀寫分離、分庫分表等方式重新架構(gòu),而且這種方式如果要做的徹底,需要花費(fèi)大量人力物力。

個(gè)人建議,DevOps 結(jié)合自己對(duì)于業(yè)務(wù)目前以及長期的發(fā)展判斷,能夠在項(xiàng)目初期使用微服務(wù)架構(gòu),多為后人謀福。

隨著 Docker 周圍開源社區(qū)的發(fā)展,讓微服務(wù)架構(gòu)的概念能有更好的一個(gè)落地實(shí)施的方案。并且在每一個(gè)微服務(wù)應(yīng)用內(nèi)部,都可以使用 DDD(Domain-Drive Design)的六邊形架構(gòu)來進(jìn)行服務(wù)內(nèi)的設(shè)計(jì)。關(guān)于 DDD 的一些概念也可以參考之前寫的幾篇文章:領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)整理——概念&架構(gòu)、領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)整理——實(shí)體和值對(duì)象設(shè)計(jì)、領(lǐng)域服務(wù)、領(lǐng)域事件。

清晰的微服務(wù)的領(lǐng)域劃分,服務(wù)內(nèi)部有架構(gòu)層次的優(yōu)雅的實(shí)現(xiàn),服務(wù)間通過 RPC 或者事件驅(qū)動(dòng)完成必要的 IPC,使用 API gateway 進(jìn)行所有微服務(wù)的請(qǐng)求轉(zhuǎn)發(fā),非阻塞的請(qǐng)求結(jié)果合并。本文下面會(huì)具體介紹,如何在分布式環(huán)境下,也可以快速搭建起來具有以上幾點(diǎn)特征的,微服務(wù)架構(gòu) with Docker。

服務(wù)發(fā)現(xiàn)模式

如果使用 Docker 技術(shù)來架構(gòu)微服務(wù)體系,服務(wù)發(fā)現(xiàn)就是一個(gè)必然的課題。目前主流的服務(wù)發(fā)現(xiàn)模式有兩種:客戶端發(fā)現(xiàn)模式,以及服務(wù)端發(fā)現(xiàn)模式。

客戶端發(fā)現(xiàn)模式

客戶端發(fā)現(xiàn)模式的架構(gòu)圖如下:

客戶端發(fā)現(xiàn)模式的典型實(shí)現(xiàn)是Netflix體系技術(shù)??蛻舳藦囊粋€(gè)服務(wù)注冊(cè)服務(wù)中心查詢所有可用服務(wù)實(shí)例??蛻舳耸褂秘?fù)載均衡算法從多個(gè)可用的服務(wù)實(shí)例中選擇出一個(gè),然后發(fā)出請(qǐng)求。比較典型的一個(gè)開源實(shí)現(xiàn)就是 Netflix 的 Eureka。

Netflix-Eureka

Eureka 的客戶端是采用自注冊(cè)的模式,客戶端需要負(fù)責(zé)處理服務(wù)實(shí)例的注冊(cè)和注銷,發(fā)送心跳。

在使用 SpringBoot 集成一個(gè)微服務(wù)時(shí),結(jié)合 SpringCloud 項(xiàng)目可以很方便得實(shí)現(xiàn)自動(dòng)注冊(cè)。在服務(wù)啟動(dòng)類上添加@EnableEurekaClient即可在服務(wù)實(shí)例啟動(dòng)時(shí),向配置好的 Eureka 服務(wù)端注冊(cè)服務(wù),并且定時(shí)發(fā)送以心跳??蛻舳说呢?fù)載均衡由 Netflix Ribbon 實(shí)現(xiàn)。服務(wù)網(wǎng)關(guān)使用 Netflix Zuul,熔斷器使用 Netflix Hystrix。

除了服務(wù)發(fā)現(xiàn)的配套框架,SpringCloud 的 Netflix-Feign,提供了聲明式的接口來處理服務(wù)的 Rest 請(qǐng)求。當(dāng)然,除了使用 FeignClient,也可以使用 Spring RestTemplate。項(xiàng)目中如果使用@FeignClient可以使代碼的可閱讀性更好,Rest API 也一目了然。

服務(wù)實(shí)例的注冊(cè)管理、查詢,都是通過應(yīng)用內(nèi)調(diào)用 Eureka 提供的 REST API 接口(當(dāng)然使用 SpringCloud-Eureka 不需要編寫這部分代碼)。由于服務(wù)注冊(cè)、注銷是通過客戶端自身發(fā)出請(qǐng)求的,所以這種模式的一個(gè)主要問題是對(duì)于不同的編程語言會(huì)注冊(cè)不同服務(wù),需要為每種開發(fā)語言單獨(dú)開發(fā)服務(wù)發(fā)現(xiàn)邏輯。另外,使用 Eureka 時(shí)需要顯式配置健康檢查支持。

服務(wù)端發(fā)現(xiàn)模式

服務(wù)端發(fā)現(xiàn)模式的架構(gòu)圖如下:

客戶端向負(fù)載均衡器發(fā)出請(qǐng)求,負(fù)載均衡器向服務(wù)注冊(cè)表發(fā)出請(qǐng)求,將請(qǐng)求轉(zhuǎn)發(fā)到注冊(cè)表中可用的服務(wù)實(shí)例。服務(wù)實(shí)例也是在注冊(cè)表中注冊(cè),注銷的。負(fù)載均衡可以使用可以使用 Haproxy 或者 Nginx。服務(wù)端發(fā)現(xiàn)模式目前基于 Docker 的主流方案主要是 Consul、Etcd 以及 Zookeeper。

Consul

Consul 提供了一個(gè) API 允許客戶端注冊(cè)和發(fā)現(xiàn)服務(wù)。其一致性上基于RAFT算法。通過 WAN 的 Gossip 協(xié)議,管理成員和廣播消息,以完成跨數(shù)據(jù)中心的同步,且支持 ACL 訪問控制。Consul 還提供了健康檢查機(jī)制,支持 kv 存儲(chǔ)服務(wù)(Eureka 不支持)。Consul 的一些更詳細(xì)的介紹可以參考之前寫的一篇:Docker 容器部署 Consul 集群。

Etcd

Etcd 都是強(qiáng)一致的(滿足 CAP 的 CP),高可用的。Etcd 也是基于 RAFT 算法實(shí)現(xiàn)強(qiáng)一致性的 KV 數(shù)據(jù)同步。Kubernetes 中使用 Etcd 的 KV 結(jié)構(gòu)存儲(chǔ)所有對(duì)象的生命周期。

關(guān)于 Etcd 的一些內(nèi)部原理可以看下etcd v3原理分析

Zookeeper

ZK 最早應(yīng)用于 Hadoop,其體系已經(jīng)非常成熟,常被用于大公司。如果已經(jīng)有自己的 ZK 集群,那么可以考慮用 ZK 來做自己的服務(wù)注冊(cè)中心。

Zookeeper 同 Etcd 一樣,強(qiáng)一致性,高可用性。一致性算法是基于 Paxos 的。對(duì)于微服務(wù)架構(gòu)的初始階段,沒有必要用比較繁重的 ZK 來做服務(wù)發(fā)現(xiàn)。

服務(wù)注冊(cè)

服務(wù)注冊(cè)表是服務(wù)發(fā)現(xiàn)中的一個(gè)重要組件。除了 Kubernetes、Marathon 其服務(wù)發(fā)現(xiàn)是內(nèi)置的模塊之外。服務(wù)都是需要注冊(cè)到注冊(cè)表上。上文介紹的 Eureka、consul、etcd 以及 ZK 都是服務(wù)注冊(cè)表的例子。

微服務(wù)如何注冊(cè)到注冊(cè)表也是有兩種比較典型的注冊(cè)方式:自注冊(cè)模式,第三方注冊(cè)模式。

自注冊(cè)模式 Self-registration pattern

上文中的 Netflix-Eureka 客戶端就是一個(gè)典型的自注冊(cè)模式的例子。也即每個(gè)微服務(wù)的實(shí)例本身,需要負(fù)責(zé)注冊(cè)以及注銷服務(wù)。Eureka 還提供了心跳機(jī)制,來保證注冊(cè)信息的準(zhǔn)確,具體的心跳的發(fā)送間隔時(shí)間可以在微服務(wù)的 SpringBoot 中進(jìn)行配置。

如下,就是使用 Eureka 做注冊(cè)表時(shí),在微服務(wù)(SpringBoot 應(yīng)用)啟動(dòng)時(shí)會(huì)有一條服務(wù)注冊(cè)的信息:

com.netflix .discovery.DiscoveryClient : DiscoveryClient _ SERVICE-USER/ { your_ip }:service-user :{port}:cc9f93c54a0820c7a845422f9ecc73fb : registering service...

同樣,在應(yīng)用停用時(shí),服務(wù)實(shí)例需要主動(dòng)注銷本實(shí)例信息:

2018-01-04 20:41:37.290 INFO 49244 --- [ Thread-8 ] c.n.e .EurekaDiscoveryClientConfiguration : Unregistering application service - user with eureka with status DOWN

2018 -01 -04 20:41:37.340 INFO 49244 --- [ Thread-8 ] com.netflix.discovery.DiscoveryClient : Shutting down DiscoveryClient ...

2018 -01 -04 20:41:37.381 INFO 49244 --- [ Thread-8 ] com.netflix.discovery.DiscoveryClient : Unregistering ...

2018 -01 -04 20:41:37.559 INFO 49244 --- [ Thread-8 ] com.netflix.discovery.DiscoveryClient : DiscoveryClient _SERVICE-USER /{your_ip}: service-user: {port}: cc9f93c54a0820c7a845422f9ecc73fb - deregister status: 200

自注冊(cè)方式是比較簡單的服務(wù)注冊(cè)方式,不需要額外的設(shè)施或代理,由微服務(wù)實(shí)例本身來管理服務(wù)注冊(cè)。但是缺點(diǎn)也很明顯,比如 Eureka 目前只提供了 Java 客戶端,所以不方便多語言的微服務(wù)擴(kuò)展。因?yàn)樾枰⒎?wù)自己去管理服務(wù)注冊(cè)邏輯,所以微服務(wù)實(shí)現(xiàn)也耦合了服務(wù)注冊(cè)和心跳機(jī)制??缯Z言性比較差。

第三方注冊(cè)模式 Third party registration pattern

第三方注冊(cè),也即服務(wù)注冊(cè)的管理(注冊(cè)、注銷服務(wù))通過一個(gè)專門的服務(wù)管理器(Registar)來負(fù)責(zé)。Registrator 就是一個(gè)開源的服務(wù)管理器的實(shí)現(xiàn)。Registrator 提供了對(duì)于 Etcd 以及 Consul 的注冊(cè)表服務(wù)支持。

Registrator 作為一個(gè)代理服務(wù),需要部署、運(yùn)行在微服務(wù)所在的服務(wù)器或者虛擬機(jī)中。比較簡單的安裝方式就是通過 Docker,以容器的方式來運(yùn)行。三方注冊(cè)模式的架構(gòu)圖如下:

通過添加一個(gè)服務(wù)管理器,微服務(wù)實(shí)例不再直接向注冊(cè)中心注冊(cè),注銷。由服務(wù)管理器(Registar)通過訂閱服務(wù),跟蹤心跳,來發(fā)現(xiàn)可用的服務(wù)實(shí)例,并向注冊(cè)中心(consul、etcd 等)注冊(cè),注銷實(shí)例,以及發(fā)送心跳。這樣就可以實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)組件和微服務(wù)架構(gòu)的解耦。

Registrator 配合 Consul,以及 Consul Template 搭建服務(wù)發(fā)現(xiàn)中心,可以參考: Scalable Architecture DR CoN: Docker, Registrator, Consul, Consul Template and Nginx 。此文示例了 Nginx 來做負(fù)載均衡,在具體的實(shí)施過程中也可以用 Haproxy 或其他方案進(jìn)行替代。

小結(jié)

除了以上幾種做服務(wù)發(fā)現(xiàn)的技術(shù),Kubernetes 自帶了服務(wù)發(fā)現(xiàn)模塊,負(fù)責(zé)處理服務(wù)實(shí)例的注冊(cè)和注銷。Kubernetes 也在每個(gè)集群節(jié)點(diǎn)上運(yùn)行代理,來實(shí)現(xiàn)服務(wù)端發(fā)現(xiàn)路由器的功能。如果編排技術(shù)使用的 k8n,可以用 k8n 的一整套 Docker 微服務(wù)方案,對(duì) k8n 感興趣的可以閱讀下Kubernetes 架構(gòu)設(shè)計(jì)與核心原理。

在實(shí)際的技術(shù)選型中,最主要還是要結(jié)合業(yè)務(wù)、系統(tǒng)的未來發(fā)展的特征進(jìn)行合理判斷。

在 CAP 理論中。Eureka 滿足了 AP,Consul 是 CA,ZK 和 Etcd 是 CP。 在分布式場景下 Eureka 和 Consul 都能保證可用性。而搭建 Eureka 服務(wù)會(huì)相對(duì)更快速,因?yàn)椴恍枰罱~外的高可用服務(wù)注冊(cè)中心,在小規(guī)模服務(wù)器實(shí)例時(shí),使用 Eureka 可以節(jié)省一定成本。

Eureka、Consul 都提供了可以查看服務(wù)注冊(cè)數(shù)據(jù)的 WebUI 組件。Consul 還提供了 KV 存儲(chǔ),支持支持 http 和 dns 接口。對(duì)于創(chuàng)業(yè)公司最開始搭建微服務(wù),比較推薦這兩者。

在多數(shù)據(jù)中心方面,Consul 自帶數(shù)據(jù)中心的 WAN 方案。ZK 和 Etcd 均不提供多數(shù)據(jù)中心功能的支持,需要額外的開發(fā)。

跨語言性上,Zookeeper 需要使用其提供的客戶端 api,跨語言支持較弱。Etcd、Eureka 都支持 http,Etcd 還支持 grpc。Consul 除了 http 之外還提供了 DNS 的支持。

安全方面,Consul,Zookeeper 支持 ACL,另外 Consul、Etcd 支持安全通道 Https。

SpringCloud 目前對(duì)于 Eureka、Consul、Etcd、ZK 都有相應(yīng)的支持。

Consul 和 Docker 一樣,都是用 Go 語言實(shí)現(xiàn),基于 Go 語言的微服務(wù)應(yīng)用可以優(yōu)先考慮用 Consul。

服務(wù)間的 IPC 機(jī)制

按照微服務(wù)的架構(gòu)體系,解決了服務(wù)發(fā)現(xiàn)的問題之后。就需要選擇合適的服務(wù)間通信的機(jī)制。如果是在 SpringBoot 應(yīng)用中,使用基于 Http 協(xié)議的 REST API 是一種同步的解決方案。而且 Restful 風(fēng)格的 API 可以使每個(gè)微服務(wù)應(yīng)用更加趨于資源化,使用輕量級(jí)的協(xié)議也是微服務(wù)一直提倡的。

如果每個(gè)微服務(wù)是使用 DDD(Domain-Driven Design)思想的話,那么需要每個(gè)微服務(wù)盡量不使用同步的 RPC 機(jī)制。異步的基于消息的方式比如 AMQP 或者 STOMP,來松耦合微服務(wù)間的依賴會(huì)是很好的選擇。目前基于消息的點(diǎn)對(duì)點(diǎn)的 pub/sub 的框架選擇也比較多。下面具體介紹下兩種 IPC 的一些方案。

同步

對(duì)于同步的請(qǐng)求/響應(yīng)模式的通信方式??梢赃x擇基于 Restful 風(fēng)格的 Http 協(xié)議進(jìn)行服務(wù)間通信,或者跨語言性很好的 Thrift 協(xié)議。如果是使用純 Java 語言的微服務(wù),也可以使用 Dubbo。如果是 SpringBoot 集成的微服務(wù)架構(gòu)體系,建議選擇跨語言性好、Spring 社區(qū)支持比較好的 RPC。

Dubbo

Dubbo是由阿里巴巴開發(fā)的開源的 Java 客戶端的 RPC 框架。Dubbo 基于 TCP 協(xié)議的長連接進(jìn)行數(shù)據(jù)傳輸。傳輸格式是使用 Hessian 二進(jìn)制序列化。服務(wù)注冊(cè)中心可以通過 Zookeeper 實(shí)現(xiàn)。

ApacheThrift

ApacheThrift 是由 Facebook 開發(fā)的 RPC 框架。其代碼生成引擎可以在多種語言中,如 C++、 Java、Python、PHP、Ruby、Erlang、Perl 等創(chuàng)建高效的服務(wù)。傳輸數(shù)據(jù)采用二進(jìn)制格式,其數(shù)據(jù)包要比使用 Json 或者 XML 格式的 HTTP 協(xié)議小。高并發(fā),大數(shù)據(jù)場景下更有優(yōu)勢。

Rest

Rest 基于 HTTP 協(xié)議,HTTP 協(xié)議本身具有語義的豐富性。隨著 Springboot 被廣泛使用,越來越多的基于 Restful 風(fēng)格的 API 流行起來。REST 是基于 HTTP 協(xié)議的,并且大多數(shù)開發(fā)者也是熟知 HTTP 的。

這里另外提一點(diǎn),很多公司或者團(tuán)隊(duì)也是使用Springboot的,也在說自己是基于 Restful 風(fēng)格的。但是事實(shí)其實(shí)往往是實(shí)施得并不到位。對(duì)于你的 Restful 是否是真的 Restful,可以參考這篇文章,對(duì)于 Restful 風(fēng)格 API 的成熟度進(jìn)行了四個(gè)層次的分析: Richardson Maturity Model steps toward the glory of REST。

如果使用Springboot的話,無論使用什么服務(wù)發(fā)現(xiàn)機(jī)制,都可以通過 Spring 的RestTemplate來做基礎(chǔ)的Http請(qǐng)求封裝。

如果使用的前文提到的Netflix-Eureka的話,可以使用Netflix-Feign。Feign是一個(gè)聲明式 Web Service 客戶端??蛻舳说呢?fù)載均衡使用 Netflix-Ribbon。

異步

在微服務(wù)架構(gòu)中,排除純粹的“事件驅(qū)動(dòng)架構(gòu)”,使用消息隊(duì)列的場景一般是為了進(jìn)行微服務(wù)之間的解耦。服務(wù)之間不需要了解是由哪個(gè)服務(wù)實(shí)例來消費(fèi)或者發(fā)布消息。

只要處理好自己領(lǐng)域范圍的邏輯,然后通過消息通道來發(fā)布,或者訂閱自己關(guān)注的消息就可以。目前開源的消息隊(duì)列技術(shù)也很多。比如 Apache Kafka,RabbitMQ,Apache ActiveMQ 以及阿里巴巴的 RocketMQ 目前已經(jīng)成為 Apache 項(xiàng)目之一。消息隊(duì)列的模型中,主要的三個(gè)組成就是:

Producer:生產(chǎn)消息,將消息寫入 channel。

Message Broker:消息代理,將寫入 channel 的消息按隊(duì)列的結(jié)構(gòu)進(jìn)行管理。負(fù)責(zé)存儲(chǔ)/轉(zhuǎn)發(fā)消息。Broker 一般是需要單獨(dú)搭建、配置的集群,而且必須是高可用的。

Consumer:消息的消費(fèi)者。目前大多數(shù)的消息隊(duì)列都是保證消息至少被消費(fèi)一次。所以根據(jù)使用的消息隊(duì)列設(shè)施不同,消費(fèi)者要做好冪等。

不同的消息隊(duì)列的實(shí)現(xiàn),消息模型不同。各個(gè)框架的特性也不同:

RabbitMQ

RabbitMQ 是基于 AMQP 協(xié)議的開源實(shí)現(xiàn),由以高性能、可伸縮性出名的 Erlang 寫成。目前客戶端支持 Java、.Net/C# 和 Erlang。在 AMQP(Advanced Message Queuing Protocol)的組件中,Broker 中可以包含多個(gè)Exchange(交換機(jī))組件。Exchange 可以綁定多個(gè) Queue 以及其他 Exchange。

消息會(huì)按照 Exchange 中設(shè)置的 Routing 規(guī)則,發(fā)送到相應(yīng)的 Message Queue。在 Consumer 消費(fèi)了這個(gè)消息之后,會(huì)跟 Broker 建立連接。發(fā)送消費(fèi)消息的通知。則 Message Queue 才會(huì)將這個(gè)消息移除。

Kafka

Kafka 是一個(gè)高性能的基于發(fā)布/訂閱的跨語言分布式消息系統(tǒng)。Kafka 的開發(fā)語言為 Scala。其比較重要的特性是:

以時(shí)間復(fù)雜度為O(1)的方式快速消息持久化;

高吞吐率;

支持服務(wù)間的消息分區(qū),及分布式消費(fèi),同時(shí)保證消息順序傳輸;

支持在線水平擴(kuò)展,自帶負(fù)載均衡;

支持只消費(fèi)且僅消費(fèi)一次(Exactly Once)模式等等。

說個(gè)缺點(diǎn): 管理界面是個(gè)比較雞肋了點(diǎn),可以使用開源的kafka-manager

其高吞吐的特性,除了可以作為微服務(wù)之間的消息隊(duì)列,也可以用于日志收集, 離線分析, 實(shí)時(shí)分析等。

Kafka 官方提供了 Java 版本的客戶端 API,Kafka 社區(qū)目前也支持多種語言,包括 PHP、Python、Go、C/C++、Ruby、NodeJS 等。

ActiveMQ

ActiveMQ 是基于 JMS(Java Messaging Service)實(shí)現(xiàn)的 JMSProvider。JMS主要提供了兩種類型的消息:點(diǎn)對(duì)點(diǎn)(Point-to-Point)以及發(fā)布/訂閱(Publish/Subscribe)。目前客戶端支持 Java、C、C++、 C#、Ruby、Perl、Python、PHP。而且 ActiveMQ 支持多種協(xié)議:Stomp、AMQP、MQTT 以及 OpenWire。

RocketMQ/ONS

RocketMQ 是由阿里巴巴研發(fā)開源的高可用分布式消息隊(duì)列。ONS是提供商業(yè)版的高可用集群。ONS 支持 pull/push??芍С种鲃?dòng)推送,百億級(jí)別消息堆積。ONS 支持全局的順序消息,以及有友好的管理頁面,可以很好的監(jiān)控消息隊(duì)列的消費(fèi)情況,并且支持手動(dòng)觸發(fā)消息多次重發(fā)。

小結(jié)

通過上篇的微服務(wù)的服務(wù)發(fā)現(xiàn)機(jī)制,加上 Restful API,可以解決微服務(wù)間的同步方式的進(jìn)程間通信。當(dāng)然,既然使用了微服務(wù),就希望所有的微服務(wù)能有合理的限界上下文(系統(tǒng)邊界)。

微服務(wù)之間的同步通信應(yīng)盡量避免,以防止服務(wù)間的領(lǐng)域模型互相侵入。為了避免這種情況,就可以在微服務(wù)的架構(gòu)中使用一層API gateway(會(huì)在下文介紹)。所有的微服務(wù)通過API gateway進(jìn)行統(tǒng)一的請(qǐng)求的轉(zhuǎn)發(fā),合并。并且API gateway也需要支持同步請(qǐng)求,以及NIO的異步的請(qǐng)求(可以提高請(qǐng)求合并的效率以及性能)。

消息隊(duì)列可以用于微服務(wù)間的解耦。在基于Docker的微服務(wù)的服務(wù)集群環(huán)境下,網(wǎng)絡(luò)環(huán)境會(huì)比一般的分布式集群復(fù)雜。選擇一種高可用的分布式消息隊(duì)列實(shí)現(xiàn)即可。如果自己搭建諸如Kafka、RabbitMQ集群環(huán)境的話,那對(duì)于Broker設(shè)施的高可用性會(huì)要求很高。

基于Springboot的微服務(wù)的話,比較推薦使用Kafka 或者ONS。雖然ONS是商用的,但是易于管理以及穩(wěn)定性高,尤其對(duì)于必要場景才依賴于消息隊(duì)列進(jìn)行通信的微服務(wù)架構(gòu)來說,會(huì)更適合。如果考慮到會(huì)存在日志收集,實(shí)時(shí)分析等場景,也可以搭建Kafka集群。目前阿里云也有了基于Kafka的商用集群設(shè)施。

使用 API Gateway 處理微服務(wù)請(qǐng)求轉(zhuǎn)發(fā)、合并

前面主要介紹了如何解決微服務(wù)的服務(wù)發(fā)現(xiàn)和通信問題。在微服務(wù)的架構(gòu)體系中,使用DDD思想劃分服務(wù)間的限界上下文的時(shí)候,會(huì)盡量減少微服務(wù)之間的調(diào)用。為了解耦微服務(wù),便有了基于API Gateway方式的優(yōu)化方案。

解耦微服務(wù)的調(diào)用

比如,下面一個(gè)常見的需求場景——“用戶訂單列表”的一個(gè)聚合頁面。需要請(qǐng)求”用戶服務(wù)“獲取基礎(chǔ)用戶信息,以及”訂單服務(wù)“獲取訂單信息,再通過請(qǐng)求“商品服務(wù)”獲取訂單列表中的商品圖片、標(biāo)題等信息。如下圖所示的場景 :

如果讓客戶端(比如H5、Android、iOS)發(fā)出多個(gè)請(qǐng)求來解決多個(gè)信息聚合,則會(huì)增加客戶端的復(fù)雜度。比較合理的方式就是增加API Gateway層。API Gateway跟微服務(wù)一樣,也可以部署、運(yùn)行在Docker容器中,也是一個(gè)Springboot應(yīng)用。如下,通過Gateway API進(jìn)行轉(zhuǎn)發(fā)后:

所有的請(qǐng)求的信息,由Gateway進(jìn)行聚合,Gateway也是進(jìn)入系統(tǒng)的唯一節(jié)點(diǎn)。并且Gateway和所有微服務(wù),以及提供給客戶端的也是Restful風(fēng)格API。Gateway層的引入可以很好的解決信息的聚合問題。而且可以更好得適配不同的客戶端的請(qǐng)求,比如H5的頁面不需要展示用戶信息,而iOS客戶端需要展示用戶信息,則只需要添加一個(gè)Gateway API請(qǐng)求資源即可,微服務(wù)層的資源不需要進(jìn)行變更。

API Gateway 的特點(diǎn)

API gateway除了可以進(jìn)行請(qǐng)求的合并、轉(zhuǎn)發(fā)。還需要有其他的特點(diǎn),才能成為一個(gè)完整的Gateway。

響應(yīng)式編程

Gateway是所有客戶端請(qǐng)求的入口。類似Facade模式。為了提高請(qǐng)求的性能,最好選擇一套非阻塞I/O的框架。在一些需要請(qǐng)求多個(gè)微服務(wù)的場景下,對(duì)于每個(gè)微服務(wù)的請(qǐng)求不一定需要同步。前文舉例的“用戶訂單列表”的例子中,獲取用戶信息,以及獲取訂單列表,就是兩個(gè)獨(dú)立請(qǐng)求。

只有獲取訂單的商品信息,需要等訂單信息返回之后,根據(jù)訂單的商品id列表再去請(qǐng)求商品微服務(wù)。為了減少整個(gè)請(qǐng)求的響應(yīng)時(shí)間,需要Gateway能夠并發(fā)處理相互獨(dú)立的請(qǐng)求。一種解決方案就是采用響應(yīng)式編程。

目前使用Java技術(shù)棧的響應(yīng)式編程方式有,Java8的CompletableFuture,以及ReactiveX提供的基于JVM的實(shí)現(xiàn)-RxJava。

ReactiveX是一個(gè)使用可觀察數(shù)據(jù)流進(jìn)行異步編程的編程接口,ReactiveX結(jié)合了觀察者模式、迭代器模式和函數(shù)式編程的精華。除了RxJava還有RxJS,RX.NET等多語言的實(shí)現(xiàn)。

對(duì)于Gateway來說,RxJava提供的Observable可以很好的解決并行的獨(dú)立I/O請(qǐng)求,并且如果微服務(wù)項(xiàng)目中使用Java8,團(tuán)隊(duì)成員會(huì)對(duì)RxJava的函數(shù)學(xué)習(xí)吸收會(huì)更快。同樣基于Lambda風(fēng)格的響應(yīng)式編程,可以使代碼更加簡潔。關(guān)于RxJava的詳細(xì)介紹可以可以閱讀RxJava文檔和教程。

通過響應(yīng)式編程的Observable模式,可以很簡潔、方便得創(chuàng)建事件流、數(shù)據(jù)流,以及用簡潔的函數(shù)進(jìn)行數(shù)據(jù)的組合和轉(zhuǎn)換,同時(shí)可以訂閱任何可觀察的數(shù)據(jù)流并執(zhí)行操作。

通過使用RxJava,“用戶訂單列表”的資源請(qǐng)求時(shí)序圖:

響應(yīng)式編程可以更好的處理各種線程同步、并發(fā)請(qǐng)求,通過Observables和Schedulers提供了透明的數(shù)據(jù)流、事件流的線程處理。在敏捷開發(fā)模式下,響應(yīng)式編程使代碼更加簡潔,更好維護(hù)。

鑒權(quán)

Gateway作為系統(tǒng)的唯一入口,基于微服務(wù)的所有鑒權(quán),都可以圍繞Gateway去做。在Springboot工程中,基礎(chǔ)的授權(quán)可以使用spring-boot-starter-security以及Spring Security(Spring Security也可以集成在Spring MVC項(xiàng)目中)。

Spring Security主要使用AOP,對(duì)資源請(qǐng)求進(jìn)行攔截,內(nèi)部維護(hù)了一個(gè)角色的Filter Chain。因?yàn)槲⒎?wù)都是通過Gateway請(qǐng)求的,所以微服務(wù)的@Secured可以根據(jù)Gateway中不同的資源的角色級(jí)別進(jìn)行設(shè)置。

Spring Security提供了基礎(chǔ)的角色的校驗(yàn)接口規(guī)范。但客戶端請(qǐng)求的Token信息的加密、存儲(chǔ)以及驗(yàn)證,需要應(yīng)用自己完成。對(duì)于Token加密信息的存儲(chǔ)可以使用Redis。

這里再多提一點(diǎn),為了保證一些加密信息的可變性,最好在一開始設(shè)計(jì)Token模塊的時(shí)候就考慮到支持多個(gè)版本密鑰,以防止萬一內(nèi)部密鑰被泄露(之前聽一個(gè)朋友說其公司的Token加密代碼被員工公布出去)。

至于加密算法,以及具體的實(shí)現(xiàn)在此就不再展開。 在Gateway鑒權(quán)通過之后,解析后的token信息可以直接傳遞給需要繼續(xù)請(qǐng)求的微服務(wù)層。

如果應(yīng)用需要授權(quán)(對(duì)資源請(qǐng)求需要管理不同的角色、權(quán)限),也只要在Gateway的Rest API基礎(chǔ)上基于AOP思想來做即可。統(tǒng)一管理鑒權(quán)和授權(quán),這也是使用類似Facade模式的Gateway API的好處之一。

負(fù)載均衡

API Gateway跟Microservice一樣,作為Springboot應(yīng)用,提供Rest api。所以同樣運(yùn)行在Docker容器中。Gateway和微服務(wù)之間的服務(wù)發(fā)現(xiàn)還是可以采用前文所述的客戶端發(fā)現(xiàn)模式,或者服務(wù)端發(fā)現(xiàn)模式。

在集群環(huán)境下,API Gateway 可以暴露統(tǒng)一的端口,其實(shí)例會(huì)運(yùn)行在不同IP的服務(wù)器上。因?yàn)槲覀兪鞘褂冒⒗镌频腅CS作為容器的基礎(chǔ)設(shè)施,所以在集群環(huán)境的負(fù)載均衡也是使用阿里云的負(fù)載均衡SLB,域名解析也使用AliyunDNS。下圖是一個(gè)簡單的網(wǎng)絡(luò)請(qǐng)求的示意:

在實(shí)踐中,為了不暴露服務(wù)的端口和資源地址,也可以在服務(wù)集群中再部署Nginx服務(wù)作為反向代理,外部的負(fù)載均衡設(shè)施比如SLB可以將請(qǐng)求轉(zhuǎn)發(fā)到Nginx服務(wù)器,請(qǐng)求通過Nginx再轉(zhuǎn)發(fā)給Gateway端口。如果是自建機(jī)房的集群,就需要搭建高可用的負(fù)載均衡中心。為了應(yīng)對(duì)跨機(jī)器請(qǐng)求,最好使用Consul,Consul(Consul Template)+Registor+Haproxy來做服務(wù)發(fā)現(xiàn)和負(fù)載均衡中心。

緩存

對(duì)于一些高QPS的請(qǐng)求,可以在API Gateway做多級(jí)緩存。分布式的緩存可以使用Redis,Memcached等。如果是一些對(duì)實(shí)時(shí)性要求不高的,變化頻率不高但是高QPS的頁面級(jí)請(qǐng)求,也可以在Gateway層做本地緩存。而且Gateway可以讓緩存方案更靈活和通用。

API Gateway的錯(cuò)誤處理

在Gateway的具體實(shí)現(xiàn)過程中,錯(cuò)誤處理也是一個(gè)很重要的事情。對(duì)于Gateway的錯(cuò)誤處理,可以使用Hystrix來處理請(qǐng)求的熔斷。并且RxJava自帶的Return回調(diào)也可以方便得處理錯(cuò)誤信息的返回。對(duì)于熔斷機(jī)制,需要處理以下幾個(gè)方面:

服務(wù)請(qǐng)求的容錯(cuò)處理

作為一個(gè)合理的Gateway,其應(yīng)該只負(fù)責(zé)處理數(shù)據(jù)流、事件流,而不應(yīng)該處理業(yè)務(wù)邏輯。在處理多個(gè)微服務(wù)的請(qǐng)求時(shí),會(huì)出現(xiàn)微服務(wù)請(qǐng)求的超時(shí)、不可用的情況。在一些特定的場景下, 需要能夠合理得處理部分失敗。比如上例中的“用戶訂單列表”,當(dāng)“User”微服務(wù)出現(xiàn)錯(cuò)誤時(shí),不應(yīng)該影響“Order”數(shù)據(jù)的請(qǐng)求。

最好的處理方式就是給當(dāng)時(shí)錯(cuò)誤的用戶信息請(qǐng)求返回一個(gè)默認(rèn)的數(shù)據(jù),比如顯示一個(gè)默認(rèn)頭像,默認(rèn)用戶昵稱。然后對(duì)于請(qǐng)求正常的訂單,以及商品信息給與正確的數(shù)據(jù)返回。

如果是一個(gè)關(guān)鍵的微服務(wù)請(qǐng)求異常,比如當(dāng)“Order”領(lǐng)域的微服務(wù)異常時(shí),則應(yīng)該給客戶端一個(gè)錯(cuò)誤碼,以及合理的錯(cuò)誤提示信息。這樣的處理可以盡量在部分系統(tǒng)不可用時(shí)提升用戶體驗(yàn)。使用RxJava時(shí),具體的實(shí)現(xiàn)方式就是針對(duì)不同的客戶端請(qǐng)求的情況,寫好Return,做好錯(cuò)誤數(shù)據(jù)兼容即可。

異常的捕捉和記錄

Gateway主要是做請(qǐng)求的轉(zhuǎn)發(fā)、合并。為了能清楚得排查問題,定位到具體哪個(gè)服務(wù)、甚至是哪個(gè)Docker容器的問題,需要Gateway能對(duì)不同類型的異常、業(yè)務(wù)錯(cuò)誤進(jìn)行捕捉和記錄。

如果使用FeignClient來請(qǐng)求微服務(wù)資源,可以通過對(duì)ErrorDecoder接口的實(shí)現(xiàn),來針對(duì)Response結(jié)果進(jìn)行進(jìn)一步的過濾處理,以及在日志中記錄下所有請(qǐng)求信息。如果是使用Spring Rest Template,則可以通過定義一個(gè)定制化的RestTempate,并對(duì)返回的ResponseEntity進(jìn)行解析。在返回序列化之后的結(jié)果對(duì)象之前,對(duì)錯(cuò)誤信息進(jìn)行日志記錄。

超時(shí)機(jī)制

Gateway線程中大多是IO線程,為了防止因?yàn)槟骋晃⒎?wù)請(qǐng)求阻塞,導(dǎo)致Gateway過多的等待線程,耗盡線程池、隊(duì)列等系統(tǒng)資源。需要Gateway中提供超時(shí)機(jī)制,對(duì)超時(shí)接口能進(jìn)行優(yōu)雅的服務(wù)降級(jí)。

在SpringCloud的Feign項(xiàng)目中集成了Hystrix。Hystrix提供了比較全面的超時(shí)處理的熔斷機(jī)制。默認(rèn)情況下,超時(shí)機(jī)制是開啟的。除了可以配置超時(shí)相關(guān)的參數(shù),Netflix還提供了基于Hytrix的實(shí)時(shí)監(jiān)控Netflix -Dashboard,并且集群服務(wù)只需再附加部署Netflix-Turbine。通用的Hytrix的配置項(xiàng)可以參考Hystrix-Configuration。

如果是使用RxJava的Observable的響應(yīng)式編程,想對(duì)不同的請(qǐng)求設(shè)置不同的超時(shí)時(shí)間,可以直接在Observable的timeout()方法的參數(shù)進(jìn)行設(shè)置回調(diào)的方法以及超時(shí)時(shí)間等。

重試機(jī)制

對(duì)于一些關(guān)鍵的業(yè)務(wù),在請(qǐng)求超時(shí)時(shí),為了保證正確的數(shù)據(jù)返回,需要Gateway能提供重試機(jī)制。如果使用SpringCloudFeign,則其內(nèi)置的Ribbon,會(huì)提供的默認(rèn)的重試配置,可以通過設(shè)置spring.cloud.loadbalancer.retry.enabled=false將其關(guān)閉。

Ribbon提供的重試機(jī)制會(huì)在請(qǐng)求超時(shí)或者socket read timeout觸發(fā),除了設(shè)置重試,也可以定制重試的時(shí)間閥值以及重試次數(shù)等。

對(duì)于除了使用Feign,也使用Spring RestTemplate的應(yīng)用,可以通過自定義的RestTemplate,對(duì)于返回的ResponseEntity對(duì)象進(jìn)行結(jié)果解析,如果請(qǐng)求需要重試(比如某個(gè)固定格式的error-code的方式識(shí)別重試策略),則通過Interceptor進(jìn)行請(qǐng)求攔截,以及回調(diào)的方式invoke多次請(qǐng)求。

小結(jié)

對(duì)于微服務(wù)的架構(gòu),通過一個(gè)獨(dú)立的API Gateway,可以進(jìn)行統(tǒng)一的請(qǐng)求轉(zhuǎn)發(fā)、合并以及協(xié)議轉(zhuǎn)換??梢愿`活得適配不同客戶端的請(qǐng)求數(shù)據(jù)。而且對(duì)于不同客戶端(比如H5和iOS的展示數(shù)據(jù)不同)、不同版本兼容的請(qǐng)求,可以很好地在Gateway進(jìn)行屏蔽,讓微服務(wù)更加純粹。微服務(wù)只要關(guān)注內(nèi)部的領(lǐng)域服務(wù)的設(shè)計(jì),事件的處理。

API gateway還可以對(duì)微服務(wù)的請(qǐng)求進(jìn)行一定的容錯(cuò)、服務(wù)降級(jí)。使用響應(yīng)式編程來實(shí)現(xiàn)API gateway可以使線程同步、并發(fā)的代碼更簡潔,更易于維護(hù)。在對(duì)于微服務(wù)的請(qǐng)求可以統(tǒng)一通過FeignClint。代碼也會(huì)很有層次。如下圖,是一個(gè)示例的請(qǐng)求的類層次。

Clint負(fù)責(zé)集成服務(wù)發(fā)現(xiàn)(對(duì)于使用Eureka自注冊(cè)方式)、負(fù)載均衡以及發(fā)出請(qǐng)求,并獲取ResponseEntity對(duì)象。

Translator將ResponseEntity轉(zhuǎn)換成Observable<XDTO>對(duì)象,以及對(duì)異常進(jìn)行統(tǒng)一日志采集,類似于DDD中防腐層的概念。

Adapter調(diào)用各個(gè)Translator,使用Observable函數(shù),對(duì)請(qǐng)求的數(shù)據(jù)流進(jìn)行合并。如果存在多個(gè)數(shù)據(jù)的組裝,可以增加一層Assembler專門處理DTO對(duì)象到Model的轉(zhuǎn)換。

Controller,提供Restful資源的管理,每個(gè)Controller只請(qǐng)求唯一的一個(gè)Adapter方法。

微服務(wù)的持續(xù)集成部署

主要介紹了微服務(wù)的服務(wù)發(fā)現(xiàn)、服務(wù)通信以及API Gateway。整體的微服務(wù)架構(gòu)的模型初見。在實(shí)際的開發(fā)、測試以及生產(chǎn)環(huán)境中。使用Docker實(shí)現(xiàn)微服務(wù),集群的網(wǎng)絡(luò)環(huán)境會(huì)更加復(fù)雜。

微服務(wù)架構(gòu)本身就意味著需要對(duì)若干個(gè)容器服務(wù)進(jìn)行治理,每個(gè)微服務(wù)都應(yīng)可以獨(dú)立部署、擴(kuò)容、監(jiān)控。下面會(huì)繼續(xù)介紹如何進(jìn)行Docker微服務(wù)的持續(xù)集成部署(CI/CD)。

鏡像倉庫

用Docker來部署微服務(wù),需要將微服務(wù)打包成Docker鏡像,就如同部署在Web server打包成war文件一樣。只不過Docker鏡像運(yùn)行在Docker容器中。

如果是Springboot服務(wù),則會(huì)直接將包含Apache Tomcat server的Springboot,以及包含Java運(yùn)行庫的編譯后的Java應(yīng)用打包成Docker鏡像。

為了能統(tǒng)一管理打包以及分發(fā)(pull/push)鏡像。企業(yè)一般需要建立自己的鏡像私庫。實(shí)現(xiàn)方式也很簡單??梢栽诜?wù)器上直接部署Docker hub的鏡像倉庫的容器版Registry2。目前最新的版本是V2。

代碼倉庫

代碼的提交、回滾等管理,也是項(xiàng)目持續(xù)集成的一環(huán)。一般也是需要建立企業(yè)的代碼倉庫的私庫??梢允褂肧VN,GIT等代碼版本管理工具。

目前公司使用的是Gitlab,通過Git的Docker鏡像安裝、部署操作也很便捷。具體步驟可以參考docker gitlab install。為了能快速構(gòu)建、打包,也可將Git和Registry部署在同一臺(tái)服務(wù)器上。

項(xiàng)目構(gòu)建

在Springboot項(xiàng)目中,構(gòu)建工具可以用Maven,或者Gradle。Gradle相比Maven更加靈活,而且Springboot應(yīng)用本身去配置化的特點(diǎn),用基于Groovy的Gradle會(huì)更加適合,DSL本身也比XML更加簡潔高效。

因?yàn)镚radle支持自定義task。所以微服務(wù)的Dockerfile寫好之后,就可以用Gradle的task腳本來進(jìn)行構(gòu)建打包成Docker Image。

目前也有一些開源的Gradle構(gòu)建Docker鏡像的工具,比如Transmode-Gradlew插件。其除了可以對(duì)子項(xiàng)目(單個(gè)微服務(wù))進(jìn)行構(gòu)建Docker鏡像,也可以支持同時(shí)上傳鏡像到遠(yuǎn)程鏡像倉庫。在生產(chǎn)環(huán)境中的build機(jī)器上,可以通過一個(gè)命令直接執(zhí)行項(xiàng)目的build,Docker Image的打包,以及鏡像的push。

容器編排技術(shù)

Docker鏡像構(gòu)建之后,因?yàn)槊總€(gè)容器運(yùn)行著不同的微服務(wù)實(shí)例,容器之間也是隔離部署服務(wù)的。通過編排技術(shù),可以使DevOps輕量化管理容器的部署以及監(jiān)控,以提高容器管理的效率。

目前一些通用的編排工具比如Ansible、Chef、Puppet,也可以做容器的編排。但他們都不是專門針對(duì)容器的編排工具,所以使用時(shí)需要自己編寫一些腳本,結(jié)合Docker的命令。比如Ansible,確實(shí)可以實(shí)現(xiàn)很便利的集群的容器的部署和管理。目前Ansible針對(duì)其團(tuán)隊(duì)自己研發(fā)的容器技術(shù)提供了集成方案:Ansible Container。

集群管理系統(tǒng)將主機(jī)作為資源池,根據(jù)每個(gè)容器對(duì)資源的需求,決定將容器調(diào)度到哪個(gè)主機(jī)上。

目前,圍繞Docker容器的調(diào)度、編排,比較成熟的技術(shù)有Google的Kubernetes(下文會(huì)簡寫k8s),Mesos結(jié)合Marathon管理Docker集群,以及在Docker 1.12.0版本以上官方提供的Docker Swarm。編排技術(shù)是容器技術(shù)的重點(diǎn)之一。選擇一個(gè)適合自己團(tuán)隊(duì)的容器編排技術(shù)也可以使運(yùn)維更高效、更自動(dòng)化。

Docker Compose

Docker Compose是一個(gè)簡單的Docker容器的編排工具,通過YAML文件配置需要運(yùn)行的應(yīng)用,然后通過compose up命令啟動(dòng)多個(gè)服務(wù)對(duì)應(yīng)的容器實(shí)例。Docker中沒有集成Compose,需要另外安裝。

Compose可以用于微服務(wù)項(xiàng)目的持續(xù)集成,但其不適合大型集群的容器管理,大集群中,可以Compose結(jié)合Ansible做集群資源管理,以及服務(wù)治理。

對(duì)于集群中服務(wù)器不多的情況,可以使用Compose,其使用步驟主要是:

結(jié)合微服務(wù)運(yùn)行環(huán)境,定義好服務(wù)的Dockerfile

根據(jù)服務(wù)鏡像、端口、運(yùn)行變量等編寫docker-compose.yml文件,以使服務(wù)可以一起部署,運(yùn)行

運(yùn)行docker-compose up 命令啟動(dòng)并且進(jìn)入容器實(shí)例,如果需要使用后臺(tái)進(jìn)程方式運(yùn)行,使用docker-compose up -d即可。

Docker Swarm

在16年,Docker的1.12版本出來之后,使用新版本的Docker,就自帶Docker swarm mode了。不需要額外安裝任何插件工具??梢钥闯鋈ツ觊_始Docker團(tuán)隊(duì)也開始重視服務(wù)編排技術(shù),通過內(nèi)置Swarm mode,也要搶占一部分服務(wù)編排市場。

如果團(tuán)隊(duì)開始使用新版本的Docker,可以選擇Docker swarm mode來進(jìn)行集群化的容器調(diào)度和管理。Swarm還支持滾動(dòng)更新、節(jié)點(diǎn)間傳輸層安全加密、負(fù)載均衡等。

DockerSwarm的使用示例可以參考之前寫的一篇:使用docker-swarm搭建持續(xù)集成集群服務(wù)。

Kubernetes

Kubernetes是Google開源的容器集群管理系統(tǒng),使用Go語言實(shí)現(xiàn),其提供應(yīng)用部署、維護(hù)、 擴(kuò)展機(jī)制等功能。目前可以在GCE、vShpere、CoreOS、OpenShift、Azure等平臺(tái)使用k8s。

國內(nèi)目前Aliyun也提供了基于k8s的服務(wù)治理平臺(tái)。如果是基于物理機(jī)、虛擬機(jī)搭建的Docker集群的話,也可以直接部署、運(yùn)行k8s。在微服務(wù)的集群環(huán)境下,Kubernetes可以很方便管理跨機(jī)器的微服務(wù)容器實(shí)例。

目前k8s基本是公認(rèn)的最強(qiáng)大開源服務(wù)治理技術(shù)之一。其主要提供以下功能:

自動(dòng)化對(duì)基于Docker對(duì)服務(wù)實(shí)例進(jìn)行部署和復(fù)制

以集群的方式運(yùn)行,可以管理跨機(jī)器的容器,以及滾動(dòng)升級(jí)、存儲(chǔ)編排。

內(nèi)置了基于Docker的服務(wù)發(fā)現(xiàn)和負(fù)載均衡模塊

K8s提供了強(qiáng)大的自我修復(fù)機(jī)制,會(huì)對(duì)崩潰的容器進(jìn)行替換(對(duì)用戶,甚至開發(fā)團(tuán)隊(duì)都無感知),并可隨時(shí)擴(kuò)容、縮容。讓容器管理更加彈性化。

k8s主要通過以下幾個(gè)重要的組件完成彈性容器集群的管理的:

Pod是Kubernetes的最小的管理元素,一個(gè)或多個(gè)容器運(yùn)行在pod中。pod的生命周期很短暫,會(huì)隨著調(diào)度失敗,節(jié)點(diǎn)崩潰,或者其他資源回收時(shí)消亡。

Label是key/value存儲(chǔ)結(jié)構(gòu)的,可以關(guān)聯(lián)pod,主要用來標(biāo)記pod,給服務(wù)分組。微服務(wù)之間通過label選擇器(Selectors)來識(shí)別Pod。

Replication Controller是k8s Master節(jié)點(diǎn)的核心組件。用來確保任何時(shí)候Kubernetes集群中有指定數(shù)量的pod副本(replicas)運(yùn)行。即提供了自我修復(fù)機(jī)制的功能,并且對(duì)縮容擴(kuò)容、滾動(dòng)升級(jí)也很有用。

Service是對(duì)一組Pod的策略的抽象。也是k8s管理的基本元素之一。Service通過Label識(shí)別一組Pod。創(chuàng)建時(shí)也會(huì)創(chuàng)建一個(gè)本地集群的DNS(存儲(chǔ)Service對(duì)應(yīng)的Pod的服務(wù)地址)。所以在客戶端請(qǐng)求通過請(qǐng)求DNS來獲取一組當(dāng)前可用的Pods的ip地址。之后通過每個(gè)Node中運(yùn)行的kube-proxy將請(qǐng)求轉(zhuǎn)發(fā)給其中一個(gè)Pod。這層負(fù)載均衡是透明的,但是目前的k8s的負(fù)載均衡策略還不是很完善,默認(rèn)是隨機(jī)的方式。

小結(jié)

微服務(wù)架構(gòu)體系中,一個(gè)合適的持續(xù)集成的工具,可以很好得提升團(tuán)隊(duì)的運(yùn)維、開發(fā)效率。目前類似Jenkins也有針對(duì)Docker的持續(xù)集成的插件,但是還是存在很多不完善。所以建議還是選擇專門應(yīng)對(duì)Docker容器編排技術(shù)的Swarm,k8s,Mesos。或者多個(gè)技術(shù)結(jié)合起來,比如Jenkins做CI+k8s做CD。

Swarm,k8s,Mesos各有各的特性,他們對(duì)于容器的持續(xù)部署、管理以及監(jiān)控都提供了支持。Mesos還支持?jǐn)?shù)據(jù)中心的管理。Docker swarm mode擴(kuò)展了現(xiàn)有的Docker API,通過Docker Remote API的調(diào)用和擴(kuò)展,可以調(diào)度容器運(yùn)行到指定的節(jié)點(diǎn)。

Kubernetes則是目前市場規(guī)模最大的編排技術(shù),目前很多大公司也都加入到了k8s家族,k8s應(yīng)對(duì)集群應(yīng)用的擴(kuò)展、維護(hù)和管理更加靈活,但是負(fù)載均衡策略比較粗糙。而Mesos更專注于通用調(diào)度,提供了多種調(diào)度器。

對(duì)于服務(wù)編排,還是要選擇最適合自己團(tuán)隊(duì)的,如果初期機(jī)器數(shù)量很少,集群環(huán)境不復(fù)雜也可以用Ansible+Docker Compose,再加上Gitlab CI來做持續(xù)集成。

服務(wù)集群的解決方案

企業(yè)在實(shí)踐使用Docker部署、運(yùn)行微服務(wù)應(yīng)用的時(shí)候,無論是一開始就布局微服務(wù)架構(gòu),或者從傳統(tǒng)的單應(yīng)用架構(gòu)進(jìn)行微服務(wù)化遷移。都需要能夠處理復(fù)雜的集群中的服務(wù)調(diào)度、編排、監(jiān)控等問題。下面主要為介紹在分布式的服務(wù)集群下,如何更安全、高效得使用Docker,以及在架構(gòu)設(shè)計(jì)上,需要考慮的方方面面。

負(fù)載均衡

這里說的是集群中的負(fù)載均衡,如果是純服務(wù)端API的話就是指Gateway API的負(fù)載均衡,如果使用了Nginx的話,則是指Nginx的負(fù)載均衡。我們目前使用的是阿里云的負(fù)載均衡服務(wù)SLB。

其中一個(gè)主要原因是可以跟DNS域名服務(wù)進(jìn)行綁定。對(duì)于剛開始進(jìn)行創(chuàng)業(yè)的公司來說,可以通過Web界面來設(shè)置負(fù)載均衡的權(quán)重,比較便于部分發(fā)布、測試驗(yàn)證,以及健康檢查監(jiān)控等等。從效率和節(jié)約運(yùn)維成本上來說都是個(gè)比較適合的選擇。

如果自己搭建七層負(fù)載均衡如使用Nginx或Haproxy的話,也需要保證負(fù)責(zé)負(fù)載均衡的集群也是高可用的,以及提供便捷的集群監(jiān)控,藍(lán)綠部署等功能。

持久化及緩存

關(guān)系型數(shù)據(jù)庫(RDBMS)

對(duì)于微服務(wù)來說,使用的存儲(chǔ)技術(shù)主要是根據(jù)企業(yè)的需要。為了節(jié)約成本的話,一般都是選用Mysql,在Mysql的引擎選擇的話建議選擇InnoDB引擎(5.5版本之前默認(rèn)MyISAM)。

InnoDB在處理并發(fā)時(shí)更高效,其查詢性能的差距也可以通過緩存、搜索等方案進(jìn)行彌補(bǔ)。InnoDB處理數(shù)據(jù)拷貝、備份的免費(fèi)方案有binlog,mysqldump。不過要做到自動(dòng)化的備份恢復(fù)、可監(jiān)控的數(shù)據(jù)中心還是需要DBA或者運(yùn)維團(tuán)隊(duì)。

相對(duì)花費(fèi)的成本也較高。如果初創(chuàng)企業(yè),也可以考慮依托一些國內(nèi)外比較大型的云計(jì)算平臺(tái)提供的PaaS服務(wù)。

微服務(wù)一般按照業(yè)務(wù)領(lǐng)域進(jìn)行邊界劃分,所以微服務(wù)最好是一開始就進(jìn)行分庫設(shè)計(jì)。是否需要進(jìn)行分表需要根據(jù)每個(gè)微服務(wù)具體的業(yè)務(wù)領(lǐng)域的發(fā)展以及數(shù)據(jù)規(guī)模進(jìn)行具體分析。但建議對(duì)于比較核心的領(lǐng)域的模型,比如“訂單”提前做好分表字段的設(shè)計(jì)和預(yù)留。

KV模型數(shù)據(jù)庫(Key-Value-stores)

Redis是開源的Key-Value結(jié)構(gòu)的數(shù)據(jù)庫。其基于內(nèi)存,具有高效緩存的性能,同時(shí)也支持持久化。Redis主要有兩種持久化方式。一種是RDB,通過指定時(shí)間間隔生成數(shù)據(jù)集的時(shí)間點(diǎn)快照,從內(nèi)存寫入磁盤進(jìn)行持久化。

RDB方式會(huì)引起一定程度的數(shù)據(jù)丟失,但性能好。另外一種是AOF,其寫入機(jī)制,有點(diǎn)類似InnoDB的binlog,AOF的文件的命令都是以Redis協(xié)議格式保存。這兩種持久化是可以同時(shí)存在的,在Redis重啟時(shí),AOF文件會(huì)被優(yōu)先用于恢復(fù)數(shù)據(jù)。因?yàn)槌志没强蛇x項(xiàng),所以也可以禁用Redis持久化。

在實(shí)際的場景中,建議保留持久化。比如目前比較流行的解決短信驗(yàn)證碼的驗(yàn)證,就可使用Redis。在微服務(wù)架構(gòu)體系中,也可以用Redis處理一些KV數(shù)據(jù)結(jié)構(gòu)的場景。輕量級(jí)的數(shù)據(jù)存儲(chǔ)方案,也很適合本身強(qiáng)調(diào)輕量級(jí)方案的微服務(wù)思想。

我們?cè)趯?shí)踐中,是對(duì)Redis進(jìn)行了緩存、持久化,兩個(gè)功能特征進(jìn)行分庫的。

在集成Springboot項(xiàng)目中會(huì)使用到spring-boot-starter-data-redis來進(jìn)行Redis的數(shù)據(jù)庫連接以及基礎(chǔ)配置、以及spring-data-redis提供的豐富的數(shù)據(jù)APIOperations。

另外,如果是要求高吞吐量的應(yīng)用,可以考慮用Memcached來專門做簡單的KV數(shù)據(jù)結(jié)構(gòu)的緩存。其比較適合大數(shù)據(jù)量的讀取,但支持的數(shù)據(jù)結(jié)構(gòu)類型比較單一。

圖形數(shù)據(jù)庫(Graph Database)

涉及到社交相關(guān)的模型數(shù)據(jù)的存儲(chǔ),圖形數(shù)據(jù)庫是一種相交關(guān)系型數(shù)據(jù)庫更高效、更靈活的選擇。圖形數(shù)據(jù)庫也是Nosql的一種。其和KV不同,存儲(chǔ)的數(shù)據(jù)主要是數(shù)據(jù)節(jié)點(diǎn)(node),具有指向性的關(guān)系(Relationship)以及節(jié)點(diǎn)和關(guān)系上的屬性(Property)。

如果用Java作為微服務(wù)的主開發(fā)語言,最好選擇Neo4j。Neo4j是一種基于Java實(shí)現(xiàn)的支持ACID的圖形數(shù)據(jù)庫。其提供了豐富的JavaAPI。在性能方面,圖形數(shù)據(jù)庫的局部性使遍歷的速度非常快,尤其是大規(guī)模深度遍歷。這個(gè)是關(guān)系型數(shù)據(jù)庫的多表關(guān)聯(lián)無法企及的。

下圖是使用Neo4j的WebUI工具展示的一個(gè)官方Getting started數(shù)據(jù)模型示例。示例中的語句MATCH p=()-[r:DIRECTED]->() RETURN p LIMIT 25是Neo4j提供的查詢語言——Cypher。

在項(xiàng)目使用時(shí)可以集成SpringData的項(xiàng)目Spring Data Neo4j。以及SpringBootStartersspring-boot-starter-data-neo4j

文檔數(shù)據(jù)庫(Document database)

目前應(yīng)用的比較廣泛的開源的面向文檔的數(shù)據(jù)庫可以用Mongodb。Mongo具有高可用、高可伸縮性以及靈活的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ),尤其是對(duì)于Json數(shù)據(jù)結(jié)構(gòu)的存儲(chǔ)。比較適合博客、評(píng)論等模型的存儲(chǔ)。

搜索技術(shù)

在開發(fā)的過程中,有時(shí)候經(jīng)常會(huì)看到有人寫了很長很繞、很難維護(hù)的多表查詢SQL,或者是各種多表關(guān)聯(lián)的子查詢語句。對(duì)于某一領(lǐng)域模型,當(dāng)這種場景多的時(shí)候,就該考慮接入一套搜索方案了。不要什么都用SQL去解決,尤其是查詢的場景。慢查詢語句的問題有時(shí)候甚至?xí)峡錎B,如果DB的監(jiān)控體系做的不到位,可能問題也很難排查。

Elasticsearch是一個(gè)基于Apache Lucene實(shí)現(xiàn)的開源的實(shí)時(shí)分布式搜索和分析引擎。Springboot的項(xiàng)目也提供了集成方式: spring-boot-starter-data-elasticsearch以及spring-data-elasticsearch。

對(duì)于搜索集群的搭建,可以使用Docker。具體搭建方法可以參考用Docker搭建Elasticsearch集群,對(duì)于Springboot項(xiàng)目的集成可以參考在Springboot微服務(wù)中集成搜索服務(wù)。至今,最新版本的SpringDataElasticsearch已經(jīng)支持到了5.x版本的ES,可以解決很多2.x版本的痛點(diǎn)了。

如果是小規(guī)模的搜索集群,可以用三臺(tái)低配置的機(jī)器,然后用ES的Docker進(jìn)項(xiàng)進(jìn)行搭建。也可以使用一些商業(yè)版的PaaS服務(wù)。如何選擇還是要根據(jù)團(tuán)隊(duì)和業(yè)務(wù)的規(guī)模、場景來看。

目前除了ES,使用比較廣泛的開源搜索引擎還有Solr,Solr也基于Lucene,且專注在文本搜索。而ES的文本搜索確實(shí)不如Solr,ES主要專注于對(duì)分布式的支持,并且內(nèi)置了服務(wù)發(fā)現(xiàn)組件Zen來維護(hù)集群狀態(tài),相對(duì)Solr(需要借助類似Zookeeper實(shí)現(xiàn)分布式)部署也更加輕量級(jí)。ES除了分析查詢,還可以集成日志收集以及做分析處理。

消息隊(duì)列

消息隊(duì)列如前篇所述,可以作為很好的微服務(wù)解耦通信方式。在分布式集群的場景下,對(duì)于分布式下的最終一致性也可以提供技術(shù)基礎(chǔ)保障。并且消息隊(duì)列也可以用來處理流量削鋒。

消息隊(duì)列的對(duì)比在此不再贅述。目前公司使用的是阿里云的ONS。因?yàn)槭褂孟㈥?duì)列還是考慮用在對(duì)高可用以及易于管理、監(jiān)控上的要求,所以選擇了安全可靠的消息隊(duì)列平臺(tái)。

安全技術(shù)

安全性是做架構(gòu)需要考慮的基礎(chǔ)?;ヂ?lián)網(wǎng)的環(huán)境復(fù)雜,保護(hù)好服務(wù)的安全,也是對(duì)用戶的基本承諾。安全技術(shù)涉及到的范圍比較廣,本文選幾個(gè)常見問題以及常用方式來簡單介紹下。

服務(wù)實(shí)例安全

分布式集群本身就是對(duì)于服務(wù)實(shí)例安全的一種保障。一臺(tái)服務(wù)器或者某一個(gè)服務(wù)實(shí)例出現(xiàn)問題的時(shí)候,負(fù)載均衡可以將請(qǐng)求轉(zhuǎn)發(fā)到其他可用的服務(wù)實(shí)例。但很多企業(yè)是自建機(jī)房,而且是單機(jī)房的,這種布局其實(shí)比較危險(xiǎn)。

因?yàn)榉?wù)器的備份容災(zāi)也得不到完整的保障。最怕的就是數(shù)據(jù)庫也是在同一機(jī)房,主備全都在一起。不單是安全性得不到很高的保障,平常的運(yùn)維花銷也會(huì)比較大。而且需要注意配置防火墻安全策略。

如果可以,盡量使用一些高可用、高可伸縮的穩(wěn)定性IaaS平臺(tái)。

網(wǎng)絡(luò)安全

1. 預(yù)防網(wǎng)絡(luò)攻擊

目前主要的網(wǎng)絡(luò)攻擊有一下幾種:

SQL注入:根據(jù)不同的持久層框架,應(yīng)對(duì)策略不同。如果使用JPA,則只要遵循JPA的規(guī)范,基本不用擔(dān)心。

XSS攻擊:做好參數(shù)的轉(zhuǎn)義處理和校驗(yàn)。具體參考XSS預(yù)防

CSRF攻擊:做好Http的Header信息的Token、Refer驗(yàn)證。具體參考CSRF預(yù)防

DDOS攻擊:大流量的DDoS攻擊,一般是采用高防IP。也可以接入一些云計(jì)算平臺(tái)的高防IP。

以上只是列舉了幾種常見的攻擊,想要深入了解的可以多看看REST安全防范表。在網(wǎng)絡(luò)安全領(lǐng)域,一般很容易被初創(chuàng)企業(yè)忽視,如果沒有一個(gè)運(yùn)維安全團(tuán)隊(duì),最好使用類似阿里云-云盾之類的產(chǎn)品。省心省成本。

2. 使用安全協(xié)議

這個(gè)不用多說,無論是對(duì)于使用Restful API的微服務(wù)通信,還是使用的CDN或者使用的DNS服務(wù)。涉及到Http協(xié)議的,建議都統(tǒng)一使用Https。無論是什么規(guī)模的應(yīng)用,都要防范流量劫持,否則將會(huì)給用戶帶來很不好的使用體驗(yàn)。

3. 鑒權(quán)

關(guān)于微服務(wù)的鑒權(quán)前面API Gateway已經(jīng)有介紹。除了微服務(wù)本身之外,我們使用的一些如Mysql,Redis,Elasticsearch,Eureka等服務(wù),也需要設(shè)置好鑒權(quán),并且盡量通過內(nèi)網(wǎng)訪問。不要對(duì)外暴露過多的端口。對(duì)于微服務(wù)的API Gateway,除了鑒權(quán),最好前端通過Nginx反向代理來請(qǐng)求API層。

日志采集、監(jiān)控

基于容器技術(shù)的微服務(wù)的監(jiān)控體系面臨著更復(fù)雜的網(wǎng)絡(luò)、服務(wù)環(huán)境。日志采集、監(jiān)控如何能對(duì)微服務(wù)減少侵入性、對(duì)開發(fā)者更透明,一直是很多微服務(wù)的DevOps在不斷思考和實(shí)踐的。

1. 微服務(wù)日志的采集

微服務(wù)的API層的監(jiān)控,需要從API Gateway到每個(gè)微服務(wù)的調(diào)用路徑的跟蹤,采集以及分析。使用Rest API的話,為了對(duì)所有請(qǐng)求進(jìn)行采集,可以使用Spring Web的OncePerRequestFilter對(duì)所有請(qǐng)求進(jìn)行攔截,在采集日志的時(shí)候,也最好對(duì)請(qǐng)求的rt進(jìn)行記錄。

除了記錄access,request等信息,還需要對(duì)API調(diào)用進(jìn)行請(qǐng)求跟蹤。如果單純記錄每個(gè)服務(wù)以及Gateway的日志,那么當(dāng)Gateway Log出現(xiàn)異常的時(shí)候,就不知道其具體是微服務(wù)的哪個(gè)容器實(shí)例出現(xiàn)了問題。如果容器達(dá)到一定數(shù)量,也不可能排查所有容器以及服務(wù)實(shí)例的日志。比較簡單的解決方式就是對(duì)log信息都append一段含有容器信息的、唯一可標(biāo)識(shí)的Trace串。

日志采集之后,還需要對(duì)其進(jìn)行分析。如果使用E.L.K的技術(shù)體系,就可以靈活運(yùn)用Elasticsearch的實(shí)時(shí)分布式特性。Logstash可以進(jìn)行日志進(jìn)行收集、分析,并將數(shù)據(jù)同步到Elasticsearch。Kibana結(jié)合Logstash和ElasticSearch,提供良好的便于日志分析的WebUI,增強(qiáng)日志數(shù)據(jù)的可視化管理。

對(duì)于數(shù)據(jù)量大的日志的采集,為了提升采集性能,需要使用上文提到的消息隊(duì)列。優(yōu)化后的架構(gòu)如下:

2. 基礎(chǔ)服務(wù)的調(diào)用日志采集

通過對(duì)微服務(wù)的所有Rest API的日志采集、分析可以監(jiān)控請(qǐng)求信息。

在服務(wù)內(nèi)部,對(duì)于中間件、基礎(chǔ)設(shè)施(包括Redis,Mysql,Elasticsearch等)調(diào)用的性能的日志采集和分析也是必要的。

對(duì)于中間件服務(wù)的日志采集,我們目前可以通過動(dòng)態(tài)代理的方式,對(duì)于服務(wù)調(diào)用的如cache、repository(包括搜索和DB)的基礎(chǔ)方法,進(jìn)行攔截及回調(diào)日志記錄方法。

具體的實(shí)現(xiàn)方式可以采用字節(jié)碼生成框架ASM,關(guān)于方法的邏輯注入,可以參考之前寫的一篇ASM(四) 利用Method 組件動(dòng)態(tài)注入方法邏輯,如果覺得ASM代碼不太好維護(hù),也可以使用相對(duì)API友好的Cglib。

架構(gòu)五要素:

最后,結(jié)合架構(gòu)核心的五要素來回顧下我們?cè)诖罱―ocker微服務(wù)架構(gòu)使用的技術(shù)體系:

高性能 消息隊(duì)列、RxJava異步并發(fā)、分布式緩存、本地緩存、Http的Etag緩存、使用Elasticsearch優(yōu)化查詢、CDN等等。

可用性 容器服務(wù)集群、RxJava的熔斷處理、服務(wù)降級(jí)、消息的冪等處理、超時(shí)機(jī)制、重試機(jī)制、分布式最終一致性等等。

伸縮性 服務(wù)器集群的伸縮、容器編排Kubernetes、數(shù)據(jù)庫分庫分表、Nosql的線性伸縮、搜索集群的可伸縮等等。

擴(kuò)展性 基于Docker的微服務(wù)本身就是為了擴(kuò)展性而生!

安全性 JPA/Hibernate,SpringSecurity、高防IP、日志監(jiān)控、Https、Nginx反向代理、HTTP/2.0等等。

小結(jié)

對(duì)于服務(wù)集群的解決方案,其實(shí)無論是微服務(wù)架構(gòu)或者SOA架構(gòu),都是比較通用的。只是對(duì)于一些中間件集群的搭建,可以使用Docker。一句Docker ps就可以很方便查詢運(yùn)行的服務(wù)信息,并且升級(jí)基礎(chǔ)服務(wù)也很方便。

對(duì)于優(yōu)秀的集群架構(gòu)設(shè)計(jì)的追求是永無止境的。在跟很多創(chuàng)業(yè)公司的技術(shù)朋友們接觸下來,大家都是比較偏向于快速搭建以及開發(fā)、發(fā)布服務(wù)。然而一方面也顧慮微服務(wù)的架構(gòu)會(huì)比較復(fù)雜,殺雞用牛刀。但是微服務(wù)本身就是一種敏捷模式的優(yōu)秀實(shí)踐。這些朋友往往會(huì)在業(yè)務(wù)飛速發(fā)展的時(shí)候面臨一個(gè)困擾,就是服務(wù)拆分,數(shù)據(jù)庫的分庫分表、通過消息去解耦像面條一樣的同步代碼,想要優(yōu)化性能但是無從下手的尷尬。

相關(guān)文檔

Apache Thrift

使用Mesos和Marathon管理Docker集群

基于docker-swarm搭建持續(xù)集成集群服務(wù)

Kubernetes中文文檔

后記

本文主要是對(duì)于Docker的微服務(wù)實(shí)踐進(jìn)行技術(shù)方案選型以及介紹。不同的業(yè)務(wù)、團(tuán)隊(duì)可能會(huì)適合不通過的架構(gòu)體系和技術(shù)方案。

作為架構(gòu)師應(yīng)該結(jié)合公司近期、長期的戰(zhàn)略規(guī)劃,進(jìn)行長遠(yuǎn)的布局。最起碼基礎(chǔ)的架構(gòu)也是需要能支撐3年發(fā)展,期間可以不斷引入新的技術(shù)并進(jìn)行服務(wù)升級(jí)和持續(xù)的代碼層重構(gòu)。

也許一個(gè)架構(gòu)師從0開始搭建一整套體系并不需要花費(fèi)多久時(shí)間,最需要其進(jìn)行的就是不斷在團(tuán)隊(duì)推行Domain-Driven Design。并且使團(tuán)隊(duì)一起遵循Clean Code,進(jìn)行敏捷開發(fā)OvO

鏈接已復(fù)制,快去分享吧

企業(yè)網(wǎng)版權(quán)所有?2010-2024 京ICP備09108050號(hào)-6京公網(wǎng)安備 11010502049343號(hào)