考慮一個(gè)問題汁展,外部的應(yīng)用如何來訪問內(nèi)部各種各樣的微服務(wù)呢?在微服務(wù)架構(gòu)中厌殉,后端服務(wù)往往不直接開放給調(diào)用端食绿,而是通過一個(gè)API網(wǎng)關(guān)根據(jù)請求的url,路由到相應(yīng)的服務(wù)公罕。當(dāng)添加API網(wǎng)關(guān)后器紧,在第三方調(diào)用端和服務(wù)提供方之間就創(chuàng)建了一面墻,這面墻直接與調(diào)用方通信進(jìn)行權(quán)限控制楼眷,后將請求均衡分發(fā)給后臺服務(wù)端铲汪。
Spring Cloud Zuul
Spring Cloud Zuul路由是微服務(wù)架構(gòu)的不可或缺的一部分,提供動態(tài)路由罐柳,監(jiān)控掌腰,彈性,安全等的邊緣服務(wù)张吉。Zuul是Netflix出品的一個(gè)基于JVM路由和服務(wù)端的負(fù)載均衡器齿梁。
下面我們通過代碼來了解Zuul是如何工作的
簡單使用
1、添加依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
引入spring-cloud-starter-zuul
包
2肮蛹、配置文件
spring.application.name=gateway-service-zuul
server.port=8888
#這里的配置表示士飒,訪問/it/** 直接重定向到http://127.0.0.1:8080/**
zuul.routes.baidu.path=/it/**
zuul.routes.baidu.url=http://127.0.0.1:8080/
3、啟動類
@EnableZuulProxy
@SpringCloudApplication
public class GatewayServiceZuulApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServiceZuulApplication.class, args);
}
}
啟動類添加@EnableZuulProxy
蔗崎,支持網(wǎng)關(guān)路由。
史上最簡單的zuul案例就配置完了
4扰藕、測試
啟動vicente-cloud-zuul
項(xiàng)目缓苛,在瀏覽器中訪問:http://localhost:8764/it/test
,看到頁面返回了:http://127.0.0.1:8080/test
頁面的信息邓深,如下:
說明訪問vicente-cloud-zuul
的請求自動轉(zhuǎn)發(fā)到了vicente-boot
未桥,并且將結(jié)果返回。
服務(wù)化
通過url映射的方式來實(shí)現(xiàn)zull的轉(zhuǎn)發(fā)有局限性芥备,比如每增加一個(gè)服務(wù)就需要配置一條內(nèi)容冬耿,另外后端的服務(wù)如果是動態(tài)來提供,就不能采用這種方案來配置了萌壳。實(shí)際上在實(shí)現(xiàn)微服務(wù)架構(gòu)時(shí)亦镶,服務(wù)名與服務(wù)實(shí)例地址的關(guān)系在eureka server中已經(jīng)存在了日月,所以只需要將Zuul注冊到eureka server上去發(fā)現(xiàn)其他服務(wù),就可以實(shí)現(xiàn)對serviceId的映射缤骨。
我們結(jié)合示例來說明爱咬,在上面示例項(xiàng)目vicente-cloud-zuul
的基礎(chǔ)上來改造。
1绊起、添加依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
增加spring-cloud-starter-eureka包精拟,添加對eureka的支持。
2虱歪、配置文件
配置修改為:
eureka:
client:
serviceUrl:
defaultZone: http://192.168.243.21:9001/eureka/
server:
port: 8764
spring:
application:
name: vice-zuul-local
zuul:
routes:
client-hi:
path: /hhhi/**
serviceId: service-hi
consumer:
path: /bd-its/**
serviceId: bd-its-deg-service
baidu:
path: /it/**
url: http://192.168.243.21:9090/
啟動完成后蜂绎,zuul會自動注冊發(fā)現(xiàn)的服務(wù)
3、測試
訪問:http://127.0.0.1:8764/it/itsdeg/select/style
,http://127.0.0.1:8764/bd-its/itsdeg/select/style
,http://127.0.0.1:8764/bd-its-deg-service/itsdeg/select/style
笋鄙,
實(shí)際均是訪問的是服務(wù)名為:bd-its-deg-service
的項(xiàng)目师枣,
url地址:http://192.168.243.21:9090/itsdeg/select/style
返回:
說明訪問/bd-its-deg-service的請求自動轉(zhuǎn)發(fā)到了bd-its-deg-service項(xiàng)目,并且將結(jié)果返回局装。
為了更好的模擬服務(wù)集群坛吁,我們復(fù)制BD-ITS-DEG-SERVICE項(xiàng)目,啟動2個(gè)同名的項(xiàng)目铐尚,修改方法拨脉,使兩個(gè)同名項(xiàng)目返回的結(jié)果不一樣
修改完成后啟動項(xiàng)目。測試多次訪問http://127.0.0.1:8764/bd-its-deg-service/itsdeg/select/style
宣增,依次返回:
說明通過zuul成功調(diào)用了BD-ITS-DEG-SERVICE服務(wù)并且做了均衡負(fù)載玫膀。
網(wǎng)關(guān)的默認(rèn)路由規(guī)則
但是如果后端服務(wù)多達(dá)十幾個(gè)的時(shí)候,每一個(gè)都這樣配置也挺麻煩的爹脾,spring cloud zuul已經(jīng)幫我們做了默認(rèn)配置帖旨。默認(rèn)情況下,Zuul會代理所有注冊到Eureka Server的微服務(wù)灵妨,并且Zuul的路由規(guī)則如下:http://ZUUL_HOST:ZUUL_PORT/微服務(wù)在Eureka上的serviceId/**會被轉(zhuǎn)發(fā)到serviceId對應(yīng)的微服務(wù)解阅。
我們注銷掉gateway-service-zuul-eureka項(xiàng)目中關(guān)于路由的配置:
zuul.routes.api-a.path=/producer/**
zuul.routes.api-a.serviceId=spring-cloud-producer
重新啟動后,訪問http://localhost:8888/spring-cloud-producer/hello?name=%E5%B0%8F%E6%98%8E泌霍,測試返回結(jié)果和上述示例相同货抄,說明Spring cloud zuul默認(rèn)已經(jīng)提供了轉(zhuǎn)發(fā)功能。
到此zuul的基本使用我們就介紹完了朱转。
上篇文章主要介紹了Zuul網(wǎng)關(guān)使用模式蟹地,以及自動轉(zhuǎn)發(fā)機(jī)制,但其實(shí)Zuul還有更多的應(yīng)用場景藤为,比如:鑒權(quán)怪与、流量轉(zhuǎn)發(fā)、請求統(tǒng)計(jì)等等缅疟,這些功能都可以使用Zuul來實(shí)現(xiàn)分别。
Zuul的核心
Filter是Zuul的核心遍愿,用來實(shí)現(xiàn)對外服務(wù)的控制。Filter的生命周期有4個(gè)茎杂,分別是“PRE”错览、“ROUTING”、“POST”煌往、“ERROR”倾哺,整個(gè)生命周期可以用下圖來表示。
Zuul大部分功能都是通過過濾器來實(shí)現(xiàn)的刽脖,這些過濾器類型對應(yīng)于請求的典型生命周期羞海。
- PRE: 這種過濾器在請求被路由之前調(diào)用。我們可利用這種過濾器實(shí)現(xiàn)身份驗(yàn)證曲管、在集群中選擇請求的微服務(wù)却邓、記錄調(diào)試信息等。
- ROUTING:這種過濾器將請求路由到微服務(wù)院水。這種過濾器用于構(gòu)建發(fā)送給微服務(wù)的請求腊徙,并使用Apache HttpClient或Netfilx Ribbon請求微服務(wù)。
- POST:這種過濾器在路由到微服務(wù)以后執(zhí)行檬某。這種過濾器可用來為響應(yīng)添加標(biāo)準(zhǔn)的HTTP Header撬腾、收集統(tǒng)計(jì)信息和指標(biāo)、將響應(yīng)從微服務(wù)發(fā)送給客戶端等恢恼。
- ERROR:在其他階段發(fā)生錯(cuò)誤時(shí)執(zhí)行該過濾器民傻。 除了默認(rèn)的過濾器類型,Zuul還允許我們創(chuàng)建自定義的過濾器類型场斑。例如漓踢,我們可以定制一種STATIC類型的過濾器,直接在Zuul中生成響應(yīng)漏隐,而不將請求轉(zhuǎn)發(fā)到后端的微服務(wù)喧半。
Zuul中默認(rèn)實(shí)現(xiàn)的Filter
類型 | 順序 | 過濾器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 標(biāo)記處理Servlet的類型 |
pre | -2 | Servlet30WrapperFilter | 包裝HttpServletRequest請求 |
pre | -1 | FormBodyWrapperFilter | 包裝請求體 |
route | 1 | DebugFilter | 標(biāo)記調(diào)試標(biāo)志 |
route | 5 | PreDecorationFilter | 處理請求上下文供后續(xù)使用 |
route | 10 | RibbonRoutingFilter | serviceId請求轉(zhuǎn)發(fā) |
route | 100 | SimpleHostRoutingFilter | url請求轉(zhuǎn)發(fā) |
route | 500 | SendForwardFilter | forward請求轉(zhuǎn)發(fā) |
post | 0 | SendErrorFilter | 處理有錯(cuò)誤的請求響應(yīng) |
post | 1000 | SendResponseFilter | 處理正常的請求響應(yīng) |
禁用指定的Filter
可以在application.yml中配置需要禁用的filter,格式:
zuul:
FormBodyWrapperFilter:
pre:
disable: true
自定義Filter
實(shí)現(xiàn)自定義Filter青责,需要繼承ZuulFilter的類薯酝,并覆蓋其中的4個(gè)方法。
public class MyFilter extends ZuulFilter {
@Override
String filterType() {
return "pre"; //定義filter的類型爽柒,有pre、route者填、post浩村、error四種
}
@Override
int filterOrder() {
return 10; //定義filter的順序,數(shù)字越小表示順序越高占哟,越先執(zhí)行
}
@Override
boolean shouldFilter() {
return true; //表示是否需要執(zhí)行該filter心墅,true表示執(zhí)行酿矢,false表示不執(zhí)行
}
@Override
Object run() {
return null; //filter需要執(zhí)行的具體操作
}
}
自定義Filter示例
我們假設(shè)有這樣一個(gè)場景,因?yàn)榉?wù)網(wǎng)關(guān)應(yīng)對的是外部的所有請求怎燥,為了避免產(chǎn)生安全隱患瘫筐,我們需要對請求做一定的限制,比如請求中含有Token便讓請求繼續(xù)往下走铐姚,如果請求不帶Token就直接返回并給出提示策肝。
首先自定義一個(gè)Filter,在run()方法中驗(yàn)證參數(shù)是否含有Token隐绵。
public class TokenFilter extends ZuulFilter {
private final Logger logger = LoggerFactory.getLogger(TokenFilter.class);
@Override
public String filterType() {
return "pre"; // 可以在請求被路由之前調(diào)用
}
@Override
public int filterOrder() {
return 0; // filter執(zhí)行順序之众,通過數(shù)字指定 ,優(yōu)先級為0,數(shù)字越大依许,優(yōu)先級越低
}
@Override
public boolean shouldFilter() {
return true;// 是否執(zhí)行該過濾器棺禾,此處為true,說明需要過濾
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
logger.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString());
String token = request.getParameter("token");// 獲取請求的參數(shù)
if (StringUtils.isNotBlank(token)) {
ctx.setSendZuulResponse(true); //對請求進(jìn)行路由
ctx.setResponseStatusCode(200);
ctx.set("isSuccess", true);
return null;
} else {
ctx.setSendZuulResponse(false); //不對其進(jìn)行路由
ctx.setResponseStatusCode(400);
ctx.setResponseBody("token is empty");
ctx.set("isSuccess", false);
return null;
}
}
}
將TokenFilter加入到請求攔截隊(duì)列峭跳,在啟動類中添加以下代碼:
@Bean
public TokenFilter tokenFilter() {
return new TokenFilter();
}
這樣就將我們自定義好的Filter加入到了請求攔截中膘婶。
測試
我們依次啟動示例項(xiàng)目:spring-cloud-eureka
、spring-cloud-producer
蛀醉、spring-cloud-zuul
悬襟,這個(gè)三個(gè)項(xiàng)目均為上一篇示例項(xiàng)目,spring-cloud-zuul
稍微進(jìn)行改造滞欠。
訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo
古胆,返回:token is empty ,請求被攔截返回筛璧。
訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx
逸绎,返回:hello neo,this is first messge夭谤,說明請求正常響應(yīng)棺牧。
通過上面這例子我們可以看出,我們可以使用“PRE”類型的Filter做很多的驗(yàn)證工作朗儒,在實(shí)際使用中我們可以結(jié)合shiro颊乘、oauth2.0等技術(shù)去做鑒權(quán)、驗(yàn)證醉锄。
路由熔斷
當(dāng)我們的后端服務(wù)出現(xiàn)異常的時(shí)候乏悄,我們不希望將異常拋出給最外層,期望服務(wù)可以自動進(jìn)行一降級恳不。Zuul給我們提供了這樣的支持檩小。當(dāng)某個(gè)服務(wù)出現(xiàn)異常時(shí),直接返回我們預(yù)設(shè)的信息烟勋。
我們通過自定義的fallback方法规求,并且將其指定給某個(gè)route來實(shí)現(xiàn)該route訪問出問題的熔斷處理筐付。主要繼承ZuulFallbackProvider接口來實(shí)現(xiàn),ZuulFallbackProvider默認(rèn)有兩個(gè)方法阻肿,一個(gè)用來指明熔斷攔截哪個(gè)服務(wù)瓦戚,一個(gè)定制返回內(nèi)容。
public interface ZuulFallbackProvider {
/**
* The route this fallback will be used for.
* @return The route the fallback will be used for.
*/
public String getRoute();
/**
* Provides a fallback response.
* @return The fallback response.
*/
public ClientHttpResponse fallbackResponse();
}
實(shí)現(xiàn)類通過實(shí)現(xiàn)getRoute方法丛塌,告訴Zuul它是負(fù)責(zé)哪個(gè)route定義的熔斷较解。而fallbackResponse方法則是告訴 Zuul 斷路出現(xiàn)時(shí),它會提供一個(gè)什么返回值來處理請求姨伤。
后來Spring又?jǐn)U展了此類哨坪,豐富了返回方式,在返回的內(nèi)容中添加了異常信息乍楚,因此最新版本建議直接繼承類FallbackProvider
当编。
我們以上面的spring-cloud-producer服務(wù)為例,定制它的熔斷返回內(nèi)容徒溪。
@Component
public class ProducerFallback implements FallbackProvider {
private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class);
//指定要處理的 service忿偷。
@Override
public String getRoute() {
return "spring-cloud-producer";
}
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("The service is unavailable.".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
@Override
public ClientHttpResponse fallbackResponse(Throwable cause) {
if (cause != null && cause.getCause() != null) {
String reason = cause.getCause().getMessage();
logger.info("Excption {}",reason);
}
return fallbackResponse();
}
}
當(dāng)服務(wù)出現(xiàn)異常時(shí),打印相關(guān)異常信息臊泌,并返回”The service is unavailable.”鲤桥。
啟動項(xiàng)目spring-cloud-producer-2,這時(shí)候服務(wù)中心會有兩個(gè)spring-cloud-producer項(xiàng)目渠概,我們重啟Zuul項(xiàng)目茶凳。再手動關(guān)閉spring-cloud-producer-2項(xiàng)目,多次訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx
播揪,會交替返回:
hello neo贮喧,this is first messge
The service is unavailable.
...
根據(jù)返回結(jié)果可以看出:spring-cloud-producer-2項(xiàng)目已經(jīng)啟用了熔斷,返回:The service is unavailable.
Zuul 目前只支持服務(wù)級別的熔斷猪狈,不支持具體到某個(gè)URL進(jìn)行熔斷箱沦。
路由重試
有時(shí)候因?yàn)榫W(wǎng)絡(luò)或者其它原因,服務(wù)可能會暫時(shí)的不可用雇庙,這個(gè)時(shí)候我們希望可以再次對服務(wù)進(jìn)行重試谓形,Zuul也幫我們實(shí)現(xiàn)了此功能,需要結(jié)合Spring Retry 一起來實(shí)現(xiàn)疆前。下面我們以上面的項(xiàng)目為例做演示寒跳。
添加Spring Retry依賴
首先在spring-cloud-zuul項(xiàng)目中添加Spring Retry依賴。
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
開啟Zuul Retry
再配置文件中配置啟用Zuul Retry
#是否開啟重試功能
zuul.retryable=true
#對當(dāng)前服務(wù)的重試次數(shù)
ribbon.MaxAutoRetries=2
#切換相同Server的次數(shù)
ribbon.MaxAutoRetriesNextServer=0
這樣我們就開啟了Zuul的重試功能竹椒。
測試
我們對spring-cloud-producer-2進(jìn)行改造冯袍,在hello方法中添加定時(shí),并且在請求的一開始打印參數(shù)。
@RequestMapping("/hello")
public String index(@RequestParam String name) {
logger.info("request two name is "+name);
try{
Thread.sleep(1000000);
}catch ( Exception e){
logger.error(" hello two error",e);
}
return "hello "+name+"康愤,this is two messge";
}
重啟 spring-cloud-producer-2和spring-cloud-zuul項(xiàng)目。
訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx
舶吗,當(dāng)頁面返回:The service is unavailable.
時(shí)查看項(xiàng)目spring-cloud-producer-2后臺日志如下:
2018-01-22 19:50:32.401 INFO 19488 --- [io-9001-exec-14] o.s.c.n.z.f.route.FallbackProvider : request two name is neo
2018-01-22 19:50:33.402 INFO 19488 --- [io-9001-exec-15] o.s.c.n.z.f.route.FallbackProvider : request two name is neo
2018-01-22 19:50:34.404 INFO 19488 --- [io-9001-exec-16] o.s.c.n.z.f.route.FallbackProvider : request two name is neo
說明進(jìn)行了三次的請求征冷,也就是進(jìn)行了兩次的重試。這樣也就驗(yàn)證了我們的配置信息誓琼,完成了Zuul的重試功能检激。
注意
開啟重試在某些情況下是有問題的,比如當(dāng)壓力過大腹侣,一個(gè)實(shí)例停止響應(yīng)時(shí)叔收,路由將流量轉(zhuǎn)到另一個(gè)實(shí)例,很有可能導(dǎo)致最終所有的實(shí)例全被壓垮傲隶。說到底饺律,斷路器的其中一個(gè)作用就是防止故障或者壓力擴(kuò)散。用了retry跺株,斷路器就只有在該服務(wù)的所有實(shí)例都無法運(yùn)作的情況下才能起作用复濒。這種時(shí)候,斷路器的形式更像是提供一種友好的錯(cuò)誤信息乒省,或者假裝服務(wù)正常運(yùn)行的假象給使用者巧颈。
不用retry,僅使用負(fù)載均衡和熔斷袖扛,就必須考慮到是否能夠接受單個(gè)服務(wù)實(shí)例關(guān)閉和eureka刷新服務(wù)列表之間帶來的短時(shí)間的熔斷砸泛。如果可以接受,就無需使用retry蛆封。
跨域問題處理
在項(xiàng)目中添加上ZuulFilter的文件
public class PreFilter extends ZuulFilter{
private Logger LOG =Logger.getLogger(PreFilter.class);
@Override
public Object run() {
// TODO Auto-generated method stub
// return checkToken();
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletResponse response = ctx.getResponse();
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
ctx.setSendZuulResponse(true);// 對該請求進(jìn)行路由
ctx.setResponseStatusCode(200);
ctx.set("isSuccess", true);// 設(shè)值唇礁,讓下一個(gè)Filter看到上一個(gè)Filter的狀態(tài)
LOG.info(" PreFilter1 ok");
return null;
}
@Override
public boolean shouldFilter() {
// TODO Auto-generated method stub
return true;
}
@Override
public int filterOrder() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String filterType() {
// TODO Auto-generated method stub
return "pre";
}
}
這樣前端通過zuul訪問服務(wù)器接口就可以跨域訪問。
還有個(gè)問題:就是設(shè)置跨域訪問只能設(shè)置一處娶吞,如果zuul調(diào)用的cloud服務(wù)也設(shè)置了跨域訪問垒迂,這樣就會報(bào)錯(cuò),這個(gè)要怎么解決呢?可以在application配置文件中使用ZUUL配置忽略頭部信息
zuul:
#需要忽略的頭部信息妒蛇,不再傳播到其他服務(wù)
sensitive-headers: Access-Control-Allow-Origin
ignored-headers: Access-Control-Allow-Origin,H-APP-Id,Token,APPToken
404頁面處理
springboot2中可以通過ErrorPageRegistrar接口來實(shí)現(xiàn)錯(cuò)誤跳轉(zhuǎn)不同頁面
自定義404和500等頁面:
@Configuration
public class ErrorPageConfig {
@Bean
public ErrorPageRegistrar errorPageRegistrar() {
return new ErrorPageRegistrar() {
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
// registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error404"),
registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error404"),
new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error500"));
}
};
}
}
ErrorPage中第一個(gè)參數(shù)是錯(cuò)誤的狀態(tài)机断,第二個(gè)參數(shù)是發(fā)生這個(gè)錯(cuò)誤要跳轉(zhuǎn)的鏈接,比如NOT_FOUND是404的錯(cuò)誤代碼绣夺,發(fā)生404的時(shí)候就會跳轉(zhuǎn)到error404的鏈接吏奸,我們再配置一個(gè)controller,用于控制跳轉(zhuǎn)錯(cuò)誤頁面
@Controller
public class ErrorController {
@RequestMapping("/error404")
public String error() {
return "redirect:/bd-portal-view/";
}
}
第二種方法
springboot2我們不需要自定義error的filter,最后都會進(jìn)入到/error映射的方法里面毁涉。
然后我們看/error娘汞,對應(yīng)的文件是BasicErrorController漫仆,里面有兩個(gè)方法:
errorHtml和error泊碑,看方法名就知道一個(gè)是返回html一個(gè)是返回json坤按、xml對象的。他們是根據(jù)客戶端請求是的accept進(jìn)行選擇具體返回內(nèi)容的馒过。這里面返回的參數(shù)可以這樣進(jìn)行配置,當(dāng)然如果你想自己重新/error這個(gè)映射也是可以的臭脓,如下配置:
@Controller
public class GlobalErrorController implements ErrorController {
@Override
public String getErrorPath() {
return "/error";
}
@ResponseBody
@RequestMapping("/error")
public String error() {
return "自定義錯(cuò)誤信息";
}
}
Zuul高可用
我們實(shí)際使用Zuul的方式如上圖,不同的客戶端使用不同的負(fù)載將請求分發(fā)到后端的Zuul腹忽,Zuul在通過Eureka調(diào)用后端服務(wù)来累,最后對外輸出。因此為了保證Zuul的高可用性窘奏,前端可以同時(shí)啟動多個(gè)Zuul實(shí)例進(jìn)行負(fù)載嘹锁,在Zuul的前端使用Nginx或者F5進(jìn)行負(fù)載轉(zhuǎn)發(fā)以達(dá)到高可用性。
Zuul的高可用
Zuul的高可用非常關(guān)鍵着裹,因?yàn)橥獠空埱蟮胶蠖宋⒎?wù)的流量都會經(jīng)過Zuul领猾。故而在生產(chǎn)環(huán)境中,我們一般都需要部署高可用的Zuul以避免單點(diǎn)故障求冷。
筆者分兩種場景討論Zuul的高可用瘤运。
Zuul客戶端也注冊到了Eureka Server上
這種情況下,Zuul的高可用非常簡單匠题,只需將多個(gè)Zuul節(jié)點(diǎn)注冊到Eureka Server上拯坟,就可實(shí)現(xiàn)Zuul的高可用。此時(shí)韭山,Zuul的高可用與其他微服務(wù)的高可用沒什么區(qū)別郁季。
Zuul高可用架構(gòu)圖
當(dāng)Zuul客戶端也注冊到Eureka Server上時(shí),只需部署多個(gè)Zuul節(jié)點(diǎn)即可實(shí)現(xiàn)其高可用钱磅。Zuul客戶端會自動從Eureka Server中查詢Zuul Server的列表梦裂,并使用Ribbon負(fù)載均衡地請求Zuul集群。
Zuul客戶端未注冊到Eureka Server上
現(xiàn)實(shí)中盖淡,這種場景往往更常見,例如褪迟,Zuul客戶端是一個(gè)手機(jī)APP——我們不可能讓所有的手機(jī)終端都注冊到Eureka Server上冗恨。這種情況下,我們可借助一個(gè)額外的負(fù)載均衡器來實(shí)現(xiàn)Zuul的高可用味赃,例如Nginx掀抹、HAProxy、F5等心俗。
Zuul高可用架構(gòu)圖
Zuul客戶端將請求發(fā)送到負(fù)載均衡器傲武,負(fù)載均衡器將請求轉(zhuǎn)發(fā)到其代理的其中一個(gè)Zuul節(jié)點(diǎn)蓉驹。這樣,就可以實(shí)現(xiàn)Zuul的高可用揪利。
參考:
springcloud(十):服務(wù)網(wǎng)關(guān)zuul初級篇
springcloud(十一):服務(wù)網(wǎng)關(guān)Zuul高級篇
Zuul的高可用
zuul統(tǒng)一異常處理