Spring Cloud Netflix 概覽和架構設計

李穎傑發表於2017-03-10

Spring Cloud簡介

Spring Cloud是基於Spring Boot的一整套實現微服務的框架。他提供了微服務開發所需的配置管理、服務發現、斷路器、智慧路由、微代理、控制匯流排、全域性鎖、決策競選、分散式會話和叢集狀態管理等元件。最重要的是,跟spring boot框架一起使用的話,會讓你開發微服務架構的雲服務非常好的方便。

Spring Cloud包含了非常多的子框架,其中,Spring Cloud Netflix是其中一套框架,由Netflix開發後來又併入Spring Cloud大家庭,它主要提供的模組包括:服務發現、斷路器和監控、智慧路由、客戶端負載均衡等。

Spring Cloud Netflix專案的時間還不長,併入Spring Cloud大家族還是2年前,所以相關的使用文件還比較少,除了官方文件,國內也有一箇中文社群。但是,如果是剛開始接觸這個,想使用它搭建一套微服務的應用架構,總是會有不知如何下手的感覺。所以,這篇文章就是從整體上來看看這個框架的各個元件、用處是什麼、如何相互作用。最後再結合實際的經驗,介紹一下可能會出現的問題,以及針對一些問題,採用什麼樣的解決方案。

微服務架構

首先,我們來看看一般的微服務架構需要的功能或使用場景:

  • 我們把整個系統根據業務拆分成幾個子系統。
  • 每個子系統可以部署多個應用,多個應用之間使用負載均衡。
  • 需要一個服務註冊中心,所有的服務都在註冊中心註冊,負載均衡也是通過在註冊中心註冊的服務來使用一定策略來實現。
  • 所有的客戶端都通過同一個閘道器地址訪問後臺的服務,通過路由配置,閘道器來判斷一個URL請求由哪個服務處理。請求轉發到服務上的時候也使用負載均衡。
  • 服務之間有時候也需要相互訪問。例如有一個使用者模組,其他服務在處理一些業務的時候,要獲取使用者服務的使用者資料。
  • 需要一個斷路器,及時處理服務呼叫時的超時和錯誤,防止由於其中一個服務的問題而導致整體系統的癱瘓。
  • 還需要一個監控功能,監控每個服務呼叫花費的時間等。

Spring Cloud Netflix元件以及部署

Spring Cloud Netflix框架剛好就滿足了上面所有的需求,而且最重要的是,使用起來非常的簡單。Spring Cloud Netflix包含的元件及其主要功能大致如下:

  • Eureka,服務註冊和發現,它提供了一個服務註冊中心、服務發現的客戶端,還有一個方便的檢視所有註冊的服務的介面。 所有的服務使用Eureka的服務發現客戶端來將自己註冊到Eureka的伺服器上。
  • Zuul,閘道器,所有的客戶端請求通過這個閘道器訪問後臺的服務。他可以使用一定的路由配置來判斷某一個URL由哪個服務來處理。並從Eureka獲取註冊的服務來轉發請求。
  • Ribbon,即負載均衡,Zuul閘道器將一個請求傳送給某一個服務的應用的時候,如果一個服務啟動了多個例項,就會通過Ribbon來通過一定的負載均衡策略來傳送給某一個服務例項。
  • Feign,服務客戶端,服務之間如果需要相互訪問,可以使用RestTemplate,也可以使用Feign客戶端訪問。它預設會使用Ribbon來實現負載均衡。
  • Hystrix,監控和斷路器。我們只需要在服務介面上新增Hystrix標籤,就可以實現對這個介面的監控和斷路器功能。
  • Hystrix Dashboard,監控皮膚,他提供了一個介面,可以監控各個服務上的服務呼叫所消耗的時間等。
  • Turbine,監控聚合,使用Hystrix監控,我們需要開啟每一個服務例項的監控資訊來檢視。而Turbine可以幫助我們把所有的服務例項的監控資訊聚合到一個地方統一檢視。這樣就不需要挨個開啟一個個的頁面一個個檢視。

下面就是使用上述的子框架實現的為服務架構的組架構圖:

spring-cloud.jpg

在上圖中,有幾個需要說明的地方:

  • Zuul閘道器也在註冊中心註冊,把它也當成一個服務來統一檢視。 負載均衡不是一個獨立的元件,它執行在閘道器、服務呼叫等地方,每當需要訪問一個服務的時候,就會通過Ribbon來獲得一個該服務的例項去掉用。Ribbon從Eureka註冊中心獲得服務和例項的列表,而不是傳送每個請求的時候從註冊中心獲得。
  • 我們可以使用RestTemplate來進行服務間呼叫,也可以配置FeignClient來使用,不管什麼方式,只要使用服務註冊,就會預設使用Ribbon負載均衡。(RestTemplate需要新增@LoadBalanced)
  • 每個服務都可以開啟監控功能,開啟監控的服務會提供一個servlet介面/hystrix.stream,如果你需要監控這個服務的某一個方法的執行統計,就在這個方法上加一個@HystrixCommand的標籤。
  • 檢視監控資訊,就是在Hystrix Dashboard上輸入這個服務的監控url: http://serviceIp:port/hystrix.stream,就可以用圖表的方式檢視執行監控資訊。
  • 如果要把所有的服務的監控資訊聚合在一起統一檢視,就需要使用Turbine來聚合所需要的服務的監控資訊。

我們也可以從上圖中看出該架構的部署方式:

  • 獨立部署一個閘道器應用
  • 服務註冊中心和監控可以配置在一個應用裡,也可以是2個應用。
  • 服務註冊中心也可以部署多個,通過區域zone來區分,來實現高可用。
  • 每個服務,根據負載和高可用的需要,部署一個或多個例項。

Spring Cloud Netflix元件開發

上面說到,開發基於Spring Cloud Netflix的微服務非常簡單,一般我們是和Spring Boot一起使用,如果你想在自己原先的Java Web應用中使用也可以通過新增相關配置來實踐。

有關開發的詳細內容,可以參考Spring Cloud中文社群的這個系列文章,裡面詳細介紹了每一種元件的開發。這裡,就只是來看一下服務註冊中和監控模組的開發,還有服務呼叫的開發,其他的可以直接參考上面的系列文章。

註冊和監控中心的開發

這個非常簡單,就下面一個類:

// 省略import
@SpringBootApplication
@EnableEurekaServer
@EnableHystrixDashboard
public class ApplicationRegistry {
public static void main(String[] args) {
    new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}

這裡使用Spring Boot標籤的@SpringBootApplication說明當前的應用是一個Spring Boot應用。這樣我就可以直接用main函式在IDE裡面啟動這個應用,也可以打包後用命令列啟動。當然也可以把打包的war包用Tomcat之類的伺服器啟動。

使用標籤@EnableEurekaServer,就能在啟動過程中啟動Eureka服務註冊中心的元件。它會監聽一個埠,預設是8761,來接收服務註冊。並提供一個Web頁面,開啟以後,可以看到註冊的服務。

新增@EnableHystrixDashboard就會提供一個監控的頁面,我們可以在上面輸入要監控的服務的地址,就可以檢視啟用了Hystrix監控的介面的呼叫情況。

當然,為了使用上面的元件,我們需要在Maven的POM檔案裡新增相應的依賴,比如使用spring-boot-starter-parent,依賴spring-cloud-starter-eureka-serverspring-cloud-starter-hystrix-dashboard等。

服務間呼叫

在網上的各種文件中,對服務間呼叫,都沒有說明的很清楚,所以這裡特別說明一下這個如何開發。

有兩種方式可以進行服務呼叫,RestTemplateFeignClient。不管是什麼方式,他都是通過REST介面呼叫服務的http介面,引數和結果預設都是通過Jackson序列化和反序列化。因為Spring MVC的RestController定義的介面,返回的資料都是通過Jackson序列化成JSON資料。

RestTemplate

使用這種方式,只需要定義一個RestTemplate的Bean,設定成LoadBalanced即可:

@Configuration
public class SomeCloudConfiguration {
@LoadBalanced
@Bean
RestTemplate restTemplate() {
    return new RestTemplate();
}
}

這樣我們就可以在需要用的地方注入這個bean使用:

public class SomeServiceClass {
@Autowired
private RestTemplate restTemplate;
public String getUserById(Long userId) {
    UserDTO results = restTemplate.getForObject("http://users/getUserDetail/" + userId, UserDTO.class);
    return results;
}
}

其中,users是服務ID,Ribbon會從服務例項列表獲得這個服務的一個例項,傳送請求,並獲得結果。物件UserDTO需要序列號,它的反序列號會自動完成。

FeignClient

除了上面的方式,我們還可以用FeignClient。還是直接看程式碼:

@FeignClient(value = "users", path = "/users")
public interface UserCompositeService {
@RequestMapping(value = "/getUserDetail/{id}",
        method = RequestMethod.GET,
        produces = MediaType.APPLICATION_JSON_VALUE)
UserDTO getUserById(@PathVariable Long id);
}

我們只需要使用@FeignClient定義一個介面,Spring Cloud Feign會幫我們生成一個它的實現,從相應的users服務獲取資料。

其中,@FeignClient(value = "users", path = "/users/getUserDetail")裡面的value是服務ID,path是這一組介面的path字首。

在下面的方法定義裡,就好像設定Spring MVC的介面一樣,對於這個方法,它對應的URL是/users/getUserDetail/{id}

然後,在使用它的時候,就像注入一個一般的服務一樣注入後使用即可:

public class SomeOtherServiceClass {
@Autowired
private UserCompositeService userService;
public void doSomething() {
    // .....
    UserDTO results = userService.getUserById(userId);
    // other operation...
}
}

遇到的問題

由於Spring Cloud說明文件較少,微服務的架構相對來說也比較複雜,在開發的時候,難免會遇到很多問題,有一些是如何更好地使用這套框架去搭建架構,也有一些問題是如何配置。這裡就一些我在搭建微服務架構的時候遇到的問題提供一些方法。

請求超時問題

Zuul閘道器預設的超時時間非常短,這是為了保證呼叫服務的時候能夠很快的響應。但是,我們會有一些業務方法執行的時間比較長,特別是在測試伺服器。這時候,就需要調整超時時間。這個超時有幾個地方:

  1. 負載均衡Ribbon,負載均衡有一個超時的設定,包括連結時間和讀取時間
  2. Hystrix斷路器也有一個超時設定,它需要在適當的時候返回,而不是一直等在一個請求上。

對應的配置如下:

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 30000
ribbon:
ReadTimeout: 30000
ConnectTimeout: 15000

服務ID的問題

服務的ID,也就是服務名,可以通過在application.yml或者Bootstrap.yml裡面設定:

spring:
application:
    name: users

管理路徑的問題

Spring Boot的應用預設都是開放一些管理的介面,如/info/health和metrics監控的介面/metrics等。如果你使用預設的路徑,使用Hystrix監控、服務註冊中心的監聽服務狀態都不會有問題,但是,如果你想使用別的路徑,例如/management/info/management/health,那就牽扯到很多地方,而且,每個版本可能會或多或少的有一些問題,導致你遇到的問題還會不一樣。我遇到過的問題有:

註冊成功卻找不到服務

首先,註冊可以成功,在Eureka伺服器頁面上也可以看到各個服務。但是,當你通過閘道器呼叫的時候,卻總是提示服務找不到。這時候可能就需要在每個服務的application.yml裡面進行如下配置:

eureka:
instance:
    nonSecurePort: ${server.port}
    appname: ${spring.application.name}
    statusPageUrlPath: ${management.context-path}/info
    healthCheckUrlPath: ${management.context-path}/health

簡單來說,這就是告訴在註冊的時候,同時告訴Eureka伺服器,服務的埠是什麼,用來監聽狀態的路徑是什麼。這是因為我們使用了不同的管理介面路徑,而Eureka伺服器沒有使用相應的路徑。

如果一切正常,你在Eureka伺服器上點選一個註冊的服務,應該能開啟一個info頁面。他可能是空白的,但是,至少Eureka伺服器能通過這個知道服務的執行正常。

這個問題也不是在所有的版本都存在,只是在某一些Spring Cloud的版本存在。

設定了管理路徑的Hystrix監控

剛才說了Hystrix監控的路徑是http://serviceIp:port/hystrix.stream,如果你設定了管理介面的路徑,那麼這個監控路徑也會變成:

http://serviceIp:port/${management.context-path}/hystrix.stream

如果這時候,你再想使用Turbine聚合,Turbine就會找不到了,因為它預設使用Eureka伺服器上的伺服器地址和埠,在後面新增/hystrix.stream。這時候,你就需要設定Turbine:

turbine:
aggregator:
    clusterConfig: USER
appConfig: USER
instanceUrlSuffix:
    USER: /user/hystrix.stream

管理路徑的安全性

對於微服務部署的幾臺機器,可以通過開通防火牆來控制誰可以訪問管理介面,但是,即使是這樣,為了安全性等,我一般還是會把管理端介面也用Spring Security來保護。這樣一來,監控介面就沒法直接訪問了。

服務間呼叫的許可權驗證

一般我們的API介面都需要某種授權才能訪問,登陸成功以後,然後通過token或者cookie等方式才能呼叫介面。

使用Spring Cloud Netfix框架的話,登入的時候,把登入請求轉發到相應的使用者服務上,登陸成功後,會設定cookie或header token等。然後客戶端接下來的請求就會帶著這些驗證資訊,從Zuul閘道器傳到相應的服務上進行驗證。

Zuul閘道器在把請求轉發到後臺的服務的時候,會預設把一些header傳到服務端,如:Cookie、Set-Cookie、Authorization。這樣,客戶端請求的相關headers就可以傳遞到服務端,服務端設定的cookie也可以傳到客戶端。

但是,如果你想禁止某些header透傳到服務端,可以在Zuul閘道器的application.yml配置裡通過下面的方式禁用:

zuul:
routes:
users:
 path: /users/**
 sensitiveHeaders: Cookie,Set-Cookie,Authorization
 serviceId: user

剛才說了我們的某個服務有時候需要呼叫另一個服務,這時候,這個請求不是客戶端發起,他的請求的header裡面也不會有任何驗證資訊。這時候,要麼,通過防火牆等設定,保證服務間呼叫的介面,只能某幾個地址訪問;要麼,就通過某種方式設定header。

同時,如果你想在某個服務裡面獲得這個請求的真是IP,(因為請求的通過閘道器轉發而來,你直接通過request獲得ip得到的是閘道器的IP),就可以從headerX-Forwarded-Host獲得。如果想禁用這個header,也可以:

zuul.addProxyHeaders = false

如果你使用RestTemplate的方式呼叫,可以在請求裡面新增一個有header的Options。

也可以通過如下的攔截器的方式設定,它對RestTemplate方式和FeignClient的方式都可以起作用:

@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
    @Override
    public void apply(RequestTemplate template) {
        String authToken = getToken();
        template.header(AUTH_TOKEN_HEADER, authToken);
    }
};
}

相關文章