Spring Cloud Netflix 概覽和架構設計
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可以幫助我們把所有的服務實例的監控信息聚合到一個地方統一查看。這樣就不需要挨個打開一個個的頁面一個個查看。
下面就是使用上述的子框架實現的為服務架構的組架構圖:
在上圖中,有幾個需要說明的地方:
- 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應用中使用也可以通過添加相關配置來實踐。
這里,就只是來看一下服務注冊中和監控模塊的開發,還有服務調用的開發,其他的可以直接參考上面的系列文章。
注冊和監控中心的開發
這個非常簡單,就下面一個類:
// 省略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-server 和 spring-cloud-starter-hystrix-dashboard 等。
服務間調用
在網上的各種文檔中,對服務間調用,都沒有說明的很清楚,所以這里特別說明一下這個如何開發。
有兩種方式可以進行服務調用, RestTemplate 和 FeignClient 。不管是什么方式,他都是通過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網關默認的超時時間非常短,這是為了保證調用服務的時候能夠很快的響應。但是,我們會有一些業務方法運行的時間比較長,特別是在測試服務器。這時候,就需要調整超時時間。這個超時有幾個地方:
- 負載均衡Ribbon,負載均衡有一個超時的設置,包括鏈接時間和讀取時間
- 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);
}
};
}
來自:http://developer.51cto.com/art/201703/534462.htm