關(guān)于服務(wù)網(wǎng)關(guān)Spring Cloud Zuul(Finchley版本)
一、Zuul簡介
Zuul作為微服務(wù)系統(tǒng)的網(wǎng)關(guān)組件,用于構(gòu)建邊界服務(wù)(Edge Service),致力于動態(tài)路由、過濾、監(jiān)控、彈性伸縮和安全。其在微服務(wù)架構(gòu)中有著重要的作用,主要體現(xiàn)在以下六個方面:
Zull、Ribbon以及Eureka相結(jié)合可以實現(xiàn)智能路由和負載均衡的功能,Zull可以按照某種策略將請求分發(fā)到不同的實例上;
網(wǎng)關(guān)作為邊界服務(wù),將內(nèi)部服務(wù)的API接口進行聚合并統(tǒng)一對外暴露接口。保護內(nèi)部服務(wù)的API接口,防止內(nèi)部服務(wù)被外界調(diào)用泄露敏感信息;
網(wǎng)關(guān)可以對用戶的身份權(quán)限進行認證,防止非法請求API接口;
網(wǎng)關(guān)可以實現(xiàn)監(jiān)控功能,實時日志輸出,對請求進行記錄;
網(wǎng)關(guān)可以用來實現(xiàn)流量監(jiān)控,在高流量的情況下,對服務(wù)進行降級;
API接口從內(nèi)部服務(wù)分離出來,便于測試
二、請求路由
使用Spring Cloud Zuul實現(xiàn)路由的規(guī)則是十分簡單的。路由方式包括兩種:傳統(tǒng)路由方式,面向服務(wù)的路由方式。
2.1 傳統(tǒng)路由
下面我們看以下配置:
zuul.routes.holiday.path=/holiday/** zuul.routes.holiday.url=http://localhosst:8080/
該規(guī)則配置表示所有符合/holiday/** 規(guī)則的訪問都會被路由轉(zhuǎn)發(fā)到http://localhosst:8080/地址上,例如:當我們訪問http://localhost:5555/holiday/getAllDays,API網(wǎng)關(guān)就會將請求轉(zhuǎn)發(fā)到http://localhost:8080/holiday/getAllDays提供的微服務(wù)接口上。其中holiday為微服務(wù)的名稱,可以任意定義,但是一組path和url映射關(guān)系的路由名稱必須相同,下面面向服務(wù)的路由方式也是如此。
2.2 面向服務(wù)的路由
Spring Cloud Zuul 與 Spring Cloud Eureka 可以實現(xiàn)無縫對接實現(xiàn)面向服務(wù)的路由。我們讓路由的path映射到具體的服務(wù)上,而具體的url交由Eureka的服務(wù)發(fā)現(xiàn)機制去自動維護。具體配置如下(其他配置參考下面的實戰(zhàn)):
zuul.routes.holiday.path=/holiday/** zuul.routes.holiday.service-id=holiday
通過上面的配置,我們不需要維護具體實例的位置,是得維護工作十分簡單。另外,面向服務(wù)打的路由默認實現(xiàn)了負載均衡,而傳統(tǒng)路由還需要手動添加所有實例的位置。
三、路由規(guī)則
Spring Cloud Zuul提供了默認的路由規(guī)則,當然我們也可以修改這個路由規(guī)則。
3.1 默認路由規(guī)則
Zull與Eureka的配合使用后,Zull會默認配置一個路由規(guī)則,這些默認規(guī)則的path會使用service-id配置的服務(wù)名作為請求的前綴。例如:有holiday服務(wù),他的默認規(guī)則如下
zuul.routes.holiday.path=/holiday/** zuul.routes.holiday.service-id=holiday
由于默認情況下所有Eureka上的服務(wù)都會被Zuul自動創(chuàng)建映射關(guān)系進行路由,這會使得一些我們不希望對外開放的服務(wù)也被外部訪問到。這個時候我們可以配置zuul.ignored-services參數(shù)來設(shè)置一個服務(wù)名匹配表達式進行判斷,如果服務(wù)名匹配表達式,那么Zull將跳過這個服務(wù),不為其創(chuàng)建路由規(guī)則。例如:zuul.ignored-services=*表示對所有的服務(wù)不自動創(chuàng)建路由規(guī)則,這樣我們就需要為每個服配置路由規(guī)則。
3.2 自定義路由規(guī)則
有這樣一個場景,由于業(yè)務(wù)的擴展,版本的升級,服務(wù)存在不同的版本。比如我們有這樣的命名:holiday-v1、holiday-v2,默認情況下,Zuul自動為服務(wù)創(chuàng)建的路由表達式會采用服務(wù)名做前綴,針對holiday-v1就會產(chǎn)生/holiday-v1,/holiday-v2兩個路徑來做映射,但這樣生成的表達式規(guī)則較為單一,不利于路徑規(guī)則的管理。
通常,對于上面這種情況,我們希望是生成的路徑為/v1/holiday,/v2/holiday。我們可以通過自定義路由規(guī)則來實現(xiàn),具體代碼如下:
@Bean public PatternServiceRouteMapper serviceRouteMapper(){ ? return new PatternServiceRouteMapper( ? ? "(?<name>^.+)-(?<version>v.+$)", ? ? "${version}/${name}"); }
PatternServiceRouteMapper對象可以讓開發(fā)者通過正則表達式來自定義服務(wù)于路由映射的生成關(guān)系。
四、Zuul的過濾器
Zull有請求過濾的功能,其過濾器可以在Http請求的發(fā)起和響應(yīng)返回期間執(zhí)行一系列的過濾器。
Zuul包擴以下四種過濾器:
PRE
:該類型的filters在Request routing到源web-service之前執(zhí)行。可以進行一些權(quán)限認證,日志記錄,或者額外給Request增加一些屬性供后續(xù)的filter使用;ROUTING
:該類型的filters用于把Request routing到源web-service,源web-service是實現(xiàn)業(yè)務(wù)邏輯的服務(wù)。這里使用HttpClient請求web-service;POST
:該類型的filters在ROUTING返回Response后執(zhí)行。用來實現(xiàn)對Response結(jié)果進行修改,收集統(tǒng)計數(shù)據(jù)以及把Response傳輸會客戶端;ERROR
:上面三個過程中任何一個出現(xiàn)錯誤都交由ERROR類型的filters進行處理。
Zuul過濾器具有以下關(guān)鍵特性:
Type(類型)
:Zuul過濾器的類型,這個類型決定過濾器在哪個階段執(zhí)行,例如:pre,post等階段;Execution Order(執(zhí)行順序)
:規(guī)定了過濾器的執(zhí)行順序,Order的值越小,越先執(zhí)行;Criteria(標準)
:Filters執(zhí)行所需條件Action(行動)
:如果符合執(zhí)行條件,則執(zhí)行Action(具體邏輯代碼)
示例如下:
public class MyFilter extends ZuulFilter { ? ? @Override ? ? public Object run() { ? ? ? ? ? ? RequestContext ctx = RequestContext.getCurrentContext(); ? ? ? ? HttpServletRequest request = ctx.getRequest(); ? ? ? ? System.out.println(String.format("%s AccessUserNameFilter request to %s", request.getMethod(), request.getRequestURL().toString())); ? ? ? ? // 獲取請求的參數(shù) ? ? ? ? String username = request.getParameter("username"); ? ? ? ? // 如果請求的參數(shù)不為空,且值為chhliu時,則通過 ? ? ? ? if(null != username && username.equals("chhliu")) { ? ? ? ? ? // 對該請求進行路由 ? ? ? ? ? ? ctx.setSendZuulResponse(true); ? ? ? ? ? ? ctx.setResponseStatusCode(200); ? ? ? ? ? ? // 設(shè)值,讓下一個Filter看到上一個Filter的狀態(tài) ? ? ? ? ? ? ctx.set("isSuccess", true); ? ? ? ? ? ? return null; ? ? ? ? }else{ ? ? ? ? ? // 過濾該請求,不對其進行路由 ? ? ? ? ? ? ctx.setSendZuulResponse(false); ? ? ? ? ? ? // 返回錯誤碼 ? ? ? ? ? ? ctx.setResponseStatusCode(401); ? ? ? ? ? ? // 返回錯誤內(nèi)容 ? ? ? ? ? ? ctx.setResponseBody("{\"result\":\"username is not correct!\"}"); ? ? ? ? ? ? ctx.set("isSuccess", false); ? ? ? ? ? ? return null; ? ? ? ? } ? ? } ? ? @Override ? ? public boolean shouldFilter() { ? ? // 是否執(zhí)行該過濾器,此處為true,說明需要過濾 ? ? ? ? return true; ? ? } ? ? @Override ? ? public int filterOrder() { ? ? // 優(yōu)先級為0,數(shù)字越大,優(yōu)先級越低 ? ? ? ? return 0; ? ? } ? ? @Override ? ? public String filterType() { ? ? // 前置過濾器 ? ? ? ? return "pre"; ? ? } }
Zuul請求的生命周期如圖所示:
五、設(shè)置熔斷
通常在服務(wù)無法提供服務(wù)的時候,需要執(zhí)行熔斷。zuul上實現(xiàn)熔斷需要實現(xiàn)FallbackProvider的接口。實現(xiàn)接口中的兩個方法:getRoute()用于指定應(yīng)用在哪個服務(wù)上;fallbackResponse()進入熔斷功能的執(zhí)行邏輯。示例如下:
@Component public class CustomZuulFallbackHandler implements FallbackProvider { ? private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class); ? /** ? ?* 指定處理的service ? ?* ? ?* @return ? ?*/ ? @Override ? public String getRoute() { ? ? return "*"; ? } ? public ClientHttpResponse fallbackResponse(String route) { ? ? 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((route+" is unavailable.").getBytes()); ? ? ? } ? ? ? @Override ? ? ? public HttpHeaders getHeaders() { ? ? ? ? HttpHeaders headers = new HttpHeaders(); ? ? ? ? headers.setContentType(MediaType.APPLICATION_JSON); ? ? ? ? return headers; ? ? ? } ? ? }; ? } ? @Override ? public ClientHttpResponse fallbackResponse(String route, Throwable cause) { ? ? if (cause != null) { ? ? ? String reason = cause.getMessage(); ? ? ? logger.info("Excption {}",reason); ? ? } ? ? return fallbackResponse(route); ? } }
六、實戰(zhàn)
6.1 pom文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ? ? ? ? ?xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> ? ? <parent> ? ? ? ? <groupId>com.southgis.ibase.parent</groupId> ? ? ? ? <artifactId>parentWebService</artifactId> ? ? ? ? <version>2.0.1-SNAPSHOT</version> ? ? ? ? <relativePath>../../parent/parentWebService/pom.xml</relativePath> ? ? </parent> ? ? <modelVersion>4.0.0</modelVersion> ? ? <artifactId>api-gateway</artifactId> ? ? <groupId>com.southgis.ibase.systemassistance</groupId> ? ? <version>2.0.1-SNAPSHOT</version> ? ? <packaging>war</packaging> ? ? <description>網(wǎng)關(guān)服務(wù)</description> ? ? <dependencies> ? ? ? <!--服務(wù)注冊與發(fā)現(xiàn)--> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.springframework.cloud</groupId> ? ? ? ? ? ? <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> ? ? ? ? </dependency> ? ? ? ? <!--配置中心--> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.springframework.cloud</groupId> ? ? ? ? ? ? <artifactId>spring-cloud-config-client</artifactId> ? ? ? ? </dependency> ? ? ? ? <!--路由網(wǎng)關(guān)--> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.springframework.cloud</groupId> ? ? ? ? ? ? <artifactId>spring-cloud-starter-netflix-zuul</artifactId> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.springframework.boot</groupId> ? ? ? ? ? ? <artifactId>spring-boot-starter-web</artifactId> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.springframework.retry</groupId> ? ? ? ? ? ? <artifactId>spring-retry</artifactId> ? ? ? ? </dependency> ? ? ? ? <!--cas 客戶端--> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.jasig.cas.client</groupId> ? ? ? ? ? ? <artifactId>cas-client-core</artifactId> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.springframework.boot</groupId> ? ? ? ? ? ? <artifactId>spring-boot-starter-test</artifactId> ? ? ? ? ? ? <scope>test</scope> ? ? ? ? </dependency> ? ? </dependencies> ? ? <build> ? ? ? ? <finalName>apiGateway</finalName> ? ? ? ? <plugins> ? ? ? ? ? ? <plugin> ? ? ? ? ? ? ? ? <groupId>org.springframework.boot</groupId> ? ? ? ? ? ? ? ? <artifactId>spring-boot-maven-plugin</artifactId> ? ? ? ? ? ? ? ? <configuration> ? ? ? ? ? ? ? ? ? ? <mainClass>com.southgis.ibase.systemassistance.ApiGatewayCustomApplication</mainClass> ? ? ? ? ? ? ? ? </configuration> ? ? ? ? ? ? ? ? <executions> ? ? ? ? ? ? ? ? ? ? <execution> ? ? ? ? ? ? ? ? ? ? ? ? <goals> ? ? ? ? ? ? ? ? ? ? ? ? ? ? <goal>repackage</goal> ? ? ? ? ? ? ? ? ? ? ? ? </goals> ? ? ? ? ? ? ? ? ? ? </execution> ? ? ? ? ? ? ? ? </executions> ? ? ? ? ? ? </plugin> ? ? ? ? </plugins> ? ? </build> </project>
6.2 配置文件
bootstrap.properties
#服務(wù)名 對應(yīng)配置文件中的{application}部分 spring.application.name=apiGateway #對應(yīng)前配置文件中的{profile}部分 spring.cloud.config.profile=dev2 #配置訪問路徑 server.servlet.context-path=/eureka-server #注冊中心 eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka-server/eureka #為監(jiān)控端點 /info和/health端點也加上類似的前綴 management.server.servlet.context-path=/apiGateway eureka.instance.statusPageUrlPath=${management.server.servlet.context-path}/actuator/info eureka.instance.healthCheckUrlPath=${management.server.servlet.context-path}/actuator/health #通過服務(wù)連接配置中心 #spring.cloud.config.discovery.enabled=true #spring.cloud.config.discovery.serviceId=config-server spring.cloud.config.uri = http://localhost:8080/config-server #配置文件獲取失敗快速返回 spring.cloud.config.failFast=true #日志配置 #logging.config=classpath:logback-spring.xml #logging.path=D:/ibase/logs/holiday #logging.pattern.console=[%d{yyyy-MM-dd HH:mm:ss}] -- [%-5p]: [%c] -- %m%n #logging.pattern.file=[%d{yyyy-MM-dd HH:mm:ss}] -- [%-5p]: [%c] -- %m%n
apiGateway-dev2.properties
#訪問端口 server.port=8080 #設(shè)置session超時時間為540分鐘 server.servlet.session.timeout=PT540M #zuul默認為所有服務(wù)開啟默認的路由,為了服務(wù)安全,此處關(guān)閉 zuul.ignored-services=* #代碼字典服務(wù)路由 zuul.routes.codedict.path=/codedict/** zuul.routes.codedict.service-id=codedict #是否轉(zhuǎn)發(fā)后還帶轉(zhuǎn)發(fā)特征的字符 zuul.routes.codedict.strip-prefix=false #行政區(qū)劃服務(wù)路由 zuul.routes.adminzone.path=/adminzone/** zuul.routes.adminzone.service-id=adminzone zuul.routes.adminzone.strip-prefix=false #是否開啟路由重試 zuul.retryable=true #對當前服務(wù)的重試次數(shù) ribbon.MaxAutoRetries=2 #切換實例的重試次數(shù) ribbon.MaxAutoRetriesNextServer=0 #請求處理的超時時間 ribbon.ReadTimeout=6000 #請求連接的超時時間 ribbon.ConnectTimeout=6000 #對所有操作請求都進行重試 ribbon.OkToRetryOnAllOperations=true #將 hystrix 的超時時間設(shè)置成 5000 毫秒(hystrix超時時間小于ribbon連接超時時間,先走hystrix) hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
6.3 過濾器配置
@Configuration public class ApiGatewayFilter extends ZuulFilter { ? @Override ? public String filterType() { ? ? return "pre"; ? } ? @Override ? public int filterOrder() { ? ? return 0; ? } ? @Override ? public boolean shouldFilter() { ? ? return true; ? } ? @Override ? public Object run() throws ZuulException { ? ? RequestContext context = RequestContext.getCurrentContext(); ? ? HttpServletRequest request = context.getRequest(); ? ? Principal principal = request.getUserPrincipal(); ? ? //獲取用戶的登錄id ? ? String userId = principal.getName(); ? ? context.addZuulRequestHeader("X-AUTH-ID",userId); ? ? return null; ? } }
在這里我們將獲取的登錄用戶id設(shè)置到了請求頭中傳遞給內(nèi)部服務(wù),內(nèi)部服務(wù)可以通過下面的代碼進行獲?。?/p>
String user = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("X-AUTH-ID");
6.4 熔斷配置
@Component public class CustomZuulFallbackHandler implements FallbackProvider { ? private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class); ? /** ? ?* 指定處理的service ? ?* ? ?* @return ? ?*/ ? @Override ? public String getRoute() { ? ? return "*"; ? } ? public ClientHttpResponse fallbackResponse(String route) { ? ? 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((route+" is unavailable.").getBytes()); ? ? ? } ? ? ? @Override ? ? ? public HttpHeaders getHeaders() { ? ? ? ? HttpHeaders headers = new HttpHeaders(); ? ? ? ? headers.setContentType(MediaType.APPLICATION_JSON); ? ? ? ? return headers; ? ? ? } ? ? }; ? } ? @Override ? public ClientHttpResponse fallbackResponse(String route, Throwable cause) { ? ? if (cause != null) { ? ? ? String reason = cause.getMessage(); ? ? ? logger.info("Excption {}",reason); ? ? } ? ? return fallbackResponse(route); ? } }
6.5 啟動類
** ?* 路由網(wǎng)關(guān)服務(wù)部署啟動類 ?* ?* @author simon ?**/ @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableZuulProxy @EnableEurekaClient @SpringCloudApplication public class ApiGatewayMicroApplication { ? public static void main(String[] args) { ? ? SpringApplication.run(ApiGatewayMicroApplication.class, args); ? } }
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- 詳解Spring Cloud Zuul網(wǎng)關(guān)修改為短連接方法
- SpringCloud之Zuul網(wǎng)關(guān)原理及其配置講解
- SpringCloud網(wǎng)關(guān)(Zuul)如何給多個微服務(wù)之間傳遞共享參數(shù)
- 解決spring cloud zuul與nginx的域名轉(zhuǎn)發(fā)問題
- SpringCloud Zuul實現(xiàn)負載均衡和熔斷機制方式
- SpringCloud如何實現(xiàn)Zuul集群(負載均衡)
- spring cloud zuul 與 sentinel的結(jié)合使用操作
- SpringCloud zuul 網(wǎng)關(guān)如何解決跨域問題
- Spring?Cloud詳細講解zuul集成Eureka流程
相關(guān)文章
Java8日期類LocalDate、LocalTime和LocalDateTime使用方法詳解
這篇文章主要給大家介紹了關(guān)于Java8日期類LocalDate、LocalTime和LocalDateTime使用方法的相關(guān)資料,LocalDateTime是JDK1.8出現(xiàn)的新特性,解決線程不安全的問題,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2023-11-11java ThreadLocal線程局部變量常用方法使用場景示例詳解
這篇文章主要介紹了為大家java ThreadLocal線程局部變量常用方法使用場景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07java實現(xiàn)中綴表達式轉(zhuǎn)后綴的方法
這篇文章主要為大家詳細介紹了java實現(xiàn)中綴表達式轉(zhuǎn)后綴的表達式方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-11-11詳解Spring Data JPA動態(tài)條件查詢的寫法
本篇文章主要介紹了Spring Data JPA動態(tài)條件查詢的寫法 ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06

springboot整合mybatis-plus代碼生成器的配置解析