一、創建SpringBoot項目
用mavan搭建也可以。(重要的是后面pom里應該引入那些依賴,application.yml怎么配置)
由于開始構建項目時選擇了Eureka Server,所以pom.xml中不需要手動添加依賴了
首先在啟動類SpringcloudApplication中添加EurekaServer的注解:@EnableEurekaServer
二、application.yml
server:port: 9000 #eureka注冊中心服務端口eureka:instance:hostname: localhostclient:register-with-eureka: false #不向eureka注冊中心注冊。也就是為了關閉自己向自己注冊,eureka默認要向自己注冊fetch-registry: falseservice-url:defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
訪問注冊中心:按照配置的端口號訪問。我這里配了9000,端口隨意,如下圖代表Eureka注冊中心部署成功了。
顯示沒有實例在此中心注冊。沒事接下來我們開始配置zuul網關,它也是一個服務需要注冊到此注冊中心來。
三、提供Service服務
eurekaservice01,eurekaservice02,eurekaservice03,eurekaservice04,第一個如下,第二三四個按著第一個的建,修改一下名字,端口號還有方法返回值
需要用到mvc
服務提供者同樣需要注冊到注冊中心
接下來是同樣的套路,改yml,啟動類添加注解:@EnableEurekaClient
yml配置如下(因為是測試所有很簡陋):
server:port: 8900 # 服務提供方# 指定當前eureka客戶端的注冊地址,
eureka:client:service-url:defaultZone: http://${eureka.instance.hostname}:9000/eureka/instance:hostname: localhost#當前服務名稱
spring:application:name: eurekaservice1
注意當前服務名稱。這里用于注冊到注冊中心的名字,還可以啟動很多同樣為eurekaservice1的名字的微服務到注冊中心,zuul從注冊中心Eureka Server獲取所有服務名為eurekaservice01的服務列表后,會采用負載均衡策略訪問其中一臺服務提供者獲取資源。
下圖我們之前zuul中的配置serviceId就是指向這里的服務名稱,這是微服務調用的精髓,通過服務名調用。
既然是服務提供者 ,這里要編寫controller類了:新建controller包,新建Test01類,編寫REST的方法。返回服務1
按照服務提供者1的搭建方式,現在搭建服務提供者2:
同樣是新建模組,名字改為…02,yml配置文件中端口號與服務1要不同,服務名稱相同,為了測試負載均衡,REST方法返回值設為服務2。
server:port: 8901 # 服務提供方# 指定當前eureka客戶端的注冊地址,
eureka:client:service-url:defaultZone: http://${eureka.instance.hostname}:9000/eureka/instance:hostname: localhost#當前服務名稱
spring:application:name: eurekaservice1
同樣的方法再建兩個服務提供者3和4,服務名稱都為eurekaservice2,端口號分別為8902,和8903。REST方法返回值分別為,服務3和服務4。
四、Zuul網關服務
搭建了注冊中心,和服務的提供者,我們現在開始搭建Zuul網關服務,最后通過zuul訪問注冊中心獲取服務列表,然后訪問服務提供者。
這里模組名設為zuul。包名設置為zuul,等會生成的啟動類就會是帶有zuul了。EurekaServer也可以這樣只是我開始搭的時候沒有注意到。
要Eureka的客戶端組件,和zuul組件,點next
模組名設為zuul,不強制
項目結構:
在啟動類配置注解 @EnableEurekaClient,@EnableZuulProxy,@EnableZuulProxy可以稱為@EnableZuulServer的增強版,當Zuul與Eureka、Ribbon等組件配合使用時,我們使用@EnableZuulProxy
配置pom文件:
啟動項目
再去注冊中心看就能看見zuul服務已經被注冊到注冊中心了
五、Zuul的訪問
有一個網關服務zuul,一個注冊中心eurekaserver,4個服務提供者eurekaservice,4個服務提供者
其中兩個提供服務名為eurekaservice1的服務,另外兩個提供eurekaservice2的服務,現在我們來啟動4個服務提供者,在注冊中心查看,并通過網關訪問測試網關的服務是否正常。
總項目結構:
啟動后注冊中心查看:
如之前所想,兩個服務名下各兩臺服務提供者。
現在回想之前zuul的路由配置:
訪問zuul網關的test01/**下的任何服務都會給我轉發到服務名為eurekaservice1下的01和02服務下。
第一次訪問:test01
第二次訪問:test01
可以看見zuul網關做了轉發和負載均衡,使用的是ribbon輪詢的方式負載均衡。
那么可以猜想到我們訪問test02,zuul網關會在服務名為eurekaservice2的服務3和服務4之間去訪問了。我們來看看:
至此已經實踐完成zuul網關的基本功能轉發和負載均衡。
Zuul網關的應用
通過zuul訪問服務的,URL地址默認格式為:http://zuulHostIp:port/要訪問的服務名稱/服務中的URL
服務名稱:properties配置文件中的spring.application.name
服務的URL:就是對應的服務對外提供的URL路徑監聽
一、網關依賴注入
<!-- spring cloud Eureka Client 啟動器 -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<!-- zuul網關的重試機制,不是使用ribbon內置的重試機制是借助spring-retry組件實現的重試開啟zuul網關重試機制需要增加下述依賴-->
<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId>
</dependency>
二、啟動類
/*** @EnableZuulProxy - 開啟Zuul網關。* 當前應用是一個Zuul微服務網關。會在Eureka注冊中心中注冊當前服務。并發現其他的服務。* Zuul需要的必要依賴是spring-cloud-starter-zuul。*/
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {public static void main(String[] args) {SpringApplication.run(ZuulApplication.class, args);}
}
三、配置文件:網關全局變量配置
# URL路徑匹配
# 使用路徑方式匹配路由規則。
# 參數key結構:zuul.routes.customName.path=xxx
# 用于配置路徑匹配規則。
# 其中customName自定義。通常使用要調用的服務名稱,方便后期管理
# 可使用的通配符有:* ** ?
# ? 單個字符
# * 任意多個字符,不包含多級路徑
# ** 任意多個字符,包含多級路徑
zuul.routes.eureka-application-service.path=/api/**
# 參數key結構:zuul.routes.customName.url=xxx
# url用于配置符合path的請求路徑路由到的服務地址。
zuul.routes.eureka-application-service.url=http://127.0.0.1:8080/# 服務名稱匹配
# service id pattern 通過服務名稱路由
# key結構 :zuul.routes.customName.path=xxx
# 路徑匹配規則 符合path的請求路徑直接路由到customName對應的服務上
zuul.routes.eureka-application-service.path=/api/**
# key結構 :zuul.routes.customName.serviceId=xxx
# serviceId用于配置符合path的請求路徑路由到的服務名稱。如果只配置path,不配置serviceId。則customName相當于服務名稱
zuul.routes.eureka-application-service.serviceId=eureka-application-service# 路由排除配置
# 配置不被zuul管理的服務列表。多個服務名稱使用逗號','分隔。
# 配置的服務將不被zuul代理。
zuul.ignored-services=eureka-application-service# 此方式相當于給所有新發現的服務默認排除zuul網關訪問方式,只有配置了路由網關的服務才可以通過zuul網關訪問
# 通配方式配置排除列表。
zuul.ignored-services=*
# 使用服務名稱匹配規則配置路由列表,相當于只對已配置的服務提供網關代理。
zuul.routes.eureka-application-service.path=/api/**# 通配方式配置排除網關代理路徑。所有符合ignored-patterns的請求路徑都不被zuul網關代理。
zuul.ignored-patterns=/**/test/**
zuul.routes.eureka-application-service.path=/api/**# 路由前綴配置
# prefix URL pattern 前綴路由匹配
# 配置請求路徑前綴,所有基于此前綴的請求都由zuul網關提供代理。
zuul.prefix=/api
# 使用服務名稱匹配方式配置請求路徑規則。
# 這里的配置將為:http://ip:port/api/appservice/**的請求提供zuul網關代理,可以將要訪問服務進行前綴分類。
# 并將請求路由到服務eureka-application-service中。
zuul.routes.eureka-application-service.path=/appservice/**
網關配置方式有多種,默認、URL、服務名稱、排除|忽略、前綴。
網關配置沒有優劣好壞,應該在不同的情況下選擇合適的配置方案。
zuul網關其底層使用ribbon來實現請求的路由,并內置Hystrix,可選擇性提供網關fallback邏輯。使用zuul的時候,并不推薦使用Feign作為application client端的開發實現。畢竟Feign技術是對ribbon的再封裝,使用Feign本身會提高通訊消耗,降低通訊效率,只在服務相互調用的時候使用Feign來簡化代碼開發就夠了。而且商業開發中,使用Ribbon+RestTemplate來開發的比例更高。
四、路由網關過濾器
Zuul中提供了過濾器定義,可以用來過濾代理請求,提供額外功能邏輯。如:權限驗證,日志記錄等。
Zuul提供的過濾器是一個父類。父類是ZuulFilter。通過父類中定義的抽象方法filterType,來決定當前的Filter種類是什么。有前置過濾、路由后過濾、后置過濾、異常過濾。
- 前置過濾:是請求進入Zuul之后,立刻執行的過濾邏輯
- 路由后過濾:是請求進入Zuul之后,并Zuul實現了請求路由后執行的過濾邏輯,路由后過濾,是在遠程服務調用之前過濾的邏輯。
- 后置過濾:遠程服務調用結束后執行的過濾邏輯。
- 異常過濾:是任意一個過濾器發生異常或遠程服務調用無結果反饋的時候執行的過濾邏輯。無結果反饋,就是遠程服務調用超時。
過濾器實現方式
繼承父類ZuulFilter。在父類中提供了4個抽象方法,分別是:filterType, filterOrder, shouldFilter, run。其功能分別是:
- filterType:方法返回字符串數據,代表當前過濾器的類型。可選值有-pre, route, post, error。pre - 前置過濾器,在請求被路由前執行,通常用于處理身份認證,日志記錄等;
- route - 在路由執行后,服務調用前被調用;
- error - 任意一個filter發生異常的時候執行或遠程服務調用沒有反饋的時候執行(超時),通常用于處理異常;
- post - 在route或error執行后被調用,一般用于收集服務信息,統計服務性能指標等,也可以對response結果做特殊處理。
- filterOrder:返回int數據,用于為同filterType的多個過濾器定制執行順序,返回值越小,執行順序越優先
- shouldFilter:返回boolean數據,代表當前filter是否生效
- run:具體的過濾執行邏輯。如pre類型的過濾器,可以通過對請求的驗證來決定是否將請求路由到服務上;如post類型的過濾器,可以對服務響應結果做加工處理(如為每個響應增加footer數據)。
過濾器的生命周期
/*** Zuul過濾器,必須繼承ZuulFilter父類。* 當前類型的對象必須交由Spring容器管理。使用@Component注解描述。* 繼承父類后,必須實現父類中定義的4個抽象方法。* shouldFilter、 run、 filterType、 filterOrder*/
@Component
public class LoggerFilter extends ZuulFilter {private static final Logger logger = LoggerFactory.getLogger(LoggerFilter.class);/*** 返回boolean類型。代表當前filter是否生效。* 默認值為false。* 返回true代表開啟filter。*/@Overridepublic boolean shouldFilter() {return true;}/*** run方法就是過濾器的具體邏輯。* return 可以返回任意的對象,當前實現忽略。(spring-cloud-zuul官方解釋)* 直接返回null即可。*/@Overridepublic Object run() throws ZuulException {// 通過zuul,獲取請求上下文RequestContext rc = RequestContext.getCurrentContext();HttpServletRequest request = rc.getRequest();logger.info("LogFilter1.....method={},url={}",request.getMethod(),request.getRequestURL().toString());// 可以記錄日志、鑒權,給維護人員記錄提供定位協助、統計性能return null;}/*** 過濾器的類型。可選值有:* pre - 前置過濾* route - 路由后過濾* error - 異常過濾* post - 遠程服務調用后過濾*/@Overridepublic String filterType() {return "pre";}/*** 同種類的過濾器的執行順序。* 按照返回值的自然升序執行。*/@Overridepublic int filterOrder() {return 0;}
}
五、Zuul網關的容錯(與Hystrix的無縫結合)
在spring cloud中,Zuul啟動器中包含了Hystrix相關依賴,在Zuul網關工程中,默認是提供了Hystrix Dashboard服務監控數據的(hystrix.stream),但是不會提供監控面板的界面展示。可以說,在spring cloud中,zuul和Hystrix是無縫結合的。
1、Zuul中的服務降級處理
在Edgware版本之前,Zuul提供了接口ZuulFallbackProvider用于實現fallback處理。從Edgware版本開始,Zuul提供了ZuulFallbackProvider的子接口FallbackProvider來提供fallback處理。
Zuul的fallback容錯處理邏輯,只針對timeout異常處理,當請求被Zuul路由后,只要服務有返回(包括異常),都不會觸發Zuul的fallback容錯邏輯。
因為對于Zuul網關來說,做請求路由分發的時候,結果由遠程服務運算的。那么遠程服務反饋了異常信息,Zuul網關不會處理異常,因為無法確定這個錯誤是否是應用真實想要反饋給客戶端的。
2、代碼示例
/*** 如果需要在Zuul網關服務中增加容錯處理fallback,需要實現接口ZuulFallbackProvider* spring-cloud框架,在Edgware版本(包括)之后,聲明接口ZuulFallbackProvider過期失效,* 提供了新的ZuulFallbackProvider的子接口 - FallbackProvider* 在老版本中提供的ZuulFallbackProvider中,定義了兩個方法。* - String getRoute()* 當前的fallback容錯處理邏輯處理的是哪一個服務。可以使用通配符‘*’代表為全部的服務提供容錯處理。* 如果只為某一個服務提供容錯,返回對應服務的spring.application.name值。* - ClientHttpResponse fallbackResponse()* 當服務發生錯誤的時候,如何容錯。* 新版本中提供的FallbackProvider提供了新的方法。* - ClientHttpResponse fallbackResponse(Throwable cause)* 如果使用新版本中定義的接口來做容錯處理,容錯處理邏輯,只運行子接口中定義的新方法。也就是有參方法。* 是為遠程服務發生異常的時候,通過異常的類型來運行不同的容錯邏輯。*/
@Component
public class TestFallBbackProvider implements FallbackProvider {/*** return - 返回fallback處理哪一個服務。返回的是服務的名稱* 推薦 - 為指定的服務定義特性化的fallback邏輯。* 推薦 - 提供一個處理所有服務的fallback邏輯。* 好處 - 服務某個服務發生超時,那么指定的fallback邏輯執行。如果有新服務上線,未提供fallback邏輯,有一個通用的。*/@Overridepublic String getRoute() {return "eureka-application-service";}/*** fallback邏輯。在早期版本中使用。* Edgware版本之后,ZuulFallbackProvider接口過期,提供了新的子接口FallbackProvider* 子接口中提供了方法ClientHttpResponse fallbackResponse(Throwable cause)。* 優先調用子接口新定義的fallback處理邏輯。*/@Overridepublic ClientHttpResponse fallbackResponse() {System.out.println("ClientHttpResponse fallbackResponse()");List<Map<String, Object>> result = new ArrayList<>();Map<String, Object> data = new HashMap<>();data.put("message", "服務正忙,請稍后重試");result.add(data);ObjectMapper mapper = new ObjectMapper();String msg = "";try {msg = mapper.writeValueAsString(result);} catch (JsonProcessingException e) {msg = "";}return this.executeFallback(HttpStatus.OK, msg, "application", "json", "utf-8");}/*** fallback邏輯。優先調用。可以根據異常類型動態決定處理方式。*/@Overridepublic ClientHttpResponse fallbackResponse(Throwable cause) {System.out.println("ClientHttpResponse fallbackResponse(Throwable cause)");if(cause instanceof NullPointerException){List<Map<String, Object>> result = new ArrayList<>();Map<String, Object> data = new HashMap<>();data.put("message", "網關超時,請稍后重試");result.add(data);ObjectMapper mapper = new ObjectMapper();String msg = "";try {msg = mapper.writeValueAsString(result);} catch (JsonProcessingException e) {msg = "";}return this.executeFallback(HttpStatus.GATEWAY_TIMEOUT, msg, "application", "json", "utf-8");}else{return this.fallbackResponse();}}/*** 具體處理過程。* @param status 容錯處理后的返回狀態,如200正常GET請求結果,201正常POST請求結果,404資源找不到錯誤等。* 使用spring提供的枚舉類型對象實現。HttpStatus* @param contentMsg 自定義的響應內容。就是反饋給客戶端的數據。* @param mediaType 響應類型,是響應的主類型, 如:application、text、media。* @param subMediaType 響應類型,是響應的子類型, 如:json、stream、html、plain、jpeg、png等。* @param charsetName 響應結果的字符集。這里只傳遞字符集名稱,如:utf-8、gbk、big5等。* @return ClientHttpResponse 就是響應的具體內容。* 相當于一個HttpServletResponse。*/private final ClientHttpResponse executeFallback(final HttpStatus status, String contentMsg, String mediaType, String subMediaType, String charsetName) {return new ClientHttpResponse() {/*** 設置響應的頭信息*/@Overridepublic HttpHeaders getHeaders() {HttpHeaders header = new HttpHeaders();MediaType mt = new MediaType(mediaType, subMediaType, Charset.forName(charsetName));header.setContentType(mt);return header;}/*** 設置響應體* zuul會將本方法返回的輸入流數據讀取,并通過HttpServletResponse的輸出流輸出到客戶端。*/@Overridepublic InputStream getBody() throws IOException {String content = contentMsg;return new ByteArrayInputStream(content.getBytes());}/*** ClientHttpResponse的fallback的狀態碼 返回String*/@Overridepublic String getStatusText() throws IOException {return this.getStatusCode().getReasonPhrase();}/*** ClientHttpResponse的fallback的狀態碼 返回HttpStatus*/@Overridepublic HttpStatus getStatusCode() throws IOException {return status;}/*** ClientHttpResponse的fallback的狀態碼 返回int*/@Overridepublic int getRawStatusCode() throws IOException {return this.getStatusCode().value();}/*** 回收資源方法* 用于回收當前fallback邏輯開啟的資源對象的。* 不要關閉getBody方法返回的那個輸入流對象。*/@Overridepublic void close() {}};}
}
六、Zuul網關的限流保護
Zuul網關組件也提供了限流保護。當請求并發達到閥值,自動觸發限流保護,返回錯誤結果。只要提供error錯誤處理機制即可。
Zuul的限流保護需要額外依賴spring-cloud-zuul-ratelimit組件。
<dependency><groupId>com.marcosbarbero.cloud</groupId><artifactId>spring-cloud-zuul-ratelimit</artifactId><version>1.3.4.RELEASE</version>
</dependency>
1、全局限流配置
使用全局限流配置,zuul會對代理的所有服務提供限流保護。
# 開啟限流保護
zuul.ratelimit.enabled=true
# 60s內請求超過3次,服務端就拋出異常,60s后可以恢復正常請求
zuul.ratelimit.default-policy.limit=3
zuul.ratelimit.default-policy.refresh-interval=60
# 針對IP進行限流,不影響其他IP
zuul.ratelimit.default-policy.type=origin
2、局部限流配置
使用局部限流配置,zuul僅針對配置的服務提供限流保護。
# 開啟限流保護
zuul.ratelimit.enabled=true
# hystrix-application-client服務60s內請求超過3次,服務拋出異常。
zuul.ratelimit.policies.hystrix-application-client.limit=3
zuul.ratelimit.policies.hystrix-application-client.refresh-interval=60
# 針對IP限流。
zuul.ratelimit.policies.hystrix-application-client.type=origin
3、限流參數簡介
七、Zuul網關性能調優:網關的兩層超時調優
使用Zuul的spring cloud微服務結構圖:
從上圖中可以看出。整體請求邏輯還是比較復雜的,在沒有zuul網關的情況下,app client請求app service的時候,也有請求超時的可能。那么當增加了zuul網關的時候,請求超時的可能就更明顯了。
當請求通過zuul網關路由到服務,并等待服務返回響應,這個過程中zuul也有超時控制。zuul的底層使用的是Hystrix+ribbon來實現請求路由。結構如下:
zuul中的Hystrix內部使用線程池隔離機制提供請求路由實現,其默認的超時時長為1000毫秒。ribbon底層默認超時時長為5000毫秒。如果Hystrix超時,直接返回超時異常。如果ribbon超時,同時Hystrix未超時,ribbon會自動進行服務集群輪詢重試,直到Hystrix超時為止。如果Hystrix超時時長小于ribbon超時時長,ribbon不會進行服務集群輪詢重試。
那么在zuul中可配置的超時時長就有兩個位置:Hystrix和ribbon。具體配置如下:
# 開啟zuul網關重試
zuul.retryable=true
# hystrix超時時間設置
# 線程池隔離,默認超時時間1000ms
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=8000# ribbon超時時間設置:建議設置比Hystrix小
# 請求連接的超時時間: 默認5000ms
ribbon.ConnectTimeout=5000
# 請求處理的超時時間: 默認5000ms
ribbon.ReadTimeout=5000
# 重試次數:MaxAutoRetries表示訪問服務集群下原節點(同路徑訪問);MaxAutoRetriesNextServer表示訪問服務集群下其余節點(換臺服務器)
ribbon.MaxAutoRetries=1
ribbon.MaxAutoRetriesNextServer=1
# 開啟重試
ribbon.OkToRetryOnAllOperations=true
Spring-cloud中的zuul網關重試機制是使用spring-retry實現的。工程必須依賴下述資源:
<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId>
</dependency>