Spring Cloud Netflix 概覽和架構(gòu)設(shè)計
Spring Cloud簡介
Spring Cloud是基于Spring Boot的一整套實現(xiàn)微服務(wù)的框架。他提供了微服務(wù)開發(fā)所需的配置管理、服務(wù)發(fā)現(xiàn)、斷路器、智能路由、微代理、控制總線、全局鎖、決策競選、分布式會話和集群狀態(tài)管理等組件。最重要的是,跟spring boot框架一起使用的話,會讓你開發(fā)微服務(wù)架構(gòu)的云服務(wù)非常好的方便。
Spring Cloud包含了非常多的子框架,其中,Spring Cloud Netflix是其中一套框架,由Netflix開發(fā)后來又并入Spring Cloud大家庭,它主要提供的模塊包括:服務(wù)發(fā)現(xiàn)、斷路器和監(jiān)控、智能路由、客戶端負(fù)載均衡等。
Spring Cloud Netflix項目的時間還不長,并入Spring Cloud大家族還是2年前,所以相關(guān)的使用文檔還比較少,除了官方文檔,國內(nèi)也有一個中文社區(qū)。但是,如果是剛開始接觸這個,想使用它搭建一套微服務(wù)的應(yīng)用架構(gòu),總是會有不知如何下手的感覺。所以,這篇文章就是從整體上來看看這個框架的各個組件、用處是什么、如何相互作用。最后再結(jié)合實際的經(jīng)驗,介紹一下可能會出現(xiàn)的問題,以及針對一些問題,采用什么樣的解決方案。
微服務(wù)架構(gòu)
首先,我們來看看一般的微服務(wù)架構(gòu)需要的功能或使用場景:
- 我們把整個系統(tǒng)根據(jù)業(yè)務(wù)拆分成幾個子系統(tǒng)。
- 每個子系統(tǒng)可以部署多個應(yīng)用,多個應(yīng)用之間使用負(fù)載均衡。
- 需要一個服務(wù)注冊中心,所有的服務(wù)都在注冊中心注冊,負(fù)載均衡也是通過在注冊中心注冊的服務(wù)來使用一定策略來實現(xiàn)。
- 所有的客戶端都通過同一個網(wǎng)關(guān)地址訪問后臺的服務(wù),通過路由配置,網(wǎng)關(guān)來判斷一個URL請求由哪個服務(wù)處理。請求轉(zhuǎn)發(fā)到服務(wù)上的時候也使用負(fù)載均衡。
- 服務(wù)之間有時候也需要相互訪問。例如有一個用戶模塊,其他服務(wù)在處理一些業(yè)務(wù)的時候,要獲取用戶服務(wù)的用戶數(shù)據(jù)。
- 需要一個斷路器,及時處理服務(wù)調(diào)用時的超時和錯誤,防止由于其中一個服務(wù)的問題而導(dǎo)致整體系統(tǒng)的癱瘓。
- 還需要一個監(jiān)控功能,監(jiān)控每個服務(wù)調(diào)用花費的時間等。
Spring Cloud Netflix組件以及部署
Spring Cloud Netflix框架剛好就滿足了上面所有的需求,而且最重要的是,使用起來非常的簡單。Spring Cloud Netflix包含的組件及其主要功能大致如下:
- Eureka,服務(wù)注冊和發(fā)現(xiàn),它提供了一個服務(wù)注冊中心、服務(wù)發(fā)現(xiàn)的客戶端,還有一個方便的查看所有注冊的服務(wù)的界面。 所有的服務(wù)使用Eureka的服務(wù)發(fā)現(xiàn)客戶端來將自己注冊到Eureka的服務(wù)器上。
- Zuul,網(wǎng)關(guān),所有的客戶端請求通過這個網(wǎng)關(guān)訪問后臺的服務(wù)。他可以使用一定的路由配置來判斷某一個URL由哪個服務(wù)來處理。并從Eureka獲取注冊的服務(wù)來轉(zhuǎn)發(fā)請求。
- Ribbon,即負(fù)載均衡,Zuul網(wǎng)關(guān)將一個請求發(fā)送給某一個服務(wù)的應(yīng)用的時候,如果一個服務(wù)啟動了多個實例,就會通過Ribbon來通過一定的負(fù)載均衡策略來發(fā)送給某一個服務(wù)實例。
- Feign,服務(wù)客戶端,服務(wù)之間如果需要相互訪問,可以使用RestTemplate,也可以使用Feign客戶端訪問。它默認(rèn)會使用Ribbon來實現(xiàn)負(fù)載均衡。
- Hystrix,監(jiān)控和斷路器。我們只需要在服務(wù)接口上添加Hystrix標(biāo)簽,就可以實現(xiàn)對這個接口的監(jiān)控和斷路器功能。
- Hystrix Dashboard,監(jiān)控面板,他提供了一個界面,可以監(jiān)控各個服務(wù)上的服務(wù)調(diào)用所消耗的時間等。
- Turbine,監(jiān)控聚合,使用Hystrix監(jiān)控,我們需要打開每一個服務(wù)實例的監(jiān)控信息來查看。而Turbine可以幫助我們把所有的服務(wù)實例的監(jiān)控信息聚合到一個地方統(tǒng)一查看。這樣就不需要挨個打開一個個的頁面一個個查看。
下面就是使用上述的子框架實現(xiàn)的為服務(wù)架構(gòu)的組架構(gòu)圖:

在上圖中,有幾個需要說明的地方:
- Zuul網(wǎng)關(guān)也在注冊中心注冊,把它也當(dāng)成一個服務(wù)來統(tǒng)一查看。 負(fù)載均衡不是一個獨立的組件,它運行在網(wǎng)關(guān)、服務(wù)調(diào)用等地方,每當(dāng)需要訪問一個服務(wù)的時候,就會通過Ribbon來獲得一個該服務(wù)的實例去掉用。Ribbon從Eureka注冊中心獲得服務(wù)和實例的列表,而不是發(fā)送每個請求的時候從注冊中心獲得。
- 我們可以使用RestTemplate來進行服務(wù)間調(diào)用,也可以配置FeignClient來使用,不管什么方式,只要使用服務(wù)注冊,就會默認(rèn)使用Ribbon負(fù)載均衡。(RestTemplate需要添加@LoadBalanced)
- 每個服務(wù)都可以開啟監(jiān)控功能,開啟監(jiān)控的服務(wù)會提供一個servlet接口/hystrix.stream,如果你需要監(jiān)控這個服務(wù)的某一個方法的運行統(tǒng)計,就在這個方法上加一個@HystrixCommand的標(biāo)簽。
- 查看監(jiān)控信息,就是在Hystrix Dashboard上輸入這個服務(wù)的監(jiān)控url: http://serviceIp:port/hystrix.stream,就可以用圖表的方式查看運行監(jiān)控信息。
- 如果要把所有的服務(wù)的監(jiān)控信息聚合在一起統(tǒng)一查看,就需要使用Turbine來聚合所需要的服務(wù)的監(jiān)控信息。
我們也可以從上圖中看出該架構(gòu)的部署方式:
- 獨立部署一個網(wǎng)關(guān)應(yīng)用
- 服務(wù)注冊中心和監(jiān)控可以配置在一個應(yīng)用里,也可以是2個應(yīng)用。
- 服務(wù)注冊中心也可以部署多個,通過區(qū)域zone來區(qū)分,來實現(xiàn)高可用。
- 每個服務(wù),根據(jù)負(fù)載和高可用的需要,部署一個或多個實例。
Spring Cloud Netflix組件開發(fā)
上面說到,開發(fā)基于Spring Cloud Netflix的微服務(wù)非常簡單,一般我們是和Spring Boot一起使用,如果你想在自己原先的Java Web應(yīng)用中使用也可以通過添加相關(guān)配置來實踐。
有關(guān)開發(fā)的詳細(xì)內(nèi)容,可以參考Spring Cloud中文社區(qū)的這個系列文章,里面詳細(xì)介紹了每一種組件的開發(fā)。這里,就只是來看一下服務(wù)注冊中和監(jiān)控模塊的開發(fā),還有服務(wù)調(diào)用的開發(fā),其他的可以直接參考上面的系列文章。
注冊和監(jiān)控中心的開發(fā)
這個非常簡單,就下面一個類:
// 省略import
@SpringBootApplication
@EnableEurekaServer
@EnableHystrixDashboard
public class ApplicationRegistry {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
這里使用Spring Boot標(biāo)簽的@SpringBootApplication
說明當(dāng)前的應(yīng)用是一個Spring Boot應(yīng)用。這樣我就可以直接用main函數(shù)在IDE里面啟動這個應(yīng)用,也可以打包后用命令行啟動。當(dāng)然也可以把打包的war包用Tomcat之類的服務(wù)器啟動。
使用標(biāo)簽@EnableEurekaServer
,就能在啟動過程中啟動Eureka服務(wù)注冊中心的組件。它會監(jiān)聽一個端口,默認(rèn)是8761,來接收服務(wù)注冊。并提供一個Web頁面,打開以后,可以看到注冊的服務(wù)。
添加@EnableHystrixDashboard
就會提供一個監(jiān)控的頁面,我們可以在上面輸入要監(jiān)控的服務(wù)的地址,就可以查看啟用了Hystrix監(jiān)控的接口的調(diào)用情況。
當(dāng)然,為了使用上面的組件,我們需要在Maven的POM文件里添加相應(yīng)的依賴,比如使用spring-boot-starter-parent
,依賴spring-cloud-starter-eureka-server
和spring-cloud-starter-hystrix-dashboard
等。
服務(wù)間調(diào)用
在網(wǎng)上的各種文檔中,對服務(wù)間調(diào)用,都沒有說明的很清楚,所以這里特別說明一下這個如何開發(fā)。
有兩種方式可以進行服務(wù)調(diào)用,RestTemplate
和FeignClient
。不管是什么方式,他都是通過REST接口調(diào)用服務(wù)的http接口,參數(shù)和結(jié)果默認(rèn)都是通過Jackson序列化和反序列化。因為Spring MVC的RestController定義的接口,返回的數(shù)據(jù)都是通過Jackson序列化成JSON數(shù)據(jù)。
RestTemplate
使用這種方式,只需要定義一個RestTemplate的Bean,設(shè)置成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是服務(wù)ID,Ribbon會從服務(wù)實例列表獲得這個服務(wù)的一個實例,發(fā)送請求,并獲得結(jié)果。對象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會幫我們生成一個它的實現(xiàn),從相應(yīng)的users服務(wù)獲取數(shù)據(jù)。
其中,@FeignClient(value = "users", path = "/users/getUserDetail")
里面的value是服務(wù)ID,path是這一組接口的path前綴。
在下面的方法定義里,就好像設(shè)置Spring MVC的接口一樣,對于這個方法,它對應(yīng)的URL是/users/getUserDetail/{id}
。
然后,在使用它的時候,就像注入一個一般的服務(wù)一樣注入后使用即可:
public class SomeOtherServiceClass {
@Autowired
private UserCompositeService userService;
public void doSomething() {
// .....
UserDTO results = userService.getUserById(userId);
// other operation...
}
}
遇到的問題
由于Spring Cloud說明文檔較少,微服務(wù)的架構(gòu)相對來說也比較復(fù)雜,在開發(fā)的時候,難免會遇到很多問題,有一些是如何更好地使用這套框架去搭建架構(gòu),也有一些問題是如何配置。這里就一些我在搭建微服務(wù)架構(gòu)的時候遇到的問題提供一些方法。
請求超時問題
Zuul網(wǎng)關(guān)默認(rèn)的超時時間非常短,這是為了保證調(diào)用服務(wù)的時候能夠很快的響應(yīng)。但是,我們會有一些業(yè)務(wù)方法運行的時間比較長,特別是在測試服務(wù)器。這時候,就需要調(diào)整超時時間。這個超時有幾個地方:
- 負(fù)載均衡Ribbon,負(fù)載均衡有一個超時的設(shè)置,包括鏈接時間和讀取時間
- Hystrix斷路器也有一個超時設(shè)置,它需要在適當(dāng)?shù)臅r候返回,而不是一直等在一個請求上。
對應(yīng)的配置如下:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 30000
ribbon:
ReadTimeout: 30000
ConnectTimeout: 15000
服務(wù)ID的問題
服務(wù)的ID,也就是服務(wù)名,可以通過在application.yml或者Bootstrap.yml里面設(shè)置:
spring:
application:
name: users
管理路徑的問題
Spring Boot的應(yīng)用默認(rèn)都是開放一些管理的接口,如/info
、/health
和metrics監(jiān)控的接口/metrics等。如果你使用默認(rèn)的路徑,使用Hystrix監(jiān)控、服務(wù)注冊中心的監(jiān)聽服務(wù)狀態(tài)都不會有問題,但是,如果你想使用別的路徑,例如/management/info
、/management/health
,那就牽扯到很多地方,而且,每個版本可能會或多或少的有一些問題,導(dǎo)致你遇到的問題還會不一樣。我遇到過的問題有:
注冊成功卻找不到服務(wù)
首先,注冊可以成功,在Eureka服務(wù)器頁面上也可以看到各個服務(wù)。但是,當(dāng)你通過網(wǎng)關(guān)調(diào)用的時候,卻總是提示服務(wù)找不到。這時候可能就需要在每個服務(wù)的application.yml里面進行如下配置:
eureka:
instance:
nonSecurePort: ${server.port}
appname: ${spring.application.name}
statusPageUrlPath: ${management.context-path}/info
healthCheckUrlPath: ${management.context-path}/health
簡單來說,這就是告訴在注冊的時候,同時告訴Eureka服務(wù)器,服務(wù)的端口是什么,用來監(jiān)聽狀態(tài)的路徑是什么。這是因為我們使用了不同的管理接口路徑,而Eureka服務(wù)器沒有使用相應(yīng)的路徑。
如果一切正常,你在Eureka服務(wù)器上點擊一個注冊的服務(wù),應(yīng)該能打開一個info頁面。他可能是空白的,但是,至少Eureka服務(wù)器能通過這個知道服務(wù)的運行正常。
這個問題也不是在所有的版本都存在,只是在某一些Spring Cloud的版本存在。
設(shè)置了管理路徑的Hystrix監(jiān)控
剛才說了Hystrix監(jiān)控的路徑是http://serviceIp:port/hystrix.stream,如果你設(shè)置了管理接口的路徑,那么這個監(jiān)控路徑也會變成:
http://serviceIp:port/${management.context-path}/hystrix.stream
如果這時候,你再想使用Turbine聚合,Turbine就會找不到了,因為它默認(rèn)使用Eureka服務(wù)器上的服務(wù)器地址和端口,在后面添加/hystrix.stream。這時候,你就需要設(shè)置Turbine:
turbine:
aggregator:
clusterConfig: USER
appConfig: USER
instanceUrlSuffix:
USER: /user/hystrix.stream
管理路徑的安全性
對于微服務(wù)部署的幾臺機器,可以通過開通防火墻來控制誰可以訪問管理接口,但是,即使是這樣,為了安全性等,我一般還是會把管理端接口也用Spring Security來保護。這樣一來,監(jiān)控接口就沒法直接訪問了。
服務(wù)間調(diào)用的權(quán)限驗證
一般我們的API接口都需要某種授權(quán)才能訪問,登陸成功以后,然后通過token或者cookie等方式才能調(diào)用接口。
使用Spring Cloud Netfix框架的話,登錄的時候,把登錄請求轉(zhuǎn)發(fā)到相應(yīng)的用戶服務(wù)上,登陸成功后,會設(shè)置cookie或header token等。然后客戶端接下來的請求就會帶著這些驗證信息,從Zuul網(wǎng)關(guān)傳到相應(yīng)的服務(wù)上進行驗證。
Zuul網(wǎng)關(guān)在把請求轉(zhuǎn)發(fā)到后臺的服務(wù)的時候,會默認(rèn)把一些header傳到服務(wù)端,如:Cookie、Set-Cookie、Authorization。這樣,客戶端請求的相關(guān)headers就可以傳遞到服務(wù)端,服務(wù)端設(shè)置的cookie也可以傳到客戶端。
但是,如果你想禁止某些header透傳到服務(wù)端,可以在Zuul網(wǎng)關(guān)的application.yml配置里通過下面的方式禁用:
zuul:
routes:
users:
path: /users/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
serviceId: user
剛才說了我們的某個服務(wù)有時候需要調(diào)用另一個服務(wù),這時候,這個請求不是客戶端發(fā)起,他的請求的header里面也不會有任何驗證信息。這時候,要么,通過防火墻等設(shè)置,保證服務(wù)間調(diào)用的接口,只能某幾個地址訪問;要么,就通過某種方式設(shè)置header。
同時,如果你想在某個服務(wù)里面獲得這個請求的真是IP,(因為請求的通過網(wǎng)關(guān)轉(zhuǎn)發(fā)而來,你直接通過request獲得ip得到的是網(wǎng)關(guān)的IP),就可以從headerX-Forwarded-Host獲得。如果想禁用這個header,也可以:
zuul.addProxyHeaders = false
如果你使用RestTemplate的方式調(diào)用,可以在請求里面添加一個有header的Options。
也可以通過如下的攔截器的方式設(shè)置,它對RestTemplate方式和FeignClient的方式都可以起作用:
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
String authToken = getToken();
template.header(AUTH_TOKEN_HEADER, authToken);
}
};
}