Spring Cloud 微服務架構學習筆記與示例
本文示例基於Spring Boot 1.5.x實現,如對Spring Boot不熟悉,可以先學習我的這一篇:《 ofollow,noindex">Spring Boot 1.5.x 基礎學習示例 》。關於微服務基本概念不瞭解的童鞋,可以先閱讀下始祖Martin Fowler的《 Microservice 》,本文不做介紹和描述。
一、分散式服務框架的發展
1.1 第一代服務框架
特點:和語言繫結緊密
1.2 第二代服務框架
代表:Spring Cloud等
現狀:適合混合式開發(例如藉助Steeltoe OSS可以讓ASP.Net Core與Spring Cloud整合),正值當年
1.3 第三代服務框架
代表:Service Mesh(服務網格) => 例如Service Fabric、lstio、Linkerd、Conduit等
現狀:在快速發展中,更新迭代比較快
1.4 未來(目測不久)主流的服務架構和技術棧
基礎的雲平臺為微服務提供了資源能力(計算、儲存和網路等),容器作為最小工作單元被Kubernetes排程和編排,Service Mesh(服務網格)管理微服務的服務通訊,最後通過API Gateway向外暴露微服務的業務介面。
目前,我所在的專案組已經在採用這種技術架構了,服務網格採用的是Linkerd,容器編排採用的是K8S,Spring Cloud已經沒用了。But,不代表Spring Cloud沒有學習的意義,對於中小型專案團隊, Spring Cloud仍然是快速首選 。
二、Spring Cloud 簡介
2.1 Spring Cloud極簡介紹
首先,儘管Spring Cloud帶有“Cloud”這個單詞,但它並不是雲端計算解決方案,而是在Spring Boot基礎之上構建的,用於快速構建分散式系統的通用模式的工具集。
其次,使用Spring Cloud開發的應用程式非常適合在Docker和PaaS(比如Pivotal Cloud Foundry)上部署,所以又叫做雲原生應用(Cloud Native Application)。雲原生可以簡單地理解為面向雲環境的軟體架構。
總結 : Spring Cloud是一個基於Spring Boot實現的雲原生應用開發工具,它為基於JVM的雲原生應用開發中涉及的配置管理、服務發現、熔斷器、智慧路由、微代理、控制匯流排、分散式會話和叢集狀態管理等操作提供了一種簡單的開發方式。
Spring Cloud具有如下特點:
- 約定大於配置
- 適用於各種環境
- 隱藏了元件的複雜性,並提供宣告式、無XML式的配置方式
- 開箱即用,快速啟動
- 元件豐富,功能齊全
- ......
Spring Cloud作為第二代微服務的代表性框架,已經在國內眾多大中小型的公司有實際應用案例。許多公司的業務線全部擁抱Spring Cloud,部分公司選擇部分擁抱Spring Cloud。例如,拍拍貸資深架構師楊波老師就根據自己的實際經驗以及對Spring Cloud的深入調研,並結合國內一線網際網路大廠的開源專案應用實踐結果,認為Spring Cloud技術棧中的有些元件離生產級開發尚有一定距離,最後提出了一個可供中小團隊參考的微服務架構技術棧,又被稱為“中國特色的微服務架構技術棧1.0”:
上圖中涉及到的元件,這裡不做具體介紹,有興趣的童鞋可以瀏覽波波老師的這篇文章:《 一個可供中小團隊參考的微服務架構技術棧 》。
2.2 Spring Cloud核心子專案
- Spring Cloud Netflix:核心元件,可以對多個Netflix OSS開源套件進行整合,包括以下幾個元件:
- Spring Cloud Config:配置管理工具,實現應用配置的外部化儲存,支援客戶端配置資訊重新整理、加密/解密配置內容等。
- Spring Cloud Bus:事件、訊息匯流排,用於傳播叢集中的狀態變化或事件,以及觸發後續的處理
- Spring Cloud Security:基於spring security的安全工具包,為我們的應用程式新增安全控制
- Spring Cloud Consul : 封裝了Consul操作,Consul是一個服務發現與配置工具(與Eureka作用類似),與Docker容器可以無縫整合
- ......
三、參考學習資料
備註: 下面資料都是我們專案組新同事以及老同事(.Net技術背景)所採用的學習資料, 並不保證適合於所有人 。本示例主要也主要是基於下面的資料而寫的sample code。
(1)周立:《 Spring Cloud與Docker 微服務架構實戰 》
(2)程式猿DD:《 Spring Cloud 微服務實戰 》、《 Spring Cloud基礎教程(Dalston版本) ( 強力推薦 )》
(3)純潔的微笑,《 Spring Cloud系列文章 》
四、示例結構說明
4.1 示例環境版本
- Java : JDK & JRE 1.8 8u151
- Spring Boot : 1.5.15.RELEASE
- Spring Cloud : Edgware.SR3 ( 小貼士 :Spring Cloud的版本命名是以倫敦地鐵站的名字來命名的)
4.2 示例地址與結構說明
示例地址: https://github.com/EdisonChou/EDC.SpringCloud.Samples
4.2.1 服務註冊與發現 - 基於Eureka
此部分示例位於: part1_service-register-discovery
此部分示例主要演示瞭如何基於Eureka實現服務的註冊與發現,其中包括兩個版本:
① 單節點版本 (開發環境除錯用) => 位於eureka-service-sn (sn代表single node)專案內
這裡需要注意的地方是:在開發環境需要關閉Eureka的自我保護機制,不然你無法輕易看到服務移除的效果,需要在application.yml中如下設定:
eureka: server: enableSelfPreservation: false # 本地除錯環境下關閉自我保護機制
這是因為Eureka考慮到生產環境中可能存在的網路分割槽故障,會導致微服務與Eureka Server之間無法正常通訊。它的架構哲學是寧可同時保留所有微服務(健康的微服務和不健康的微服務都會保留),也不盲目登出任何健康的微服務。
關於自我保護機制,更多內容可以參考:《 Spring Cloud Eureka全解之自我保護機制 》
② HA多節點版本 (部署/生產環境用) => 位於eureka-service-ha-1 & eureka-service-ha-2這兩個專案內
此版本需要注意的是兩個節點的application.yml保持一致,但由於其中使用了peer1和peer2的hostname,在本地開發環境需要給Windows(我假設你使用的是Windows系統)設定hosts檔案如下:
127.0.0.1peer1 peer2
擴充套件: 除了Eureka之外,還可以選擇通用型較強的Consul,關於Consul的基本概念與服務端的安裝配置可以看看我的這一篇《 .Net Core微服務之基於Consul實現服務註冊於發現 》瞭解一下。最後,不得不說,Spring Boot 和 Spring Cloud中核心元件封裝的註解真的是太強大了,很多操作一個註解直接搞定,無須過多的coding。
4.2.2 客戶端負載均衡 - 基於Ribbon
此部分示例位於: part2_client-load-balance
此部分示例主要演示瞭如何基於Ribbon實現客戶端的負載均衡,建議啟動方式:先啟動Eureka,再啟動UserService和MovieService。通過訪問MovieService的API介面 /log-instance 進行日誌檢視,測試結果如下圖所示:
從上圖可以看出,通過客戶端的負載均衡演算法,依次訪問了不同的服務節點。
4.2.3 宣告式REST呼叫 - 基於Feign
此部分示例位於: part3_feign
此部分示例主要演示了基於Feign如何實現宣告式呼叫,包括以下內容:
(1)基本整合Feign進行單引數與多引數的請求:位於movie-service這個專案內
需要注意的就是別忘了在啟動類加上@EnableFeignClients註解
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients // 要使用Feign,需要加上此註解 public class MovieServiceApplication { public static void main(String[] args) { SpringApplication.run(MovieServiceApplication.class, args); } }
(2)自定義Feign配置的使用:位於movie-service-feign-customizing這個專案內
下面的Feign介面就使用了自定義的配置類FeignConfiguration。
@FeignClient(name = "user-service", configuration = FeignConfiguration.class) public interface UserFeignClient { /** * 使用feign自帶的註解@RequestLine * @see https://github.com/OpenFeign/feign * @param id 使用者id * @return 使用者資訊 */ @RequestLine("GET /{id}") User findById(@Param("id") Long id); }
(3)Feign的日誌的使用:位於movie-service-feign-logging這個專案內
需要注意的是:Feign雖然提供了logger,但是其日誌列印只會對DEBUG級別做出響應。日誌級別一共有4種類型,如下所示:
Logger.Level 可選值:
* NONE: 不記錄任何日誌(預設值)
* BASIC: 僅記錄請求方法、URL、響應狀態碼以及執行時間
* HEADERS: 記錄BASIC級別的基礎之上,記錄請求和響應的header
* FULL: 記錄請求和響應的header,body和元資料
要輸出日誌列印,application.yml內要設定DEBUG級別:
logging: level: # 將Feign介面的日誌級別設定為DEBUG,因為Feign的Logger.Level只針對DEBUG做出響應 com.mbps.cd.movieservice.feign.UserFeignClient: DEBUG
最後,針對FULL級別的日誌列印效果如下圖所示:
4.2.4 容錯處理 - 基於Hystrix
此部分示例位於: part4_hystrix
此部分示例主要演示如何基於Hystrix實現容錯處理,主要包括以下內容:
(1)通用方式整合Hystrix:此示例位於movie-service專案中
針對普通的方法,只需加上HystrixCommand註解及定義回退方法即可,例如:
@HystrixCommand(fallbackMethod = "findByIdFallback") @GetMapping(value = "/user/{id}") public User findById(@PathVariable Long id) { return restTemplate.getForObject("http://user-service/" + id, User.class); } public User findByIdFallback(Long id){ User user = new User(); user.setId(-1L); user.setUsername("Default User"); return user; }
(2)Feign使用Hystrix:此示例位於movie-service-feign-hystrix專案中
針對Feign,它是以介面形式工作的,好在Spring Cloud已預設為Feign整合了Hystrix,不過預設是關閉的,需要手動在配置檔案中開啟:
feign: hystrix: enabled: true
在之前的版本(Dalston之前的版本)中是預設開啟的,至於為何要改為預設禁用,可以看看這裡: https://github.com/spring-cloud/spring-cloud-netflix/issues/1277
然後直接在FeignClient註解中加入fallback屬性即可,例如下面所示:
@FeignClient(name = "user-service", fallback = FeignClientFallback.class) public interface UserFeignClient { @GetMapping(value = "/{id}") User findById(@PathVariable("id") Long id); } @Component class FeignClientFallback implements UserFeignClient{ @Override public User findById(Long id) { User user = new User(); user.setId(-1L); user.setUsername("Default User"); return user; } }
如果想要在Feign發生回退時能夠留下日誌供查看回退原因,那麼可以使用FallbackFactory,示例專案:movie-service-feign-fallback-factory.
@FeignClient(name = "user-service", fallbackFactory = FeignClientFallbackFactory.class) public interface UserFeignClient { @GetMapping(value = "/{id}") User findById(@PathVariable("id") Long id); } @Component class FeignClientFallbackFactory implements FallbackFactory<UserFeignClient> { private static final Logger LOGGER = LoggerFactory.getLogger(FeignClientFallbackFactory.class); @Override public UserFeignClient create(Throwable cause) { return new UserFeignClient() { @Override public User findById(Long id) { /* * 日誌最好放在各個fallback中,而不要直接放在create方法中 * 否則在引用啟動時,就會列印該日誌 */ FeignClientFallbackFactory.LOGGER.info("sorry, fallback. reason was: ", cause); User user = new User(); user.setId(-1L); user.setUsername("Default Username"); return user; } }; } } View Code
當發生回退時,日誌輸出資訊如下:
除此之外,關於Hystrix部分,還有監控的主題,這裡由於我所在的專案組的技術架構中不會涉及到,也就沒有弄,有興趣的童鞋可以關注一下Hystrix自帶的監控以及基於Turbine的聚合監控。參考文章:《 Hystrix監控面板(Dalston版) 》與《 Hystrix監控資料聚合 》。
4.2.5 API閘道器 - 基於Zuul
此部分示例位於: part5_zuul
此部分示例主要演示如何基於Zuul實現API閘道器,主要包括以下內容:
(1)整合Zuul編寫API閘道器:位於zuul-service專案中
可以測試驗證的內容:
- 路由規則:依次啟動eureka,user-service,movie-service,zuul-service,然後通過zuul訪問user-service介面
- 負載均衡:依次啟動eureka,多個user-service,zuul-service,然後多次訪問user-service,最後檢視日誌資訊(兩個user-service都會列印hibernate日誌資訊),驗證是否達到負載均衡的效果。 PS :Zuul內建了Ribbon來實現負載均衡。
- 路由端點:依次啟動eureka,user-service,movie-service,zuul-service,然後瀏覽器訪問zuul-service(http://localhost:5000/routes)可以得到路由端點資訊
對於路由端點,需要改一下以下配置,才能正常顯示路由端點資訊,否則會報401的錯誤:
management: security: enabled: false # 預設為true,改為false以便可以看到routes
- 路由配置:示例主要演示了路由字首、全域性敏感設定以及路由規則設定
- 大檔案上傳設定:針對超大檔案上傳(比如500M),需要在Zuul中提升超時設定
# 下面的設定針對超大檔案上傳(比如500M),提升了超時設定 hystrix: command: default: execution: isolation: thread: timeoutInMillseconds: 60000 ribbon: ConnectionTimeout: 3000 ReadTimeout: 60000 View Code
(2)Zuul的過濾器:主要位於zuul-service-filter這個專案中
對於Zuul的請求宣告週期來說,一共有4種標準過濾器型別:
- PRE:在請求被路由之前呼叫,可利用這種過濾器實現身份驗證、記錄除錯資訊等操作;
- ROUTING:將請求路由到微服務,可利用這種過濾器用於構建傳送給微服務的請求;
- POST:在路由到微服務以後執行,可用來為響應新增標準的HTTP Header、收集統計資訊和指標、將響應從微服務傳送給客戶端等;
- ERROR:在其他階段發生錯誤時執行該過濾器;
此示例中演示了PRE型別的過濾器,部分場景下,想要禁用部分過濾器,只需要在配置檔案中設定即可,例如這裡禁用PreRequestLogFilter過濾器:
zuul: # 禁用指定過濾器設定 PreRequestLogFilter: pre: disable: true # 禁用我們建立的PreRequestLogFilter過濾器 View Code
(3)Zuul的容錯與回退:主要位於zuul-service-fallback這個專案中
Zuul自身就帶有Hystrix,但是 它監控的粒度是微服務級別,而不是某個API ,當某個API不可用時,會統一拋500錯誤碼的異常頁。我們可以為Zuul添加回退,以實現更友好的返回資訊。實現也很簡單,只需要實現FallbackProvider介面即可。這裡要注意的是,對於Edgware之前的版本(即Dalston及更低版本)需要實現的是ZuulFallbackProvider介面,而Edgware及之後的版本要實現的是FallbackProvider介面。因為FallbackProvider是ZuulFallbackProvider的子介面,而它的好處就是多了一個介面可以獲取可能造成回退的原因,具體可以參考這一篇文章:《 Spring Cloud Edgware新特性之八:Zuul回退的改進 》。下面是本示例中訪問user-service介面(user-service被我手動關閉)後的返回結果:
(4)Zuul的高可用架構
生產環境中一般都需要部署高可用的Zuul以避免單點故障,實際開發中有兩種情況:
① Zuul的客戶端也註冊到了Eureka Server上(比如:MVC App, SPA 等)
此時Zuul的高可用和其他微服務沒區別,都是藉助Eureka和Ribbon來實現負載均衡。
② Zuul的客戶端未註冊到Eureka Server上(比如手機App端等)
現實中這種場景或許更常見,此時需要藉助一個額外的負載均衡器來實現Zuul的高可用,比如:Nginx、HAProxy以及F5等。
更多Zuul高可用的內容,可以瀏覽周立老師的這一篇:《 Zuul的高可用 》
(5)使用Zuul聚合微服務:此示例位於zuul-service-aggregation專案中
許多場景下可能一個外部請求要查詢Zuul後端的多個服務,這時可以使用Zuul來聚合服務請求,即只需請求一次,由Zuul來請求各個服務,然後組織好資料傳送給客戶端(比如App客戶端)。示例中主要基於RxJava與Zuul來結合實現的微服務請求的聚合。
4.2.6 統一配置管理 - 基於Spring Cloud Config
Spring Cloud Config為分散式系統外部化配置提供了服務端和客戶端的支援,包括Config Server和Config Client兩部分,其架構圖如下圖所示:
其中,各個微服務在啟動時會請求Config Server以獲取所需要的配置屬性,然後快取這些屬性以提高效能,如下圖所示:
此部分示例位於: part6_config
此部分示例主要演示如何基於Spring Cloud Config實現統一配置中心,主要包括以下內容:
(1)基本的Config Server與Config Client編寫:此示例位於config-service與config-client中
此示例需要用到一些已放到git的配置檔案,這裡我已將其放到了github方便大家可以直接拿來測試用,倉庫地址為: https://github.com/EdisonChou/EDC.SpringCloud.Samples.Config
端點與配置檔案的 對映規則 如下:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
其中,application: 表示微服務的虛擬主機名,即配置的spring.application.name
profile: 表示當前的環境,dev, test or production?
label: 表示git倉庫分支,master or relase or others repository name? 預設是master
專案中,config-service的配置檔案如下:
server: port: 8080 spring: application: name: sampleservice-config-server cloud: config: server: git: # 配置Git倉庫地址 uri: https://github.com/EdisonChou/EDC.SpringCloud.Samples.Config # Git倉庫賬號(如果需要認證) username: # Git倉庫密碼(如果需要認證) password: View Code
啟動順序:先啟動config-server,再啟動config-client,因為config-client在啟動時就回去config-server獲取配置,如果這時config-server未啟動則會報錯。
這裡需要注意的就是在config-client中,對於spring cloud config的配置應該放在bootstrap.yml中而不是application.yml中,否則會不起作用。這裡涉及到一個spring cloud的“引導上下文”的概念,可以參考這篇《 深入理解Spring Cloud引導上下文 》來了解一下。
(2)使用/refresh端點手動重新整理配置:仍然位於config-client專案中
要想在執行期間重新整理配置,需要兩點改造:加上@RefreshScope註解
@RestController @RefreshScope // @RefreshScope註解不能少,否則即使呼叫/refresh,配置也不會重新整理 public class ConfigClientController { @Value("${profile}") private String profile; @GetMapping("/profile") public String hello(){ return this.profile; } }
此外,針對Spring Boot 1.5.x,還需要給config-client端關閉安全認證,否則無法正常refresh:
management: security: enabled: false
之後,就可以通過對config-client發起POST請求重新整理配置了:
不過,如果所有微服務都需要手動重新整理配置,工作量會很大。所以,在實際環境中,一般會實現配置的自動重新整理。
(3)使用Spring Cloud Bus自動重新整理配置:此示例位於config-server-cloud-bus與config-client-cloud-bus專案中
此示例使用到的架構如下圖所示,它將Config Server加入訊息匯流排之中,並使用Config Server的/bus/refersh端點來實現配置的重新整理。這樣,各個微服務只需要關注自身的業務邏輯,而無需再自己手動重新整理配置。
Tip: Spring Cloud Bus基於輕量級地訊息代理(例如RabbitMQ、Kafka等)連線分散式系統的節點,就可以通過廣播的方式來傳播狀態的更改(例如配置的更新)或者其他的管理指令。我們可以將Spring Cloud Bus想象成一個分散式的Spring Boot Actuator。
執行順序:先啟動config-service-cloud-bus,再啟動兩個config-client-cloud-bus(第一個預設埠8081,第二個埠改為8082),修改github中sampleservice-foo-dev.properties中的profile值後commit & push,然後POST請求config-service-cloud-bus的/bus/refersh端點,最後再次訪問兩個client的/profile端點進行驗證。
如果部分場景想要知道Spring Cloud Bus事件傳播的細節,可以通過以下設定來跟蹤事件匯流排:
spring: cloud: bus: trace: enabled: true # 開啟cloud bus跟蹤
(4)與Eureka的配合使用:此示例位於config-service-eureka與config-client-eureka兩個專案中
(5)Config Server的高可用:涉及到Git倉庫的高可用、RabbitMQ的高可用以及Config Server自身的高可用。
對於Git倉庫的高可用,第三方Git倉庫類似於GitHub等本身已經實現了高可用,而針對自建Git倉庫如GitLab,可以參考GitLab官方文件搭建高可用: https://about.gitlab.com/high-availability/
對於Config Server自身的高可用,也可以分為未註冊到Eureka和註冊到Eureka兩種情形,具體可以參考Zuul的高可用的架構圖。
此外,對於配置內容的加密,此示例沒有涉及,它依賴於 JCE ( Java Cryptography Extension ),可以參考這一篇《 Spring Cloud配置檔案加密 》瞭解一下。
擴充套件: 關於統一配置中心,還可以選擇更好用的Apollo(攜程的開源專案),可以看看我的這一篇《 .Net Core微服務之基於Apollo實現統一配置中心 》瞭解一下。
4.2.7 微服務跟蹤 - 基於Spring Cloud Sleuth
首先,值得一提的是Spring Cloud Sleuth大量借用了Google Dapper,Twitter Zipkin和Apache HTrace的設計,我們得了解一些術語,例如:span、trace、annotation等,詳細可以參考這篇《 Spring Cloud系列之分散式鏈路監控Spring Cloud Sleuth 》。
此示例位於: part7_sleuth
此部分示例主要演示如何基於Spring Cloud Sleuth實現分散式鏈路監控,主要包括以下內容:
(1)基礎整合Spring Cloud Sleuth:位於user-service-trace與movie-service-trace專案中,主要檢視控制檯輸出日誌
(2)Spring Cloud Sleuth與Zipkin的配合使用:位於zipkin-service-server、user-service-trace-zipkin與movie-service-trace-zipkin三個專案中
Zipkin是Twitter開源的分散式跟蹤系統,基於Dapper論文設計而來,主要功能是收集系統的時序資料,從而追蹤微服務架構的系統延時問題,此外還提供了一個非常友好的介面來幫助追蹤分析資料。
下圖是一個接入Zipkin之後的服務呼叫簡易流程圖:
執行順序:首先執行zipkin-service-server,其次執行user-service-zipkin與movie-service-zipkin,然後訪問http://localhost:8010/user/1得到資料結果,最後訪問zipkin server首頁,填入起始時間、結束時間等篩選條件後,點選Find a trace按鈕,可以看到trace列表,如下圖所示:
點選“依賴分析”,可以得到下圖,有助於我們分析依賴關係:
需要注意的是,在開發除錯時,因為預設的取樣百分比是10%,Sleuth會忽略大量span,因此我們可以在開發環境將其設定為100%:
spring: sleuth: sampler: # 指定需取樣的請求的百分比,預設是0.1(即10%),這裡方便檢視設為100%(實際環境不要這樣設定) percentage: 1.0
(3)使用RabbitMQ收集資料:此示例位於zipkin-service-server-stream與user-service-trace-zipkin-stream兩個專案中
此外,Spring Cloud Sleuth還可以與ELK配合使用,不過此示例沒有涉及,感興趣的朋友可以參考這一篇《 Spring Cloud Sleuth與ELK整合 》。當然,示例中的跟蹤資料都是存放到記憶體中,但是跟蹤資料還是建議存放到ElasticSearch中,生產環境切莫只儲存到記憶體中。
推薦工具
IDE => Intellij Idea Community 2018
( PS: 如果是.Net程式猿背景,強烈建議更改快捷鍵與Visual Studio保持一致,這樣能加快開發效率,如不瞭解如何修改,可以參考鄒瓊俊《 從.Net到Java - Idea and Start Spring Boot 》)
Plugin => 阿里巴巴程式碼規約