你都用過SpringCloud的哪些組件,它們的原理是什麼?

前言

看到文章的題目了嗎?就是這麼抽象和籠統的一個問題,確實是我面試中真實被問到的,某共享貨車平臺的真實面試問題。
SpringCloud確實是用過,但是那是三四年前了,那個時候SpringCloud剛開始流行沒多久,我們技術總監讓我們調研一下,然後算上我在內的三個同事就一人買了一本SpringCloud的書籍,開始看,開始研究,正好那個時候DDD也比較火,然後我們就一邊研究的SpringCloud一邊按照DDD的模型搭建自己的項目。
但是這個項目最後做了三個月,才完成了一期。後面二期還沒開始,我就撤了。所以SpringCloud總共的使用時間就兩三個月,所以對這部分知識掌握的並不紮實,而且入職了新公司之後,都是使用公司自己封裝的框架,也已經三年沒有用過SpringCloud了,這次是要面試換工作了,所以決定將這方面的知識,總結一下。

服務治理 Spring Cloud Eureka

我們之前在使用服務之間相互調用的時候,一般是靠一些靜態配置來完成的。比如服務A,要調用服務B來完成一個業務操作時,爲了實現服務B的高可用,一般是通過手動配置來完成服務B的服務實例清單的維護。
隨着業務的發展,系統功能越來越複雜,相應的服務不斷增加,而且服務的IP還一直在變化,靜態配置來維護各服務,就會變得越來越困難。
在這裏插入圖片描述
這個時候就出現了服務治理框架,Spring Cloud Eureka。

Spring Cloud Eureka 主要是圍繞着服務註冊與服務發現機制來完成對微服務的自動化管理的。

服務註冊

Eureka提供了服務端組件,我們也稱爲註冊中心。每個服務都向Eureka的服務註冊中心,登記自己提供服務的元數據,包括服務的ip地址、端口號、版本號、通信協議等。這樣註冊中心,就將各個服務維護在了一個服務清單中(雙層Map,第一層key是服務名,第二層key是實例名,value是服務地址加端口)。
在這裏插入圖片描述
服務註冊中心,還會以心跳的方式去監聽清單中的服務是否可用(默認30秒),若不可用(服務續約時間默認90秒),需從清單中剔除,達到排除故障服務的效果。
Eureka註冊中心提供了高可用方案,可以支持集羣部署註冊中心,然後多個註冊中心實例之間又相互註冊,這樣每個實例中都有一樣的服務清單了。

服務發現

Eureka不但提供服務端,還提供了客戶端,客戶端是在各個服務中運行的。
Eureka客戶端主要有兩個作用:

  • 向註冊中心註冊自身提供的服務,並週期性的發送心跳來更新它非服務租約
  • 同時,也能從服務端查詢當前註冊的服務信息,並把他們緩存到本地,並週期性的刷新服務狀態

在Eureka Server中註冊的服務,相互之間調用,不再是通過指定的具體實例地址,而是通過向服務名發請求實現調用,因爲每個服務服務都是多實例,並且實例地址還有可能經常變。
但是通過服務名稱調用,並不知道具體的服務實例位置,因此需要向註冊中心諮詢,並獲取所有服務實例清單,然後實現服務的請求訪問。

舉例

在這裏插入圖片描述
服務A的一個業務操作,需要調用服務B和服務C來完成。
那麼服務A和服務B和服務C都將自己註冊到Eureka的註冊中心,然後服務A通過諮詢註冊中心,將註冊中心的服務列表清單緩存到自己本地。
通過服務名稱獲取到服務B和服務C的服務實例地址,最後通過一種輪詢策略取出一個具體的服務實例地址來進行調用。

總結一下
Eureka Client : 主要是將服務本身註冊到Eureka Server中,同時查詢Eureka Server的註冊服務列表緩存到本地
Eureka Server:註冊中心,保存了所有註冊服務的元數據,包括ip地址,端口等信息。

客戶端負載均衡 Spring Cloud Ribbon

服務的調用方,在通過Eureka Client緩存到本地的註冊表之後,通過服務名稱,找到具體的服務對應的實例地址,但是被調用方的服務地址是有多個的,那麼該用那個地址去進行調用呢?

服務A:
192.168.12.10:9001
192.168.12.11:9001
192.168.12.12:9001

這個時候Spring Cloud Ribbon就出現了,它是專門解決這個問題的,它的作用就是做負載均衡,會均勻的把請求分發到每臺機器上。

Ribbon默認使用Round Ribbon的策略進行負載均衡,具體就是採用輪詢的方式進行請求。

Ribbon除了有Round Ribbon這種輪詢策略,還有其他策略以及自定義策略。
主要有:

  • RandomRole: 從服務實例清單中隨機選擇一個服務實例。
  • RoundRobinRule: 按照線性輪詢的方式依次選擇每個服務實例。
  • RetryRule:根據輪詢方式進行,且具備重試機制進行選擇實例。
  • WeightedResponseTimeRule:對RoundRobinRule的擴展,增加了根據實例的運行情況來計算權重,並根據權重來挑選實例。
  • ZoneAvoidanceRule:根據服務方的zone區域和可用性來輪詢選擇。

Spring Cloud Ribbon具體的執行示例如下:
在這裏插入圖片描述

實例代碼

下面的代碼就是通過Ribbon調用服務的代碼實例。

@RestController
public class ConsumerController {
    @Autowired
    RestTemplate restTemplate;
    @GetMapping("/ribbon-consumer")
    public String helloConsumer(){
        return restTemplate.getForEntity("http://example-service/index",String.class).getBody();
    }
}

可以看到Ribbon也是通過發起http請求,來進行的調用,只不過是通過調用服務名的地址來實現的。雖然說Ribbon不用去具體請求服務實例的ip地址或域名了,但是每調用一個接口都還要手動去發起Http請求,也是比較繁瑣的,而且返回類型也比較抽象,所以Spring Cloud對調用方式進行了升級封裝。

聲明式服務調用 Spring Cloud Feign

Spring Cloud 爲了簡化服務間的調用,在Ribbon的基礎上進行了進一步的封裝。單獨抽出了一個組件,就是Spring Cloud Feign。在引入Spring Cloud Feign後,我們只需要創建一個接口並用註解的方式來配置它,即可完成對服務提供方的接口綁定。

Spring Cloud Feign具備可插拔的註解支持,並擴展了Spring MVC的註解支持。

下面我們來看一個具體的例子:

服務方具體的接口定義與實現代碼如下:


import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
 * 接口定義
 */
@FeignClient(value="service-hi",fallback = TestFeignServiceImpl.class)
public interface TestFeignService {

    @RequestMapping(value="/hi",method = RequestMethod.GET)
    String sayHi(@RequestParam("name") String name);
}
/**
 * 具體的服務實現
 */
@Component
public class TestFeignServiceImpl implements TestFeignService {
    @Override
    public String sayHi(String name) {
        return "你好,"+name;
    }
}

調用方的使用代碼如下:

@RestController
public class TestController
{
    @Resource
    private TestFeignService testFeignService;

    @RequestMapping(value="/hi",method = RequestMethod.GET)
    public String sayHi(@RequestParam String name)
    {
    	// 調用遠程服務
        return testFeignService.sayHi(name);
    }
}

通過上面的代碼,我們可以看到,調用方通過Feign進程遠程服務調用的時候,非常簡單,就向是在調用本地服務一樣。

像之前的建立連接,構造請求,發起請求,獲取響應,解析響應等等操作,對使用者來說都是透明化的,使用者不用關心服務是怎麼實現調用的,直接使用即可。

那麼Feign是如何實現這套封裝邏輯的呢?

其實Feign底層主要是靠動態代理來實現這整個服務的調用過程的。
主要邏輯如下:

  • 如果一個接口上定義了@FeignClient註解,Feign就會根據這個接口生成一個動態代理類。
  • 如果調用方,在調用這個定義了@FeignClient註解的接口時,本質上是會調用Feign生成的代理類。
  • Feign生成的動態代理類,會根據具體接口方法中的@RequestMapping等註解,來動態構造出需要請求的服務地址。
  • 最後針對這個地址,再通過Ribbon發起服務調用,解析響應等操作。

在這裏插入圖片描述
因爲Spring Cloud Feign的使用方式比Spring Cloud Ribbon更方便,所以一般項目中都是使用Feign,而且Feign還有繼承特性,可以將遠程服務接口繼承過來然後再進行自己的個性化擴展。因此Feign的使用範圍以及普及率更高一些。

服務容錯保護 Spring Cloud Hystrix

在微服務架構中,我們將系統拆分成多個服務單元,各個服務之間通過服務註冊與訂閱的方式互相依賴。

我們以一個電商網站下單的過程來舉例,在下單的業務操作過程中需要調用庫存服務,支付服務,積分、物流等服務。假設訂單服務最多同一時間只能處理50個請求,這個時候如果積分服務掛了,那麼每次訂單服務去調用積分服務的時候,都會卡這麼一段時間,然後才返回超時異常

在這種場景下會有什麼問題呢?

如果目前電商網站正在搞活動,進行搶購活動,下單的人非常多,這種高併發的場景下,訂單服務的已經同時在處理50個下單請求了,並且都卡在了請求積分服務的過程中。訂單服務已經沒有能力去處理其他請求了。

那麼其他服務再來調用訂單服務時,發訂單服務無響應,這樣就導致訂單服務也不可用了。然後其他依賴訂單服務的服務,也最終會導致不可用。這就是微服務架構中的服務雪崩。

在這裏插入圖片描述

就上圖所示,如果多個服務之間相互調用,而不做任何保護措施的話,那麼一個服務掛了,就會產生連鎖反應,導致其他服務也掛了。

其實就算是積分服務掛了,也並不應該導致訂單服務也掛了,積分服務掛了,我們可以跳過積分服務,或者是放一個默認值,然後繼續往下走,等着積分服務恢復了,可以手動恢復一下數據。

那麼Spring Cloud Hystrix就是解決這個問題的組件,他主要是起到熔斷,隔離,降級的作用。

Spring Cloud Hystrix其實是會爲每一個服務開闢一個線程池,然後每個線程池中的線程用於對服務的調用請求。這樣就算是積分服務掛了,那也只是調用積分服務的線程池出現問題了,而其他服務的線程池還正常工作。這就是服務的隔離。

這樣訂單服務在的調用積分服務的時候,如果發現有問題了,積分服務可以通過Hystrix返回一個默認值(默認是5秒內20次調用失敗就熔斷)。這樣訂單服務就不用在這裏卡住了,可以繼續往下調用其他服務進行業務操作了。這就是服務的熔斷。

雖然說是積分服務掛了,並且也返回了默認值了,但是後續如果積分服務恢復了,想恢復數據怎麼辦呢?這個時候積分服務可以將姐收到的請求記錄下來,或者是打到日誌中,能爲後面恢復數據提供依據就行。這就是服務的降級

整個過程大致如下圖所示:
在這裏插入圖片描述

API網關服務Spring Cloud Zuul

通過上面幾個組件的結合使用,已經能夠完成一個基本的微服務架構了。但是當一個系統中微服務的數量逐漸增多時,一些通用的邏輯,例如:權限校驗機制,請求過濾,請求路由,限流等等,這些每個服務對外提供能力的時候都要考慮到的邏輯,就會變得冗餘。

這個時候API網關的概念應運而生,它類似於面向對象設計模式中的Facade模式(門面模式/外觀模式),所有的外部客戶端訪問都需要經過它來進行調度和過濾。主要實現請求路由、負載均衡、校驗過濾、服務限流等功能。

Spring Cloud Zuul就是Spring Cloud提供的API網關組件,它通過與Eureka進行整合,將自身註冊爲Eureka下的應用,從Eureka下獲取所有服務的實例,來進行服務的路由。

Zuul還提供了一套過濾器機制,開發者可以自己指定哪些規則的請求需要執行校驗邏輯,只有通過校驗邏輯的請求才會被路由到具體服務實例上,否則返回錯誤提示。
在這裏插入圖片描述

Spring Cloud Zuul的依賴包spring-cloud-starter-zuul本身就包含了對spring-cloud-starter-hystrixspring-cloud-starter-ribbon模塊的依賴,所以Zuul天生就擁有線程隔離和斷路器的自我保護功能,以及對服務調用的客戶端負載功能。

Zuul的路由實現是通過Path和serviceId還實現的,path是一個http請求去除ip和端口號後的方法路徑,例如:http://192.168.20.12:9001/api-zuul/123,那麼path就是/api-zuul/123,Zuul在配置時支持模糊匹配,若123是動態參數,可以將path配置成/pai-zuul/**,serviceId就是服務在Eureka中註冊的服務名稱。

zuul.routes.api-zuul.path= /api-zuul/**
zuul.routes.api-zuul.serviceId= service-jimoer

有了統一的網關後,再做統一的鑑權、限流、認證授權、安全等方面的工作就會變的更加方便了。

總結

上面總結了Spring Cloud的幾個核心組件,其實Spring Cloud 除了這幾個組件還有一些其他的組件,例如:

  • 分佈式配置中心Spring Cloud Config
  • 消息總線Spring Cloud Bus
  • 消息驅動Spring Cloud Stream
  • 分佈式服務跟蹤Spring Cloud Sleuth

主要是後面這些組件我們平時用的不多,而且對於微服務來說有些是有替代品的,所以我暫時就沒有總結。還有一點畢竟我這次總結是爲了解決面試的問題,所以後面如果在實際的工作中用到了剩下的這些組件,我會繼續總結的。

好了,總結一下這次的幾個組件的內容吧。

  • Spring Cloud Eureka 各個微服務在啓動時將自己註冊到Eureka Server中,並且各個服務中的Eureka Client又能從註冊中心獲取各個服務的實例地址清單。
  • Spring Cloud Ribbon 各個服務相互調用的時候,通過Ribbon來進行客戶端的負載均衡,從多個實例中根據一定的策略選擇一臺進行請求。
  • Spring Cloud Feign 基於動態代理機制,根據註解和參數拼接URL,選擇具體的服務實例發起請求,簡化了服務間相互調用的開發工作。
  • Spring Cloud Hystrix 調用每個服務的時候都是通過線程池中的線程來發起的,不同的服務走不同的線程池,實現了服務的隔離,而且服務不可用時還提供了熔斷機制以及支持降低措施。
  • Spring Cloud Zuul 外部請求統一通過Zuul網關來進入,支持自定義路由規則,自定義過濾規則,可以實現同一的鑑權、限流、認證等功能。

最後來一個整體的架構圖,將各個組件串起來。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章