摘要:但是如果將負載均衡器置于所有服務(wù)前便不是一個好主意,會造成瓶頸。服務(wù)超時使用的和庫來進行請求。支持以下四種過濾器前置過濾器在將請求發(fā)送到目的地之前被調(diào)用。通常用于記錄從目標(biāo)服務(wù)返回的響應(yīng)處理錯誤或?qū)徍嗣舾行畔ⅰ?/p>
springcloud 總集:https://www.tapme.top/blog/detail/2019-02-28-11-33
本篇中 Zuul 版本為 1.x,目前最新的是 2.x,二者在過濾器的使用上有較大區(qū)別
超長警告
項目代碼見文章結(jié)尾
一、背景??微服務(wù)架構(gòu)將一個應(yīng)用拆分為很多個微小應(yīng)用,這樣會導(dǎo)致之前不是問題的問題出現(xiàn),比如:
安全問題如何實現(xiàn)?
日志記錄如何實現(xiàn)?
用戶跟蹤如何實現(xiàn)?
上面的問題在傳統(tǒng)的單機應(yīng)用很容易解決,只需要當(dāng)作一個功能實現(xiàn)即可。但是在微服務(wù)中就行不通了,讓每個服務(wù)都實現(xiàn)一份上述功能,那是相當(dāng)不現(xiàn)實的,費時,費力還容易出問題。
??為了解決這個問題,需要將這些橫切關(guān)注點(分布式系統(tǒng)級別的橫切關(guān)注點和 spring 中的基本一個意思)抽象成一個獨立的且作為應(yīng)用程序中所有微服務(wù)調(diào)用的過濾器和路由器的服務(wù)。這樣的服務(wù)被稱為——服務(wù)網(wǎng)管(service gateway),服務(wù)客戶端不再直接調(diào)用服務(wù)。取而代之的是,服務(wù)網(wǎng)關(guān)作為單個策略執(zhí)行點(Policy Enforcement Point,PEP) , 所有調(diào)用都通過服務(wù)網(wǎng)管進行路由,然后送到目的地。
二、服務(wù)網(wǎng)關(guān) 1、什么是服務(wù)網(wǎng)關(guān)??之前的幾節(jié)中我們是通過 http 請求直接調(diào)用各個服務(wù),通常在實際系統(tǒng)中不會直接調(diào)用。而是通過服務(wù)網(wǎng)關(guān)來進行服務(wù)調(diào)用。服務(wù)網(wǎng)關(guān)充當(dāng)了服務(wù)客戶端和被調(diào)用服務(wù)間的中介。服務(wù)客戶端僅與服務(wù)網(wǎng)關(guān)管理的單個 url 進行對話。下圖說了服務(wù)網(wǎng)關(guān)在一個系統(tǒng)中的作用:
服務(wù)網(wǎng)關(guān)位于服務(wù)客戶端和相應(yīng)的服務(wù)實例之間。所有的服務(wù)調(diào)用(內(nèi)部和外部)都應(yīng)流經(jīng)服務(wù)網(wǎng)關(guān)。
2、功能??由于服務(wù)網(wǎng)關(guān)代理了所有的服務(wù)調(diào)用,因此它還能充當(dāng)服務(wù)調(diào)用的中央策略執(zhí)行點(PEP),通俗的說就能能夠在此實現(xiàn)橫切關(guān)注點,不用在各個微服務(wù)中實現(xiàn)。主要有以下幾個:
靜態(tài)路由——服務(wù)網(wǎng)關(guān)將所有的服務(wù)調(diào)用放置在單個 URL 和 API 路由后,每個服務(wù)對應(yīng)一個固定的服務(wù)端點,方便開發(fā)人員的服務(wù)調(diào)用。
動態(tài)路由——服務(wù)網(wǎng)關(guān)可以檢測傳入的請求,根據(jù)請求數(shù)據(jù)和請求者執(zhí)行職能路由。比如將一部分的調(diào)用路由到特定的服務(wù)實例上,比如測試版本。
驗證和授權(quán)——所有服務(wù)調(diào)用都經(jīng)過服務(wù)網(wǎng)關(guān),顯然可以在此進行權(quán)限驗證,確保系統(tǒng)安全。
日志記錄——當(dāng)服務(wù)調(diào)用經(jīng)過服務(wù)網(wǎng)關(guān)時,可以使用服務(wù)網(wǎng)關(guān)來收集數(shù)據(jù)和日志信息(比如服務(wù)調(diào)用次數(shù),服務(wù)響應(yīng)時間等)。還能確保在用戶請求上提供關(guān)鍵信息以確保日志統(tǒng)計(比如給每個用戶請求加一個 url 參數(shù),每個服務(wù)中可通過該參數(shù)將關(guān)鍵信息對應(yīng)到某個用戶請求)。
看到這兒可能會有這樣的疑問:所有調(diào)用都通過服務(wù)網(wǎng)關(guān),難道服務(wù)網(wǎng)關(guān)不是單點故障和潛在瓶頸嗎?
1. 在多帶帶的服務(wù)器前,負載均衡器是很有用的。將負載均衡器放到多個服務(wù)網(wǎng)關(guān)前面是比較好的設(shè)計,確保服務(wù)網(wǎng)關(guān)可以實現(xiàn)伸縮。但是如果將負載均衡器置于所有服務(wù)前便不是一個好主意,會造成瓶頸。
2. 服務(wù)網(wǎng)關(guān)的代碼應(yīng)該是無狀態(tài)的。有狀態(tài)的應(yīng)用實現(xiàn)伸縮性較為麻煩
3. 服務(wù)網(wǎng)關(guān)的代碼應(yīng)該輕量的。服務(wù)網(wǎng)關(guān)是服務(wù)調(diào)用的“阻塞點”,不易在服務(wù)網(wǎng)關(guān)處耽誤較長的時間,比如進行同步數(shù)據(jù)庫操作
三、實戰(zhàn)??使用 Netflix Zuul 來構(gòu)建服務(wù)網(wǎng)關(guān),配合之前的代碼,讓服務(wù)網(wǎng)關(guān)來管理服務(wù)調(diào)用。
在生產(chǎn)環(huán)境中不建議使用 zuul,該組件性能較弱,且已經(jīng)停止更新
1、創(chuàng)建 zuulsvr 項目??詳細過程不贅述,和之前一樣(注意 spring cloud 版本要和之前一致),主要 pom 依賴如下:
2、配置 zuulorg.springframework.cloud spring-cloud-starter-zuul org.springframework.cloud spring-cloud-starter-eureka
??首先在啟動加入注解開啟 zuul 并注冊到 eureka 中
??然后編寫配置文件:
spring: application: name: zuulservice #服務(wù)發(fā)現(xiàn)配置 eureka: instance: prefer-ip-address: true client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:8761/eureka/ server: port: 5555
這樣便以默認配置啟動了 zuul 服務(wù)網(wǎng)關(guān)。
3、路由配置??Zuul 核心就是一個反向代理。在微服務(wù)架構(gòu)下,Zuul 從客戶端接受微服務(wù)調(diào)用并將其轉(zhuǎn)發(fā)給下游服務(wù)。要和下游服務(wù)進行溝通,Zuul 必須知道如何將進來的調(diào)用映射到下游路由中。Zuul 有一以下幾種路由機制:
通過服務(wù)發(fā)現(xiàn)自動映射路由
通過服務(wù)發(fā)現(xiàn)手動映射路由
使用靜態(tài) URL 手動映射
1)、服務(wù)發(fā)現(xiàn)自動映射默認情況下,Zuul 根據(jù)服務(wù) ID 來進行自動路由。先將組織服務(wù)中的延時去掉
啟動之前的所有服務(wù)實例,然后通過 postman 訪問localhost:5555/organizationservice/organization/12,得到結(jié)果如下:
說明服務(wù)網(wǎng)關(guān)自動路由成功。
??如果要查看 Zuul 服務(wù)器管理的路由,可以通過訪問 Zuul 服務(wù)器上的/routes,返回結(jié)果如下:
{ "/confsvr/**": "confsvr", "/licensingservice/**": "licensingservice", "/organizationservice/**": "organizationservice" }
左邊的路由由基于 Eureka 的服務(wù) ID 自動創(chuàng)建的,右邊為路由所有映射的 Eureka 服務(wù) ID。
2)、服務(wù)發(fā)現(xiàn)手動手動??如果覺得自動路由不好用,我們還可以更細粒度地明確定義路由映射。例如想要縮短組織服務(wù)名稱來簡化路由,可在application.yml配置中定義路由映射,在配置文件中加入如下配置:
zuul: routes: organizationservice: /org/**
??上面的配置將org開頭的路徑映射到組織服務(wù)上了。重啟服務(wù)器,訪問localhost:5555/org/organization/12,仍然能夠獲取到數(shù)據(jù)。
??現(xiàn)在訪問/routes端點可以看到如下結(jié)果:
{ "/org/**": "organizationservice", "/confsvr/**": "confsvr", "/licensingservice/**": "licensingservice", "/organizationservice/**": "organizationservice" }
可以看到不光有自定義的組織路由,自動映射的組織路由也存在,如果想要排除自動映射的路由可配置ignored-services屬性,用法如下:
zuul: routes: organizationservice: /org/** # 使用","分隔,“*”表示全部忽略 ignored-services: "organizationservice"
??服務(wù)網(wǎng)關(guān)有一種常見模式是通過使用/api之類的標(biāo)記來為所有服務(wù)調(diào)用添加前綴,可通過配置prefix屬性來支持。用法如下:
zuul: routes: organizationservice: /org/** # 使用","分隔,“*”表示全部忽略 ignored-services: "organizationservice" prefix: /api
配置后再次訪問/routes端點可以看到路徑前都加上了/api
3)、靜態(tài) URL 手動映射??如果系統(tǒng)系統(tǒng)中還存在一些不受 Eureka 管理的服務(wù),可以建立 Zuul 直接路由到一個靜態(tài)定義的 URL。假設(shè)許可證服務(wù)是其他語言編寫的 web 項目,并且希望通過 Zuul 來代理,可這樣配置:
zuul: routes: #用于內(nèi)部識別關(guān)鍵字 licensestatic: path: /licensestatic/** url: http://localhost:8091
配置完成后重啟 zuul 訪問/routes端點如下所示,靜態(tài)路由已經(jīng)加入:
{ "/api/licensestatic/**": "http://localhost:8091", "/api/org/**": "organizationservice", "/api/confsvr/**": "confsvr", "/api/licensingservice/**": "licensingservice", "/api/zuulservice/**": "zuulservice" }
??licensestatic 端點不再使用 Eureka,直接將請求路由到localhost:8091。但是這里存在一個問題,如果許可證服務(wù)有多個實例,該如何用到負載均衡?這里只能配置一條路徑指向請求。這里又有一個配置項來禁用 Ribbon 與 Eureka 集成,然后列出許可證服務(wù)的所有實例,配置如下:
#zuul配置 zuul: routes: #用于內(nèi)部識別關(guān)鍵字 licensestatic: path: /licensestatic/** serviceId: licensestatic organizationservice: /org/** # 使用","分隔,“*”表示全部忽略 ignored-services: "organizationservice" prefix: /api ribbon: eureka: #禁用Eureka支持 enabled: false licensestatic: ribbon: #licensestatic服務(wù)將會路由到下列地址 listOfServers: http://localhost:10011,http://localhost:10012
配置完畢后,訪問/routes端點發(fā)現(xiàn)licensestatic/**映射到了 licensestatic 服務(wù)上,相當(dāng)于 Zuul 模擬了一個服務(wù)出來。但是 Eureka 上是沒有這個服務(wù)的,所以需要禁用掉 Ribbon 的 Eureka 支持,不然是無法訪問成功的(Ribbon 向 Eureka 查詢該服務(wù)不存在,報錯)。現(xiàn)在 x=連續(xù)訪問localhost:5555//api/licensestatic/licensing/12,可以發(fā)現(xiàn)正常響應(yīng)和 404 交替出現(xiàn)(10011 上能否訪問成功,10012 報錯 404),說明配置的多個地址生效了。
問題又來了
??_禁用 eureka 支持會導(dǎo)致所有服務(wù)的地址都需要手動指定,ribbon 不會再從 eureka 中獲取服務(wù)實例信息。所以沒辦法混合使用_
??目前有兩種辦法來規(guī)避這個問題:
對于不能用 Eureka 管理的應(yīng)用,可以建立一個多帶帶的 Zuul 服務(wù)器來處理這些路由。
建立一個 Spring Cloud Sidecar 實例。Spring Cloud Sidecar 允許開發(fā)使用 Eureka 實例注冊非 JVM 服務(wù),然后再通過 Zuul 代理,相當(dāng)于曲線救國。
4、動態(tài)重載路由??zuul 還有一個動態(tài)加載路由的功能,也就是在不重啟 zuul 服務(wù)的情況下刷新路由。
??直接修改application.yml將 prefix 從/api改為/apis。注意這里修改后要讓修改生效需編譯一次 application.yml 讓修改替換到 target 文件中(idea 如此,eclipse 應(yīng)該類似),或者直接到編譯文件夾下修改 application.yml
??然后訪問/refresh路徑,可以看到如下返回值:
響應(yīng)表明更新 prefix。然后訪問/routes路徑會發(fā)現(xiàn)前綴變成了apis
??這個功能與 spring cloud config 配合,用起來就是爽。
5、服務(wù)超時??Zuul 使用 Netflix 的 Hystrix 和 Ribbon 庫來進行 http 請求。so 也是有超時機制存在的。配置方法和前面的一篇類似。但是只能通過配置文件來進行,無法通過注解(這是 Zuul 管理的沒有地方給你寫注解)。通過配置hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds屬性來實現(xiàn)。如果要為特定的服務(wù)配置只需將 default 替換為服務(wù)名就行了。
注意還要只有有另一個超時機制。雖然覆蓋了 hystrix 的超時,但是 Ribbon 也會超時任何超過 5s 的調(diào)用。so 如果超時時間大于 5s 還要配置 Ribbon 的超時,配置方式如下:
#對所有服務(wù)生效 ribbon.readTimeout: 7000 #對組織服務(wù)生效 licensingservice.ribbon.readTimeout: 70006、重點:過濾器
??這才是服務(wù)網(wǎng)關(guān)真正重要的東西。有了過濾器才能實現(xiàn)自定義的通用處理邏輯??稍诖诉M行通用的安全驗證、日志、服務(wù)跟蹤等操作。和 springboot 中的過濾器概念類似,這里就不做說明了。
??Zuul 支持以下四種過濾器:
前置過濾器——在將請求發(fā)送到目的地之前被調(diào)用。通常進行請求格式檢查、身份驗證等操作。
后置過濾器——在目標(biāo)服務(wù)被調(diào)用被將響應(yīng)發(fā)回調(diào)用者后被調(diào)用。通常用于記錄從目標(biāo)服務(wù)返回的響應(yīng)、處理錯誤或?qū)徍嗣舾行畔ⅰ?/p>
路由過濾器——在目標(biāo)服務(wù)被調(diào)用之前攔截調(diào)用。通常用來做動態(tài)路由。
錯誤過濾器——在產(chǎn)生錯誤是調(diào)用,用于對錯誤進行統(tǒng)一處理。
下圖展示了在處理客戶端請求時,各種過濾器時如何工作的:
下面說說如何來使用這些過濾器:
a、前置過濾器??這里我們來實現(xiàn)一個過濾器-IdFilter,對每個請求檢查請求頭中是否有一個關(guān)聯(lián) id,無 id 生成一個 id 加入到 header 中。代碼如下:
@Component public class IdFilter extends ZuulFilter { private static final Logger LOGGER = LoggerFactory.getLogger(IdFilter.class); /** * 返回過濾器類型 ;pre:前置過濾器。post:后置過濾器。routing:路由過濾器。error:錯誤過濾器 */ @Override public String filterType() { return "pre"; } /** * 過濾器執(zhí)行順序 */ @Override public int filterOrder() { return 1; } /** * 是否啟動此過濾器 */ @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); String id = ctx.getRequest().getHeader("id"); //如果request找不到,再到zuul的方法中找id.request不允許直接修改response中的header, // 所以為了讓后續(xù)的過濾器能夠獲取到id才有下面的語法 if(id==null){ id = ctx.getZuulRequestHeaders().get("id"); } if (id == null) { id = UUID.randomUUID().toString(); LOGGER.info("{} 無id,生成id:{}",ctx.getRequest().getRequestURI(), id); ctx.addZuulRequestHeader("id", id); } else { LOGGER.info("{}存在id:{}", ctx.getRequest().getRequestURI(), id); } return null; } }
要在 Zuul 中實現(xiàn)過濾器,必須拓展 ZuulFilter 類(2.x 版本中不是這樣的),然后覆蓋上述 4 個方法。
??要給請求頭加入一個 header 需要在ctx.addZuulRequestHreader("","")(上面代碼中的 RequestContext 是 zuul 重寫的,在其中加入了一些方法)方法中操作,zuul 會在發(fā)出請求是把 header 加到請求頭中。(因為 Zuul 本質(zhì)是一個代理,它截取請求,然后自己再發(fā)送這個請求,所有不能也沒有必要在原來的 request 上加 header。
??重啟項目 Zuul,訪問localhost:5555/apis/licensestatic/licensing/12,可以看到控制臺有如下打印:
說明前置過濾器生效。
??現(xiàn)在從 zuul 服務(wù)網(wǎng)關(guān)發(fā)往許可證服務(wù)的 http 請求已經(jīng)攜帶了 id。
b、后置過濾器??后置過濾器通常用于進行敏感信息過濾和響應(yīng)記錄。這里我們實現(xiàn)一個后置過濾器,將許可證服務(wù)請求的響應(yīng)內(nèi)容打印到控制臺上同時把idheader 插入到服務(wù)客戶端請求的 response 中。
@Component public class ResponseFilter extends ZuulFilter { private static final Logger LOGGER = LoggerFactory.getLogger(ResponseFilter.class); /** * 返回過濾器類型 ;pre:前置過濾器。post:后置過濾器。routing:路由過濾器。error:錯誤過濾器 */ @Override public String filterType() { return "post"; } /** * 過濾器執(zhí)行順序 */ @Override public int filterOrder() { return 1; } /** * 是否啟動此過濾器 */ @Override public boolean shouldFilter() { return true; } @Override public Object run(){ RequestContext ctx = RequestContext.getCurrentContext(); String id = ctx.getZuulRequestHeaders().get("id"); ctx.getResponse().addHeader("id", id); try { BufferedReader reader = new BufferedReader(new InputStreamReader(ctx.getResponseDataStream())); String response = reader.readLine(); LOGGER.info("響應(yīng)為:{}", response); //寫到輸出流中,本來可以由zuul框架來操作,但是我們已經(jīng)讀取了輸入流,zuul讀不到數(shù)據(jù)了,所以要手動寫響應(yīng)到response ctx.getResponse().setHeader("Content-Type","application/json;charset=utf-8"); ctx.getResponse().getWriter().write(response); } catch (Exception e) { } return null; } }
經(jīng)過這樣一波操作,就能達到目的了。訪問:localhost:5555/apis/licensestatic/licensing/12??刂婆_打印如下:
請求響應(yīng)如下:
c、路由過濾器??路由過濾器用起來有點復(fù)雜,這里不寫具體的實際代碼,只是寫一個思路。具體代碼可以參考spring 微服務(wù)
獲取當(dāng)前請求路徑
判斷是否需要進行特殊路由
如需要進行特殊路由,在此進行 http 調(diào)用
將 http 調(diào)用的 response 寫入到當(dāng)前請求的 response 中
結(jié)束??終于寫完了,微服務(wù)的基礎(chǔ)學(xué)習(xí)又近了一步,加油!
本篇代碼存放于:github
本篇原創(chuàng)發(fā)布于:https://tapme.top/blog/detail/2019-01-03-19-19
掃碼關(guān)注微信公眾號:FleyX 學(xué)習(xí)筆記,獲取更多干貨
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/77838.html
摘要:本篇介紹并集成是一個服務(wù)器,也可以說是進入系統(tǒng)的唯一節(jié)點。它還可能有其他功能,如授權(quán)監(jiān)控負載均衡緩存請求分片和管理靜態(tài)響應(yīng)處理等。負責(zé)請求轉(zhuǎn)發(fā)合成和協(xié)議轉(zhuǎn)換。它可以在協(xié)議與內(nèi)部使用的非友好型協(xié)議間進行轉(zhuǎn)換,如協(xié)議協(xié)議。 ????????本篇介紹并集成ZUUL API getaway:API Gateway是一個服務(wù)器,也可以說是進入系統(tǒng)的唯一節(jié)點。這跟面向?qū)ο笤O(shè)計模式中的Facade模...
摘要:英文意思就是說提供一個回退機制當(dāng)路由后面的服務(wù)發(fā)生故障時。注意注解能注冊到服務(wù)上,是因為該注解包含了客戶端的注解,該是一個復(fù)合注解。地址可以查看該微服務(wù)網(wǎng)關(guān)代理了多少微服務(wù)的。 SpringCloud(第 025 篇)Zuul 路由后面的微服務(wù)掛了后,Zuul 提供了一種回退機制來應(yīng)對熔斷處理 - 一、大致介紹 1、在一些不穩(wěn)定因素導(dǎo)致路由后面的微服務(wù)宕機或者無響應(yīng)時,zuul 就會累...
閱讀 2511·2021-11-15 11:36
閱讀 1256·2019-08-30 15:56
閱讀 2310·2019-08-30 15:53
閱讀 1100·2019-08-30 15:44
閱讀 711·2019-08-30 14:13
閱讀 1050·2019-08-30 10:58
閱讀 539·2019-08-29 15:35
閱讀 1362·2019-08-29 13:58