zuul 上傳文件
在user-service
中定義一個上傳接口:
@Controller
public class FileUploadController {
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public @ResponseBody String handleFileUpload(@RequestParam(value = "file", required = true) MultipartFile file) throws IOException {
byte[] bytes = file.getBytes();
File fileToSave = new File(file.getOriginalFilename());
FileCopyUtils.copy(bytes, fileToSave);
return fileToSave.getAbsolutePath();
}
}
配置文件配置如下:
spring:
application:
name: user-service
http:
multipart:
max-file-size: 2000Mb # Max file size,默認(rèn)1M
max-request-size: 2500Mb # Max request size玖喘,默認(rèn)10M
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
prefer-ip-address: true
server:
port: 8080
在user-service寫一個html簡單測試一下:
<form method="POST" enctype="multipart/form-data" action="/upload">
File to upload:
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
點擊上傳成功谎仲。
那么怎么通過zuul來代理呢?
If you @EnableZuulProxy
you can use the proxy paths to upload files and it should just work as long as the files are small. For large files there is an alternative path which bypasses the Spring DispatcherServlet (to avoid multipart processing) in "/zuul/*
". I.e. if zuul.routes.customers=/customers/**
then you can POST large files to "/zuul/customers/*
". The servlet path is externalized via zuul.servletPath. Extremely large files will also require elevated timeout settings if the proxy route takes you through a Ribbon load balancer, e.g.
如果是你使用@EnableZuulProxy
你可以使用代理的路徑來上傳文件,并且必須是文件比較小的時候。對于大文件一個可選擇的方案就是繞開Spring的DispatcherServlet
(避免多部分處理)通過/zuul/*
路徑。如果zuul.routes.customers=/customers/**
你可以上傳大文件通過/zuul/customers/*
路徑峡眶。 如果使用Ribbon進行負(fù)載均衡,超大文件也將需要設(shè)置的超時時間植锉。
小文件傳輸不需要修改zuul的配置:
zuul的配置:
spring:
application:
name: zuul-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
prefer-ip-address: true
server:
port: 6069
測試成功:
curl -v -H "Transfer-Encoding: chunked" -F "file=@Netty_in_Action最新版.pdf" localhost:6069/zuul/user-service/upload
如果是大文件傳輸辫樱,需要在zuul服務(wù)修改配置:
spring:
application:
name: zuul-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
prefer-ip-address: true
server:
port: 6069
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 60000
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
參考資料
Uploading Files through Zuul
重定向問題
解決了Cookie問題之后,我們已經(jīng)能夠通過網(wǎng)關(guān)來訪問并登錄到我們的web應(yīng)用了俊庇。但是這個時候又發(fā)現(xiàn)另外一個問題:雖然可以通過網(wǎng)關(guān)訪問登錄頁面并發(fā)起登錄請求狮暑,但是登錄成功之后,我們跳轉(zhuǎn)的url卻是具體web應(yīng)用實例的地址辉饱,而不是通過網(wǎng)關(guān)的路由地址搬男。這個問題特別嚴(yán)重,因為使用api網(wǎng)關(guān)的一個重要原因就是將網(wǎng)關(guān)作為統(tǒng)一入口彭沼,從而不暴露所有內(nèi)部服務(wù)細(xì)節(jié)缔逛。那么時什么原因?qū)е铝诉@個問題呢?
demo
比如我們在user服務(wù)定義了一個controller姓惑,重定向到hello.html
@Controller
public class UserController2 {
private Logger logger = LoggerFactory.getLogger(getClass());
@RequestMapping("/testRedirect")
public String testRedirect(){
logger.info("user2 testRedirect");
return "redirect:hello.html";
}
}
直接訪問user服務(wù)跳轉(zhuǎn)
http://192.168.1.57:8080/testRedirect
通過zuul代理褐奴,因為此url是跳轉(zhuǎn)資源,直接跳轉(zhuǎn)到web真實實例的url
http://192.168.1.57:6069/user-service/testRedirect
通過瀏覽器開發(fā)工具查看登錄以及登錄之后的請求詳情于毙,可以發(fā)現(xiàn)敦冬,引起問題的大致原因時由于spring secutity
或shiro
在登錄完成之后,通過重定向的方式跳轉(zhuǎn)到登錄后的頁面唯沮,此時登錄后的請求結(jié)果狀態(tài)嗎為302脖旱,請求響應(yīng)頭信息中的Location指向了具體的服務(wù)實例地址,而請求頭信息中的Host也指向了具體的服務(wù)實例ip地址和端口介蛉。所以萌庆,該問題的根本原因在于spring cloud zuul在路由請求時,并沒有將最初的host信息設(shè)置正確币旧,如何解決践险?
配置zuul.add-host-header=true
即可。
我配置了然后訪問:
http://192.168.1.57:6069/user-service/testRedirect
跳轉(zhuǎn)的地址是
http://192.168.1.57:6069/hello.html
很明顯跳轉(zhuǎn)到http://192.168.1.57:6069/user-service/hello.html
才對,沒找到解決的方案捏境。
網(wǎng)上根據(jù)這個問題有人在spring cloud
上提issue于游,自己在zuul上寫個過濾器毁葱,我覺得這個可以不解決垫言,因為現(xiàn)在都是前后端分離架構(gòu),不多數(shù)都不在后端進行跳轉(zhuǎn)倾剿,zuul.add-host-header=true
只是為了不暴露真實的ip信息筷频,如果要重定向到具體的前端頁面可以自己可以配置zuul.routes
到指定的服務(wù)上。
參考資料
Spring Cloud實戰(zhàn)小貼士:Zuul處理Cookie和重定向
Hystrix的路由回退
When a circuit for a given route in Zuul is tripped you can provide a fallback response by creating a bean of type ZuulFallbackProvider. Within this bean you need to specify the route ID the fallback is for and provide a ClientHttpResponse to return as a fallback. Here is a very simple ZuulFallbackProvider implementation.
當(dāng)Zuul中給定路由的電路跳閘時前痘,您可以通過創(chuàng)建ZuulFallbackProvider
類型的bean來提供回退響應(yīng)凛捏。 在這個bean中,您需要指定回退所對應(yīng)的路由ID芹缔,并提供一個ClientHttpResponse
作為后備返回坯癣。 這是一個非常簡單的ZuulFallbackProvider
實現(xiàn)。
demo
在zuul-service
中去定義MyFallbackProvider
繼承ZuulFallbackProvider
最欠,定義了路由id為user-service
服務(wù)的回退示罗。
@Component
public class MyFallbackProvider implements ZuulFallbackProvider {
//getRoute返回的必須要和zuul.routes.***一致,才能針對某個服務(wù)降級
@Override
public String getRoute() {
return "user-service";
}
@Override
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(("fallback"+MyFallbackProvider.this.getRoute()).getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
當(dāng)訪問user-service超時的時候頁面上顯示的是fallbackuser-service芝硬。
參考資料
官網(wǎng)Providing Hystrix Fallbacks For Routes
異構(gòu)語言支持Sidecar
Do you have non-jvm languages you want to take advantage of Eureka, Ribbon and Config Server? The Spring Cloud Netflix Sidecar was inspired by Netflix Prana. It includes a simple http api to get all of the instances (ie host and port) for a given service. You can also proxy service calls through an embedded Zuul proxy which gets its route entries from Eureka. The Spring Cloud Config Server can be accessed directly via host lookup or through the Zuul Proxy. The non-jvm app should implement a health check so the Sidecar can report to eureka if the app is up or down.
你有沒有非jvm語言你想利用Eureka
蚜点,Ribbon
和配置服務(wù)器? Spring Cloud Netflix Sidecar
的靈感來自Netflix Prana
拌阴。 它包含一個簡單的http api來獲取給定服務(wù)的所有實例(即主機和端口)绍绘。 您還可以通過嵌入式Zuul代理來代理服務(wù)調(diào)用,該代理從Eureka獲取其路由條目迟赃。 可以通過主機查找或通過Zuul Proxy直接訪問Spring Cloud Config Server
陪拘。 非jvm應(yīng)用程序應(yīng)該執(zhí)行健康檢查,以便Sidecar可以向應(yīng)用程序啟動或關(guān)閉時向eureka報告纤壁。
To include Sidecar in your project use the dependency with group org.springframework.cloud and artifact id spring-cloud-netflix-sidecar.
要在項目中包含Sidecar藻丢,請使用組org.springframework.cloud
和artifact id 為spring-cloud-netflix-sidecar
的依賴關(guān)系。
To enable the Sidecar, create a Spring Boot application with @EnableSidecar
. This annotation includes @EnableCircuitBreaker
, @EnableDiscoveryClient
, and @EnableZuulProxy
. Run the resulting application on the same host as the non-jvm application.
要啟用Sidecar摄乒,請使用@EnableSidecar
創(chuàng)建一個Spring Boot應(yīng)用程序悠反。 此注釋包括@EnableCircuitBreaker
,@EnableDiscoveryClient
和@EnableZuulProxy
馍佑。 在與非jvm應(yīng)用程序相同的主機上運行生成的應(yīng)用程序斋否。
To configure the side car add sidecar.port and sidecar.health-uri to application.yml. The sidecar.port property is the port the non-jvm app is listening on. This is so the Sidecar can properly register the app with Eureka. The sidecar.health-uri is a uri accessible on the non-jvm app that mimicks a Spring Boot health indicator. It should return a json document like the following:
要將side car配置為sidecar.port
和sidecar.health-uri
到application.yml
。 sidecar.port
屬性是非jvm應(yīng)用程序正在偵聽的端口拭荤。 這是因為Sidecar可以正確地注冊該應(yīng)用程序與eureka茵臭。 sidecar.health-uri
是一個可以在非jvm應(yīng)用程序上訪問的uri,它可以模仿Spring Boot健康指示器舅世。 它應(yīng)該返回一個json文檔旦委,如下所示:
{
"status":"UP"
}
demo
寫一個node.js的服務(wù)奇徒,端口是8060,訪問localhost:8060
返回"歡迎來到首頁"缨硝,訪問http://localhost:8060/health.json
,將會返回{"status":"UP"}
var http = require('http');
var url = require('url');
var path = require('path');
//創(chuàng)建server
var server = http.createServer(function (req,res) {
//獲得請求的路徑
var pathname = url.parse(req.url).pathname;
res.writeHead(200,{'Content-Type':'application/json;charset=utf-8'});
//訪問http://locaLhost:8060/,將會返回首頁
if(pathname === '/'){
res.end(JSON.stringify({"index":"歡迎來到首頁"}));
}
//訪問http://localhost:8060/health,將會返回{"status":"UP"}
else if(pathname ==="/health.json"){
res.end(JSON.stringify({"status":"UP"}));
}
//其他情況返回404
else {
res.end("404");
}
});
//創(chuàng)建監(jiān)聽摩钙,并打印日志
server.listen(8060,function () {
console.log('listening on localhost:8060');
})
啟動服務(wù):
node node-service.js
分別訪問首頁和健康檢查頁面。
然后新建一個zuul-sidecar服務(wù)查辩,依賴如下:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-sidecar</artifactId>
</dependency>
</dependencies>
啟動類SidecarApplication胖笛,除了@SpringBootApplication
還標(biāo)記有@EnableSidecar
注解:
@SpringBootApplication
@EnableSidecar
public class SidecarApplication {
public static void main(String[] args) {
SpringApplication.run(SidecarApplication.class, args);
}
}
配置文件application.yml:
server:
port: 8070
spring:
application:
name: zuul-sidecar
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
prefer-ip-address: true
sidecar:
port: 8060 # Node.js微服務(wù)的端口
health-uri: http://localhost:8060/health.json # Node.js微服務(wù)的健康檢查URL
啟動服務(wù),zuul-sidecar注冊到eureka上了宜岛,控制面板如下长踊,
通過zuul訪問zuul-sidecar間接訪問node的服務(wù),訪問首頁和健康頁面
http://192.168.1.57:6069/zuul-sidecar/
http://192.168.1.57:6069/zuul-sidecar/health.json
訪問sidecar的服務(wù):
http://localhost:8070/
user服務(wù)訪問node服務(wù)也可以通過sidecar來訪問通過注冊到zuul的服務(wù)名來訪問。
在user-service中定義:
@GetMapping("/sidecar")
public String sidecar(){
String response = restTemplate.getForObject("http://zuul-sidecar/",String.class);
return response;
}
http://192.168.1.57:8080/user/sidecar
成功訪問萍倡。
The api for the DiscoveryClient.getInstances()
method is /hosts/{serviceId}
. Here is an example response for /hosts/customer
that returns two instances on different hosts. This api is accessible to the non-jvm app (if the sidecar is on port 5678) at http://localhost:5678/hosts/{serviceId}
.
DiscoveryClient.getInstances()
方法的api是/hosts/{serviceId}
身弊。 以下是/ hosts/customers
的一個示例響應(yīng),它會在不同的主機上返回兩個實例列敲。 這個api可以訪問http://localhost:5678/hosts/{serviceId}
的非jvm應(yīng)用程序(如果sidecar在端口5678上)阱佛。比如我上面的列子就可以根據(jù)http://localhost:8070/hosts/user-service
來查看user-service的服務(wù)信息。具體原因下面解釋酿炸。
The Zuul proxy automatically adds routes for each service known in eureka to /<serviceId>
, so the customers service is available at /customers
. The Non-jvm app can access the customer service via http://localhost:5678/customers
(assuming the sidecar is listening on port 5678).
Zuul代理自動將eureka中已知的每個服務(wù)的路由添加到/<serviceId>
瘫絮,以便客戶可以在/客戶端使用客戶服務(wù)。 非jvm應(yīng)用程序可以通過http://localhost:5678/customers
訪問客戶服務(wù)(假設(shè)邊界正在偵聽端口5678)填硕。
If the Config Server is registered with Eureka, non-jvm application can access it via the Zuul proxy. If the serviceId of the ConfigServer is configserver and the Sidecar is on port 5678, then it can be accessed at http://localhost:5678/configserver
如果配置服務(wù)器在Eureka中注冊麦萤,則非jvm應(yīng)用程序可以通過Zuul代理訪問它。 如果ConfigServer的serviceId是configserver扁眯,而Sidecar在端口5678上壮莹,則可以訪問http://localhost:5678/configserver
使用sidecar也是可以訪問注冊到eureka上的服務(wù),也就是使用zuul的代理姻檀,而不需要另外的起一個zuul服務(wù)器命满。比如下面的可以通過zuul-sidecar訪問user服務(wù)。
http://localhost:8070/user-service/user/index
參考資料
官網(wǎng)Polyglot support with Sidecar
Hystrix和ribbon支持
spring-cloud-starter-zuul
依賴包括spring-cloud-starter-hystrix
和spring-cloud-starter-ribbon
模塊的依賴绣版,所以zuul天生就擁有線程隔離和斷路器的自我保護功能胶台,以及對服務(wù)調(diào)用的客戶端負(fù)載均衡功能。
需要注意的事杂抽,當(dāng)使用path與url的映射關(guān)系來配置路由規(guī)則的時候诈唬,對于路由轉(zhuǎn)發(fā)的請求不會采用hystrixCommand來包裝,所以這類請求沒有線程隔離和斷路器的保護缩麸,并且也不會有負(fù)載均衡的能力铸磅。因此,我們在使用zuul的時候盡量使用path和serviceId的組合來進行配置,這樣不僅可以保證api網(wǎng)關(guān)的健壯和穩(wěn)定阅仔,也能用到ribbon的客戶端負(fù)載均衡功能吹散,
我們在使用zuul搭建api網(wǎng)關(guān)的時候,可以通過hystrix和ribbon的參數(shù)來調(diào)整路由請求的各種超時時間等配置八酒,比如下面這些參數(shù)的設(shè)置空民。
-
hystrix.command.default.execution.isolation.thread.timeoutInMillseconds
:該參數(shù)可以用來設(shè)置api網(wǎng)關(guān)中路由轉(zhuǎn)發(fā)請求hystrixCommand執(zhí)行超時時間,單位為毫秒丘跌。當(dāng)路由轉(zhuǎn)發(fā)請求的命令執(zhí)行時間超過該配置值之后袭景,hystrix會將該執(zhí)行命令標(biāo)記為timeout并拋出異常唁桩,zuul會對該異常進行處理并返回如下的json信息給外部調(diào)用方闭树。
{
"timestamp":14454545234324,
"status":500,
"error":"Internal Server Error",
"exception":"com.netflix.zuul.exception.ZuulException",
"message":"TIMEOUT"
}
-
ribbon.ConnectTimeout
:該參數(shù)用來設(shè)置路由轉(zhuǎn)發(fā)請求的時候,創(chuàng)建請求連接的超時時間荒澡。當(dāng)ribbon.ConnectTimeout
的配置值小于hystrix.command.default.execttion.isolation.thread.timeoutInMilliseconds
配置值的時候报辱,若出現(xiàn)路由請求連接超時時,會自動進行重試路由請求单山,如果請求依然失敗碍现,zuul會返回如下json信息給外部調(diào)用方。
{
"timestamp":14454545234324,
"status":500,
"error":"Internal Server Error",
"exception":"com.netflix.zuul.exception.ZuulException",
"message":"NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED"
}
ribbon.ConnectTimeout
的配置值大于hystrix.command.default.execution.isolation.thread.timeoutInMillseconds
配置值的時候米奸,當(dāng)出現(xiàn)路由請求連接超時時昼接,由于此時對于路由轉(zhuǎn)發(fā)的請求命令已經(jīng)超時,所以不會進行重試路由請求悴晰,而是直接按請求命令超時處理慢睡,返回TIMEOUT的錯誤信息。
-
ribbon.ReadTimeout
:該參數(shù)用來設(shè)置路由轉(zhuǎn)發(fā)請求的超時時間铡溪,它的處理與ribbon.ConnectTimeout
類似漂辐,只是它的超時是對請求連接建立之后的處理時間。當(dāng)ribbon.ReadTimeout
的配置值小于hystrix.command.default.execttion.isolation.thread.timeoutInMilliseconds
配置值的時候棕硫,若路由請求的處理時間超過該配置值并且依賴服務(wù)還沒有響應(yīng)的時候髓涯,會自動進行重試路由請求。如果重試后依然沒有獲得請求響應(yīng)哈扮,zuul會返回NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED
錯誤纬纪。如果ribbon.ReadTimeout
的配置值大于hystrix.command.default.execttion.isolation.thread.timeoutInMilliseconds
配置值,若路由請求的處理時間超過該配置值并且依賴服務(wù)還沒有響應(yīng)時滑肉,不會進行重試路由請求包各,而是直接按請求命令超時處理,返回timeout的錯誤信息赦邻。
根據(jù)上面的介紹我們知道髓棋,在使用zuul的服務(wù)路由時,如果路由轉(zhuǎn)發(fā)請求發(fā)生超時(連接超時或處理超時),只要超時時間的設(shè)置小于hystrix的命令超時時間按声,那么它就會自動發(fā)起重試膳犹。有些背景下,我們需要關(guān)閉重試機制签则,那么可以通過下面的二個參數(shù)進行設(shè)置须床。
zuul.retryable=false
zuul.routes.<route>.retryable=false
其中,zuul.retryable
用來關(guān)閉全局的重試機制渐裂,而zuul.routes.<route>.retryable=false
指定路由關(guān)閉重試機制豺旬。
本博客代碼
代碼地址