一 微服務(wù)網(wǎng)關(guān)背景及簡(jiǎn)介不同的微服務(wù)一般有不同的網(wǎng)絡(luò)地址,,而外部的客戶端可能需要調(diào)用多個(gè)服務(wù)的接口才能完成一個(gè)業(yè)務(wù)需求。比如一個(gè)電影購票的收集APP,可能回調(diào)用電影分類微服務(wù),,用戶微服務(wù),,支付微服務(wù)等。如果客戶端直接和微服務(wù)進(jìn)行通信,,會(huì)存在一下問題: # 客戶端會(huì)多次請(qǐng)求不同微服務(wù),,增加客戶端的復(fù)雜性 # 存在跨域請(qǐng)求,在一定場(chǎng)景下處理相對(duì)復(fù)雜 # 認(rèn)證復(fù)雜,,每一個(gè)服務(wù)都需要獨(dú)立認(rèn)證 # 難以重構(gòu),,隨著項(xiàng)目的迭代,可能需要重新劃分微服務(wù),,如果客戶端直接和微服務(wù)通信,,那么重構(gòu)會(huì)難以實(shí)施 # 某些微服務(wù)可能使用了其他協(xié)議,直接訪問有一定困難 上述問題,,都可以借助微服務(wù)網(wǎng)管解決,。微服務(wù)網(wǎng)管是介于客戶端和服務(wù)器端之間的中間層,所有的外部請(qǐng)求都會(huì)先經(jīng)過微服務(wù)網(wǎng)關(guān),,架構(gòu)演變成: 這樣客戶端只需要和網(wǎng)關(guān)交互,,而無需直接調(diào)用特定微服務(wù)的接口,而且方便監(jiān)控,,易于認(rèn)證,,減少客戶端和各個(gè)微服務(wù)之間的交互次數(shù)
二 Zuul 簡(jiǎn)介Zuul是Netflix開源的微服務(wù)網(wǎng)關(guān),他可以和Eureka,Ribbon,Hystrix等組件配合使用,。Zuul組件的核心是一系列的過濾器,,這些過濾器可以完成以下功能: # 身份認(rèn)證和安全: 識(shí)別每一個(gè)資源的驗(yàn)證要求,并拒絕那些不符的請(qǐng)求 # 審查與監(jiān)控: # 動(dòng)態(tài)路由:動(dòng)態(tài)將請(qǐng)求路由到不同后端集群 # 壓力測(cè)試:逐漸增加指向集群的流量,,以了解性能 # 負(fù)載分配:為每一種負(fù)載類型分配對(duì)應(yīng)容量,,并棄用超出限定值的請(qǐng)求 # 靜態(tài)響應(yīng)處理:邊緣位置進(jìn)行響應(yīng),,避免轉(zhuǎn)發(fā)到內(nèi)部集群 # 多區(qū)域彈性:跨域AWS Region進(jìn)行請(qǐng)求路由,旨在實(shí)現(xiàn)ELB(ElasticLoad Balancing)使用多樣化 Spring Cloud對(duì)Zuul進(jìn)行了整合和增強(qiáng),。目前,,Zuul使用的默認(rèn)是Apache的HTTP Client,也可以使用Rest Client,,可以設(shè)置ribbon.restclient.enabled=true.
三 編寫Zuul微服務(wù)網(wǎng)關(guān)# 添加依賴 Zuul的依賴肯定是要加的,,如何和Eureka配合使用, Zuul需要注冊(cè)到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>
# 啟動(dòng)類加上注解@EnableZuulProxy 它默認(rèn)加上了@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: '*' // 忽略所有請(qǐng)求 routes: 服務(wù)名: /ecom/** //允許將服務(wù)名映射到ecom
# 啟動(dòng)Eureka,Zuul和 其他應(yīng)用 訪問http://localhost:8040/ecom/user/1 或者 http://localhost:8040/microservice-provider-user/user/1 都可以
四 微服務(wù)網(wǎng)關(guān)相關(guān)的配置4.1 路由路徑的配置zuul: ignoredServices: '*' // 忽略所有請(qǐng)求 routes: 服務(wù)名: /ecom/** //允許將服務(wù)名映射到ecom
為了更加細(xì)粒度控制路由路徑: // 表示只要HTTP請(qǐng)求是 /ecom開始的,,就會(huì)forward到服務(wù)id為users_service的服務(wù)上面 zuul: routes: users: path:/ecom/** // 路由路徑 serviceId: users_service // 服務(wù)id
zuul: routes: users: // 路由名稱,,隨意,唯一即可 path: /ecom/** // 路由路徑 url:http://localhost:9000
4.2 不破壞Zuul的Hystrix和Ribbon特性上述簡(jiǎn)單的url路由配置,,不會(huì)作為Hystrix Command執(zhí)行,,也不會(huì)進(jìn)行ribbon的負(fù)載均衡。為了同時(shí)指定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
4.3 使用正則表達(dá)式指定Zuul的路由匹配規(guī)則借助PatternServiceRoute Mapper實(shí)現(xiàn)從微服務(wù)到映射路由的正則配置,,例如: @Bean publicPatternServiceRouteMapper serviceRouteMapper(){ // servicePattern: 指的是微服務(wù)的pattern // routePattern: 指的是路由的pattern // 當(dāng)你訪問/microservice-provider-user/v1 其實(shí)就是 // localhost:8040/v1/microservice-provider-user/user/1 return newPatternServiceRouteMapper( "(?<name>^.+)-(?<version>v.+$)","${version}/${name}" ); } 4.4 路由的前綴zuul.prefix: 我們可以指定一個(gè)全局的前綴 strip-prefix: 是否將這個(gè)代理前綴去掉 zuul: prefix: /ecom strip-prefix: false routes: microservice-provider-user: /provider/**
比如你訪問http://localhost:8040/ecom/microservice-provider-user/user/1,其實(shí)真實(shí)訪問路徑是/ecom/user/1
zuul: prefix: /ecom strip-prefix: true routes: microservice-provider-user: /provider/** 比如你訪問http://localhost:8040/ecom/microservice-provider-user/user/1,,其實(shí)真實(shí)訪問路徑是/user/1,因?yàn)槲覀兛梢詫⑶熬Y去掉
如果strip-prefix只是放在路由下面,,那么就是局部的,不會(huì)影響全局 zuul: prefix: /ecom routes: abc: path: /provider/** service-id: microservice-provider-user strip-prefix: true
比如你訪問http://localhost:8040/ecom/microservice-provider-user/user/1 其實(shí)真實(shí)訪問路徑是/user/1,因?yàn)槲覀兛梢詫⑶熬Y去掉
zuul: prefix: /ecom routes: abc: path: /provider/** service-id: microservice-provider-user strip-prefix: false 比如你訪問http://localhost:8040/ecom/provider/user/1 其實(shí)真實(shí)訪問路徑是/provider/user/1,因?yàn)槲覀兛梢詫⑶熬Y去掉
4.5 忽略某些路徑zuul: ignoredPatterns: /**/admin/** routes: users: /myusers/** 過濾掉path包含admin的請(qǐng)求
五 Zuul的安全和Header5.1 敏感的Header設(shè)置在同一個(gè)系統(tǒng)中微服務(wù)之間共享Header,但是某些時(shí)候盡量防止讓一些敏感的Header外泄,。因此很多場(chǎng)景下,,需要通過為路由指定一系列敏感的Header列表。例如: zuul: routes: abc: path: /provider/** service-id: microservice-provider-user sensitiveHeaders:Cookie,Set-Cookie,Authorization url: https://downstream 5.2 忽略Header被忽略的Header不會(huì)被傳播到其他的微服務(wù)去,。其實(shí)敏感的Heade最終也是走的這兒 zuul: ignored-headers: Header1,Header2 默認(rèn)情況下,,ignored-headers是空的
六strangle模式在遷移現(xiàn)有應(yīng)用程序或API時(shí),一種常見的模式是“扼殺”舊的端點(diǎn),,用不同的實(shí)現(xiàn)慢慢替換它們,。Zuul代理是一個(gè)有用的工具,因?yàn)槟梢允褂盟鼇硖幚韥碜耘f端點(diǎn)客戶端的所有流量,,但是將一些請(qǐng)求重定向到新端點(diǎn),。 zuul: routes: first: path: /first/** url: http://first. second: path: /second/** url: forward:/second third: path: /third/** url: forward:/3rd legacy: path: /** url: http://legacy. // 遺留的或者剩余的都走這個(gè)路徑
七 使用Zuul上傳文件7.1 如果我們不使用Zuul上傳文件,即通過web框架,,也可以實(shí)現(xiàn)文件上傳,即原始的文件上傳功能:# 添加對(duì)應(yīng)的依賴 <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>
# 創(chuàng)建上傳文件的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測(cè)試 curl -F"file=@微服務(wù)簡(jiǎn)介.docx"localhost:8086/upload
7.2 通過Zuul上傳文件# 不添加/zuul,,上傳小文件沒有問題 curl -F"[email protected]" http://localhost:8050/microservice-consumer/upload
# 不添加/zuul前綴上傳大文件 curl -F"[email protected]" http://localhost:8050/microservice-consumer/upload 如果不加/zuul前綴就會(huì)報(bào)錯(cuò) {"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"[email protected]"http://localhost:8050/zuul/microservice-consumer/upload
{"timestamp":1507123418018,"status":500,"error":"InternalServerError","exception":"com.netflix.zuul.exception.ZuulException","message":"TIMEOUT"} 此時(shí)已經(jīng)不是文件大小的錯(cuò)誤了,我們可以將上傳大文件的超時(shí)時(shí)間設(shè)置長一些 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds:60000 ribbon: ConnectTimeout: 3000 ReadTimeout: 60000
八 Zuul的容錯(cuò)與回退在Spring Cloud, Zuul默認(rèn)已經(jīng)整合了Hystrix, 而且如果啟動(dòng)了Dashborad,也可以知道Zuul對(duì)Hystrix監(jiān)控的粒度是微服務(wù),,而不是某一個(gè)API; 同時(shí)也說明所有經(jīng)過Zuul的請(qǐng)求都會(huì)被Hystrix保護(hù)起來,。 8.1 為Zuul添加回退 想要為Zuul添加回退,需要實(shí)現(xiàn)ZuulFallbackProvider接口,。在實(shí)現(xiàn)類中,,指定為哪一個(gè)微服務(wù)提供回退,并且提供一個(gè)ClientHttpResponse作為回退響應(yīng),。
編寫Zuul回退類: @Component public classUserFallbackProvider implementsZuulFallbackProvider{
@Override public String getRoute(){ // 指定為哪一個(gè)微服務(wù)提供回退 return "microservice-provider-user"; }
@Override publicClientHttpResponse fallbackResponse() { return newClientHttpResponse() { @Override public HttpHeadersgetHeaders() { // 設(shè)置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 { // 響應(yīng)體 return newByteArrayInputStream( "Usermicro-service is unavailable, please try it again later!".getBytes()); }
@Override public StringgetStatusText() throws IOException { // 返回狀態(tài)文本 return this.getStatusCode().getReasonPhrase(); }
@Override public HttpStatusgetStatusCode() throws IOException {
return HttpStatus.OK; }
@Override public intgetRawStatusCode() throws IOException { // 返回?cái)?shù)字類型的狀態(tài)碼 return this.getStatusCode().value(); }
@Override public void close() {
} }; } }
重新啟動(dòng)Eureka Server,microservice-gateway-zuul-fallback以及microservice-provider-user 我們正常通過zuul訪問microservice-provider-user微服務(wù) http://localhost:8040/microservice-provider-user/user/1 沒有問題 然后我們關(guān)掉microservice-provider-user微服務(wù) 在訪問http://localhost:8040/microservice-provider-user/user/1,,則會(huì)出現(xiàn)User micro-service is unavailable, please try it again later! 而不是以前不友好的那個(gè)頁面了
九 Zuul的高可用 9.1 Zuul客戶端注冊(cè)到了Eureka Server上 此種情況,,Zuul的高可用實(shí)現(xiàn)比較簡(jiǎn)單,,只需將多個(gè)Zuul節(jié)點(diǎn)注冊(cè)到Eureka Server,就可以實(shí)現(xiàn)Zuul高可用。此時(shí),,Zuul與其他的微服務(wù)高可用沒啥區(qū)別,。Zuul客戶端會(huì)自動(dòng)從Eureka Server中查詢Zuul Server的列表,并使用Ribbon負(fù)載均衡的請(qǐng)求Zuul集群 9.2 Zuul客戶端未注冊(cè)到Eureka Server上 如果Zuul客戶端未注冊(cè)到Eureka上,,因?yàn)槲⒎?wù)可能被其他微服務(wù)調(diào)用,,也可能直接被終端調(diào)用,比如手機(jī)App,。此種情況下,,我們需要借助額外的負(fù)載均衡器來實(shí)現(xiàn)Zuul的高可用,比如Nginx,,比如HAProxy或者F5等 九 Sidecar我們可以使用Sidecar整合非JVM微服務(wù),,比如C++、Python,、PHP等語言寫的,。其他非JVM微服務(wù)可操作Eureka的REST端點(diǎn),從而實(shí)現(xiàn)注冊(cè)與發(fā)現(xiàn),。事實(shí)上,,也可以使用sidecar更加方便整合非JVM微服務(wù)
<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>
# 添加啟動(dòng)類,在啟動(dòng)類上加上@EnableSidecar注解,,聲明這是一個(gè)Sidecar 這個(gè)注解整合了三個(gè)注解即: @EnableCircuitBreaker @EnableDiscoveryClient @EnableZuulProxy @SpringBootApplication @EnableSidecar public classZuulSidecarApplication { public static void main(String[] args) throws Exception { SpringApplication.run(ZuulSidecarApplication.class, args); } } # 編寫application.yml Sidecar.port指的就是其他語言微服務(wù)的端口 Sidecar與其他語言的微服務(wù)分離部署 上面我們是將其他語言微服務(wù)和sidecar放在同一個(gè)機(jī)器上,,現(xiàn)實(shí)中,常常會(huì)將Sidecar與IVM微服務(wù)分離部署,,部署在不同的主機(jī)上面或者容器中,,這時(shí)候應(yīng)該如何配置呢,? 方法一: eureka: instance: hostname: # 非JVM微服務(wù)所在的hostname 方法二: sidecar: hostname: # 非JVM微服務(wù)所在的hostname ip-address: # 非JVM微服務(wù)所在的IP 地址 注意:如果這種微服務(wù)太多,而且還涉及到集群的話使用sidecar我們應(yīng)該權(quán)衡一下,。因?yàn)橐粋€(gè)sidecar只能對(duì)應(yīng)一個(gè)其他語言寫的微服務(wù),,如果很多,那表示就多個(gè)sidecar了,。
十 Zuul的過濾器過濾器是Zuul的核心組件,,Zuul大部分功能都是通過過濾器來實(shí)現(xiàn)的。 10.1 過濾器類型和請(qǐng)求生命周期Zuul中定義了四種標(biāo)準(zhǔn)的過濾器類型,,這些過濾器類型對(duì)應(yīng)于典型的生命周期,。 PRE: 這種過濾器在請(qǐng)求被路由之前調(diào)用??衫闷鋵?shí)現(xiàn)身份驗(yàn)證等 ROUTING: 這種過濾器將請(qǐng)求路由到微服務(wù),,用于構(gòu)建發(fā)送給微服務(wù)的請(qǐng)求,并使用Apache Http Client或者Netflix Ribbon請(qǐng)求微服務(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)建自定義的過濾器類型,。 10.2 編寫Zuul過濾器我們只需要繼承抽象類ZuulFilter過濾器即可,讓該過濾器打印請(qǐng)求日志 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() { // 過濾器的優(yōu)先級(jí),,越大越靠后執(zhí)行 return 1; }
@Override public StringfilterType() { // 過濾器類型 return "pre"; } }
修改啟動(dòng)類,,為啟動(dòng)類添加: @SpringBootApplication @EnableZuulProxy public classZuulFilterApplication { @Bean publicPreRequestLogFilter preRequestLogFilter(){ return newPreRequestLogFilter(); }
public static void main(String[] args) throws Exception { SpringApplication.run(ZuulFilterApplication.class, args); } } 10.3 禁用Zuul過濾器Spring Cloud默認(rèn)為Zuul編寫并啟用了一些過濾器,例如DebugFilter,FromBodyWrapperFilter,,PreDecorationFilter等,,這些過濾器都存放在spring-cloud-netflix-core這個(gè)jar里的 在某些場(chǎng)景下,希望禁掉一些過濾器,,該怎辦呢,? 只需設(shè)置zuul.<SimpleClassName>.<filterType>.disable=true即可,比如 zuul.PreRequestLogFilter.pre.disable=true
十一 Zuul聚合微服務(wù)許多場(chǎng)景下,,一個(gè)外部請(qǐng)求,,可能需要查詢Zuul后端多個(gè)微服務(wù)。比如說一個(gè)電影售票系統(tǒng),,在購票訂單頁上,,需要查詢電影微服務(wù),還需要查詢用戶微服務(wù)獲得當(dāng)前用戶信息,。如果讓系統(tǒng)直接請(qǐng)求各個(gè)微服務(wù),,就算Zuul轉(zhuǎn)發(fā),網(wǎng)絡(luò)開銷,,流量耗費(fèi),,時(shí)長都不是很好的,。這時(shí)候我們就可以使用Zuul聚合微服務(wù)請(qǐng)求,即應(yīng)用系統(tǒng)只發(fā)送一個(gè)請(qǐng)求給Zuul,由Zuul請(qǐng)求用戶微服務(wù)和電影微服務(wù),,并把數(shù)據(jù)返給應(yīng)用系統(tǒng)。 |
|