不一樣的微服務通常有不一樣的網絡地址,而外部的客戶端可能須要調用多個服務的接口才能完成一個業務需求。好比一個電影購票的收集APP,可能回調用電影分類微服務,用戶微服務,支付微服務等。若是客戶端直接和微服務進行通訊,會存在一下問題:java
# 客戶端會屢次請求不一樣微服務,增長客戶端的複雜性web
# 存在跨域請求,在必定場景下處理相對複雜正則表達式
# 認證複雜,每個服務都須要獨立認證spring
# 難以重構,隨着項目的迭代,可能須要從新劃分微服務,若是客戶端直接和微服務通訊,那麼重構會難以實施apache
# 某些微服務可能使用了其餘協議,直接訪問有必定困難json
上述問題,均可以藉助微服務網管解決。微服務網管是介於客戶端和服務器端之間的中間層,全部的外部請求都會先通過微服務網關,架構演變成:後端
這樣客戶端只須要和網關交互,而無需直接調用特定微服務的接口,並且方便監控,易於認證,減小客戶端和各個微服務之間的交互次數跨域
Zuul是Netflix開源的微服務網關,他能夠和Eureka,Ribbon,Hystrix等組件配合使用。Zuul組件的核心是一系列的過濾器,這些過濾器能夠完成如下功能:tomcat
# 身份認證和安全: 識別每個資源的驗證要求,並拒絕那些不符的請求安全
# 審查與監控:
# 動態路由:動態將請求路由到不一樣後端集羣
# 壓力測試:逐漸增長指向集羣的流量,以瞭解性能
# 負載分配:爲每一種負載類型分配對應容量,並棄用超出限定值的請求
# 靜態響應處理:邊緣位置進行響應,避免轉發到內部集羣
# 多區域彈性:跨域AWS Region進行請求路由,旨在實現ELB(ElasticLoad Balancing)使用多樣化
Spring Cloud對Zuul進行了整合和加強。目前,Zuul使用的默認是Apache的HTTP Client,也可使用Rest Client,能夠設置ribbon.restclient.enabled=true.
# 添加依賴
Zuul的依賴確定是要加的,如何和Eureka配合使用, Zuul須要註冊到Eureka上,可是Zuul的依賴不包含Eureka Discovery客戶端,因此還須要添加Eureka的客戶端依賴
<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>
# 啓動類加上註解@EnableZuulProxy
它默認加上了@EnableCircuitBreaker和@EnableDiscoveryClient
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(ZuulApplication.class, args);
}
}
# 配置application.yml
server:
port: 8040
spring:
application:
name: microservice-gateway-zuul
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone:http://nicky:123abcABC@localhost:8761/eureka
instance:
ip-address: true
zuul:
ignoredServices: '*'
routes:
microservice-provider-user: /ecom/**
zuul:
ignoredServices: '*' // 忽略全部請求
routes:
服務名: /ecom/** //容許將服務名映射到ecom
# 啓動Eureka,Zuul和 其餘應用
訪問http://localhost:8040/ecom/user/1
或者
http://localhost:8040/microservice-provider-user/user/1
均可以
zuul:
ignoredServices: '*' // 忽略全部請求
routes:
服務名: /ecom/** //容許將服務名映射到ecom
爲了更加細粒度控制路由路徑:
// 表示只要HTTP請求是 /ecom開始的,就會forward到服務id爲users_service的服務上面
zuul:
routes:
users:
path:/ecom/** // 路由路徑
serviceId: users_service // 服務id
zuul:
routes:
users: // 路由名稱,隨意,惟一便可
path: /ecom/** // 路由路徑
url:http://localhost:9000
上述簡單的url路由配置,不會做爲Hystrix Command執行,也不會進行ribbon的負載均衡。爲了同時指定path 和 url,可是不破壞Zuul的Hystrix和Ribbon特性:
zuul:
routes:
users:
path:/ecom/**
serviceId: microservice-provider-user
ribbon:
eureka:
enabled:false // ribbon禁掉Eureka
microservice-provider-user:
ribbon:
listOfServers: localhost:9000,localhost:9001
藉助PatternServiceRoute Mapper實現從微服務到映射路由的正則配置,例如:
@Bean
publicPatternServiceRouteMapper serviceRouteMapper(){
// servicePattern: 指的是微服務的pattern
// routePattern: 指的是路由的pattern
// 當你訪問/microservice-provider-user/v1 其實就是
// localhost:8040/v1/microservice-provider-user/user/1
return newPatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)","${version}/${name}"
);
}
zuul.prefix: 咱們能夠指定一個全局的前綴
strip-prefix: 是否將這個代理前綴去掉
zuul:
prefix: /ecom
strip-prefix: false
routes:
microservice-provider-user: /provider/**
好比你訪問http://localhost:8040/ecom/microservice-provider-user/user/1,其實真實訪問路徑是/ecom/user/1
zuul:
prefix: /ecom
strip-prefix: true
routes:
microservice-provider-user: /provider/**
好比你訪問http://localhost:8040/ecom/microservice-provider-user/user/1,其實真實訪問路徑是/user/1,由於咱們能夠將前綴去掉
若是strip-prefix只是放在路由下面,那麼就是局部的,不會影響全局
zuul:
prefix: /ecom
routes:
abc:
path: /provider/**
service-id: microservice-provider-user
strip-prefix: true
好比你訪問http://localhost:8040/ecom/microservice-provider-user/user/1
其實真實訪問路徑是/user/1,由於咱們能夠將前綴去掉
zuul:
prefix: /ecom
routes:
abc:
path: /provider/**
service-id: microservice-provider-user
strip-prefix: false
好比你訪問http://localhost:8040/ecom/provider/user/1
其實真實訪問路徑是/provider/user/1,由於咱們能夠將前綴去掉
zuul:
ignoredPatterns: /**/admin/**
routes:
users: /myusers/**
過濾掉path包含admin的請求
在同一個系統中微服務之間共享Header,可是某些時候儘可能防止讓一些敏感的Header外泄。所以不少場景下,須要經過爲路由指定一系列敏感的Header列表。例如:
zuul:
routes:
abc:
path: /provider/**
service-id: microservice-provider-user
sensitiveHeaders:Cookie,Set-Cookie,Authorization
url: https://downstream
被忽略的Header不會被傳播到其餘的微服務去。其實敏感的Heade最終也是走的這兒
zuul:
ignored-headers: Header1,Header2
默認狀況下,ignored-headers是空的
在遷移現有應用程序或API時,一種常見的模式是「扼殺」舊的端點,用不一樣的實現慢慢替換它們。Zuul代理是一個有用的工具,由於您可使用它來處理來自舊端點客戶端的全部流量,可是將一些請求重定向到新端點。
zuul:
routes:
first:
path: /first/**
url: http://first.example.com
second:
path: /second/**
url: forward:/second
third:
path: /third/**
url: forward:/3rd
legacy:
path: /**
url: http://legacy.example.com // 遺留的或者剩餘的都走這個路徑
# 添加對應的依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# 建立上傳文件的controller
@Controller
public classFileUploadController {
@RequestMapping(value="/upload",method=RequestMethod.POST)
public @ResponseBody StringhandleFileUpload(
@RequestParam(value="file",required=true)MultipartFile file){
try {
byte[] in = file.getBytes();
File out = new File(file.getOriginalFilename());
FileCopyUtils.copy(in, out);
return out.getAbsolutePath();
} catch (IOException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
return null;
}
}
# 編寫配置文件application.yml
# 使用工具CURL測試
curl -F"file=@微服務簡介.docx"localhost:8086/upload
# 不添加/zuul,上傳小文件沒有問題
curl -F"file=@Hystrix.docx" http://localhost:8050/microservice-consumer/upload
# 不添加/zuul前綴上傳大文件
curl -F"file=@ATGPUB.DMP" http://localhost:8050/microservice-consumer/upload
若是不加/zuul前綴就會報錯
{"timestamp":1507123311527,"status":500,"error":"InternalServerError","exception":"org.springframework.web.multipart.MultipartException","message":"Couldnot parse multipart servlet request; nested exception isjava.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException:the request was rejected because its size (132780234) exceeds the configuredmaximum(10485760)","path":"/microservice-consumer/upload"}
# 添加/zuul前綴,上傳大文件
curl -F"file=@ATGPUB.DMP"http://localhost:8050/zuul/microservice-consumer/upload
{"timestamp":1507123418018,"status":500,"error":"InternalServerError","exception":"com.netflix.zuul.exception.ZuulException","message":"TIMEOUT"}
此時已經不是文件大小的錯誤了,咱們能夠將上傳大文件的超時時間設置長一些
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds:60000
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
在Spring Cloud, Zuul默認已經整合了Hystrix, 並且若是啓動了Dashborad,也能夠知道Zuul對Hystrix監控的粒度是微服務,而不是某一個API; 同時也說明全部通過Zuul的請求都會被Hystrix保護起來。
8.1 爲Zuul添加回退
想要爲Zuul添加回退,須要實現ZuulFallbackProvider接口。在實現類中,指定爲哪個微服務提供回退,而且提供一個ClientHttpResponse做爲回退響應。
編寫Zuul回退類:
@Component
public classUserFallbackProvider implementsZuulFallbackProvider{
@Override
public String getRoute(){
// 指定爲哪個微服務提供回退
return "microservice-provider-user";
}
@Override
publicClientHttpResponse fallbackResponse() {
return newClientHttpResponse() {
@Override
public HttpHeadersgetHeaders() {
// 設置header
HttpHeaders headers = new HttpHeaders();
MediaType mediaType = new MediaType("application",
"json",Charset.forName("UTF-8"));
headers.setContentType(mediaType);
return headers;
}
@Override
public InputStreamgetBody() throws IOException {
// 響應體
return newByteArrayInputStream(
"Usermicro-service is unavailable, please try it again later!".getBytes());
}
@Override
public StringgetStatusText() throws IOException {
// 返回狀態文本
return this.getStatusCode().getReasonPhrase();
}
@Override
public HttpStatusgetStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public intgetRawStatusCode() throws IOException {
// 返回數字類型的狀態碼
return this.getStatusCode().value();
}
@Override
public void close() {
}
};
}
}
從新啓動Eureka Server,microservice-gateway-zuul-fallback以及microservice-provider-user
咱們正常經過zuul訪問microservice-provider-user微服務
http://localhost:8040/microservice-provider-user/user/1 沒有問題
而後咱們關掉microservice-provider-user微服務
在訪問http://localhost:8040/microservice-provider-user/user/1,則會出現User micro-service is unavailable, please try it again later!
而不是之前不友好的那個頁面了
九 Zuul的高可用
Zuul的高可用很是關鍵,由於外部請求到後端的微服務的流量都會通過Zuul。故而在生產環境中通常都須要部署高可用的Zuul以免單點故障
9.1 Zuul客戶端註冊到了Eureka Server上
此種狀況,Zuul的高可用實現比較簡單,只需將多個Zuul節點註冊到Eureka Server,就能夠實現Zuul高可用。此時,Zuul與其餘的微服務高可用沒啥區別。Zuul客戶端會自動從Eureka Server中查詢Zuul Server的列表,並使用Ribbon負載均衡的請求Zuul集羣
9.2 Zuul客戶端未註冊到Eureka Server上
若是Zuul客戶端未註冊到Eureka上,由於微服務可能被其餘微服務調用,也可能直接被終端調用,好比手機App。此種狀況下,咱們須要藉助額外的負載均衡器來實現Zuul的高可用,好比Nginx,好比HAProxy或者F5等
咱們可使用Sidecar整合非JVM微服務,好比C++、Python、PHP等語言寫的。其餘非JVM微服務可操做Eureka的REST端點,從而實現註冊與發現。事實上,也可使用sidecar更加方便整合非JVM微服務
# 首先添加依賴
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-sidecar</artifactId>
</dependency>
# 添加啓動類,在啓動類上加上@EnableSidecar註解,聲明這是一個Sidecar
這個註解整合了三個註解即:
@EnableCircuitBreaker
@EnableDiscoveryClient
@EnableZuulProxy
@SpringBootApplication
@EnableSidecar
public classZuulSidecarApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(ZuulSidecarApplication.class, args);
}
}
# 編寫application.yml
Sidecar.port指的就是其餘語言微服務的端口
Sidecar與其餘語言的微服務分離部署
上面咱們是將其餘語言微服務和sidecar放在同一個機器上,現實中,經常會將Sidecar與IVM微服務分離部署,部署在不一樣的主機上面或者容器中,這時候應該如何配置呢?
方法一:
eureka:
instance:
hostname: # 非JVM微服務所在的hostname
方法二:
sidecar:
hostname: # 非JVM微服務所在的hostname
ip-address: # 非JVM微服務所在的IP 地址
注意:若是這種微服務太多,並且還涉及到集羣的話使用sidecar咱們應該權衡一下。由於一個sidecar只能對應一個其餘語言寫的微服務,若是不少,那表示就多個sidecar了。
過濾器是Zuul的核心組件,Zuul大部分功能都是經過過濾器來實現的。
Zuul中定義了四種標準的過濾器類型,這些過濾器類型對應於典型的生命週期。
PRE: 這種過濾器在請求被路由以前調用。可利用其實現身份驗證等
ROUTING: 這種過濾器將請求路由到微服務,用於構建發送給微服務的請求,並使用Apache Http Client或者Netflix Ribbon請求微服務
POST: 這種過濾器在路由到微服務之後執行,好比爲響應添加標準的HTTP Header,收集統計信息和指標,將響應從微服務發送到客戶端等
ERROR: 在其餘階段發生錯誤時執行該過濾器
除了默認的過濾器類型,Zuul還容許建立自定義的過濾器類型。
咱們只須要繼承抽象類ZuulFilter過濾器便可,讓該過濾器打印請求日誌
public classPreRequestLogFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(
PreRequestLogFilter.class);
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest reqeust = context.getRequest();
PreRequestLogFilter.logger.info(
String.format("send %srequest to %s",
reqeust.getMethod(),
reqeust.getRequestURL().toString()));
return null;
}
@Override
public boolean shouldFilter() {
// 判斷是否須要過濾
return true;
}
@Override
public int filterOrder() {
// 過濾器的優先級,越大越靠後執行
return 1;
}
@Override
public StringfilterType() {
// 過濾器類型
return "pre";
}
}
修改啓動類,爲啓動類添加:
@SpringBootApplication
@EnableZuulProxy
public classZuulFilterApplication {
@Bean
publicPreRequestLogFilter preRequestLogFilter(){
return newPreRequestLogFilter();
}
public static void main(String[] args) throws Exception {
SpringApplication.run(ZuulFilterApplication.class, args);
}
}
Spring Cloud默認爲Zuul編寫並啓用了一些過濾器,例如DebugFilter,FromBodyWrapperFilter,PreDecorationFilter等,這些過濾器都存放在spring-cloud-netflix-core這個jar裏的
在某些場景下,但願禁掉一些過濾器,該怎辦呢?
只需設置zuul.<SimpleClassName>.<filterType>.disable=true便可,好比
zuul.PreRequestLogFilter.pre.disable=true
許多場景下,一個外部請求,可能須要查詢Zuul後端多個微服務。好比說一個電影售票系統,在購票訂單頁上,須要查詢電影微服務,還須要查詢用戶微服務得到當前用戶信息。若是讓系統直接請求各個微服務,就算Zuul轉發,網絡開銷,流量耗費,時長都不是很好的。這時候咱們就可使用Zuul聚合微服務請求,即應用系統只發送一個請求給Zuul,由Zuul請求用戶微服務和電影微服務,並把數據返給應用系統。