本項目的筆記和資料的Download,請點擊這一句話自行獲取。html
day01-springboot(理論篇) ;day01-springboot(實踐篇)前端
day02-springcloud(理論篇一) ;day02-springcloud(理論篇二) ;day02-springcloud(理論篇三) ;day02-springcloud(理論篇四) ;git
day03-springcloud(Hystix,Feign) ;day03-springcloud(Zuul網關); day04-ES6語法入門(視頻第三天講的ES6,筆記在day04位置)github
經過前面的學習,使用Spring Cloud實現微服務的架構基本成型,大體是這樣的:spring
咱們使用Spring Cloud Netflix中的Eureka實現了服務註冊中心以及服務註冊與發現;而服務間經過Ribbon或Feign實現服務的消費以及均衡負載;經過Spring Cloud Config實現了應用多環境的外部化配置以及版本管理。api
爲了使得服務集羣更爲健壯,使用Hystrix的融斷機制來避免在微服務架構中個別服務出現異常時引發的故障蔓延。安全
在該架構中,咱們的服務集羣包含:內部服務Service A和Service B,他們都會註冊與訂閱服務至Eureka Server,而Open Service是一個對外的服務,經過均衡負載公開至服務調用方。咱們把焦點彙集在對外服務這塊,直接暴露咱們的服務地址,這樣的實現是否合理,或者是否有更好的實現方式呢?springboot
先來講說這樣架構須要作的一些事兒以及存在的不足:架構
面對相似上面的問題,咱們要如何解決呢?答案是:服務網關!app
爲了解決上面這些問題,咱們須要將權限控制這樣的東西從咱們的服務單元中抽離出去,而最適合這些邏輯的地方就是處於對外訪問最前端的地方,咱們須要一個更強大一些的均衡負載器的 服務網關。
服務網關是微服務架構中一個不可或缺的部分。經過服務網關統一貫外系統提供REST API的過程當中,除了具有服務路由、均衡負載功能以外,它還具有了權限控制
等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,爲微服務架構提供了前門保護的做用,同時將權限控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務集羣主體可以具有更高的可複用性和可測試性。
官網:https://github.com/Netflix/zuul
Zuul:維基百科:
電影《捉鬼敢死隊》中的怪獸,Zuul,在紐約引起了巨大騷亂。
事實上,在微服務架構中,Zuul就是守門的大Boss!一夫當關,萬夫莫開!
不論是來自於客戶端(PC或移動端)的請求,仍是服務內部調用。一切對服務的請求都會通過Zuul這個網關,而後再由網關來實現 鑑權、動態路由等等操做。
Zuul就是咱們服務的統一入口。
填寫基本信息:
添加Zuul依賴:
經過@EnableZuulProxy
註解開啓Zuul的功能:
@SpringBootApplication @EnableZuulProxy // 開啓Zuul的網關功能 public class ZuulDemoApplication { public static void main(String[] args) { SpringApplication.run(ZuulDemoApplication.class, args); } }
server: port: 10010 #服務端口 spring: application: name: api-gateway #指定服務名
咱們須要用Zuul來代理user-service服務,先看一下控制面板中的服務狀態:
映射規則:
zuul: routes: user-service: # 這裏是路由id,隨意寫 path: /user-service/** # 這裏是映射路徑 url: http://127.0.0.1:8081 # 映射路徑對應的實際url地址
咱們將符合path
規則的一切請求,都代理到 url
參數指定的地址
本例中,咱們將 /user-service/**
開頭的請求,代理到http://127.0.0.1:8081
訪問的路徑中須要加上配置規則的映射路徑,咱們訪問:http://127.0.0.1:8081/user-service/user/10
在剛纔的路由規則中,咱們把路徑對應的服務地址寫死了!若是同一服務有多個實例的話,這樣作顯然就不合理了。
咱們應該根據服務的名稱,去Eureka註冊中心查找 服務對應的全部實例列表,而後進行動態路由纔對!
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
@SpringBootApplication @EnableZuulProxy // 開啓Zuul的網關功能 @EnableDiscoveryClient public class ZuulDemoApplication { public static void main(String[] args) { SpringApplication.run(ZuulDemoApplication.class, args); } }
eureka: client: registry-fetch-interval-seconds: 5 # 獲取服務列表的週期:5s service-url: defaultZone: http://127.0.0.1:10086/eureka instance: prefer-ip-address: true ip-address: 127.0.0.1
由於已經有了Eureka客戶端,咱們能夠從Eureka獲取服務的地址信息,所以映射時無需指定IP地址,而是經過服務名稱來訪問,並且Zuul已經集成了Ribbon的負載均衡功能。
zuul: routes: user-service: # 這裏是路由id,隨意寫 path: /user-service/** # 這裏是映射路徑 serviceId: user-service # 指定服務名稱
再次啓動,此次Zuul進行代理時,會利用Ribbon進行負載均衡訪問:
日誌中能夠看到使用了負載均衡器:
在剛纔的配置中,咱們的規則是這樣的:
zuul.routes.<route>.path=/xxx/**
: 來指定映射路徑。<route>
是自定義的路由名zuul.routes.<route>.serviceId=/user-service
:來指定服務名。而大多數狀況下,咱們的<route>
路由名稱每每和 服務名會寫成同樣的。所以Zuul就提供了一種簡化的配置語法:zuul.routes.<serviceId>=<path>
比方說上面咱們關於user-service的配置能夠簡化爲一條:
zuul: routes: user-service: /user-service/** # 這裏是映射路徑
省去了對服務名稱的配置。
在使用Zuul的過程當中,上面講述的規則已經大大的簡化了配置項。可是當服務較多時,配置也是比較繁瑣的。所以Zuul就指定了默認的路由規則:
user-service
,則默認的映射路徑就是:/user-service/**
也就是說,剛纔的映射規則咱們徹底不配置也是OK的,不信就試試看。
配置示例:
zuul: prefix: /api # 添加路由前綴 routes: user-service: # 這裏是路由id,隨意寫 path: /user-service/** # 這裏是映射路徑 service-id: user-service # 指定服務名稱
咱們經過zuul.prefix=/api
來指定了路由的前綴,這樣在發起請求時,路徑就要以/api開頭。
路徑/api/user-service/user/1
將會被代理到/user-service/user/1
Zuul做爲網關的其中一個重要功能,就是實現請求的鑑權。而這個動做咱們每每是經過Zuul提供的過濾器來實現的。
ZuulFilter是過濾器的頂級父類。在這裏咱們看一下其中定義的4個最重要的方法:
public abstract ZuulFilter implements IZuulFilter{ abstract public String filterType(); abstract public int filterOrder(); boolean shouldFilter();// 來自IZuulFilter Object run() throws ZuulException;// IZuulFilter }
shouldFilter
:返回一個Boolean
值,判斷該過濾器是否須要執行。返回true執行,返回false不執行。run
:過濾器的具體業務邏輯。filterType
:返回字符串,表明過濾器的類型。包含如下4種:
pre
:請求在被路由以前執行routing
:在路由請求時調用post
:在routing和errror過濾器以後調用error
:處理請求時發生錯誤調用filterOrder
:經過返回的int值來定義過濾器的執行順序,數字越小優先級越高。這張是Zuul官網提供的請求生命週期圖,清晰的表現了一個請求在各個過濾器的執行順序。
全部內置過濾器列表:
場景很是多:
接下來咱們來自定義一個過濾器,模擬一個登陸的校驗。基本邏輯:若是請求中有access-token參數,則認爲請求有效,放行。
@Component public class LoginFilter extends ZuulFilter{ @Override public String filterType() { // 登陸校驗,確定是在前置攔截 return "pre"; } @Override public int filterOrder() { // 順序設置爲1 return 1; } @Override public boolean shouldFilter() { // 返回true,表明過濾器生效。 return true; } @Override public Object run() throws ZuulException { // 登陸校驗邏輯。 // 1)獲取Zuul提供的請求上下文對象 RequestContext ctx = RequestContext.getCurrentContext(); // 2) 從上下文中獲取request對象 HttpServletRequest req = ctx.getRequest(); // 3) 從請求中獲取token String token = req.getParameter("access-token"); // 4) 判斷 if(token == null || "".equals(token.trim())){ // 沒有token,登陸校驗失敗,攔截 ctx.setSendZuulResponse(false); // 返回401狀態碼。也能夠考慮重定向到登陸頁。 ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); } // 校驗經過,能夠考慮把用戶信息放入上下文,繼續向後執行 return null; } }
沒有token參數時,訪問失敗:
添加token參數後:
Zuul中默認就已經集成了Ribbon負載均衡和Hystix熔斷機制。可是全部的超時策略都是走的默認值,好比熔斷超時時間只有1S,很容易就觸發了。所以建議咱們手動進行配置:
zuul: retryable: true ribbon: ConnectTimeout: 250 # 鏈接超時時間(ms) ReadTimeout: 2000 # 通訊超時時間(ms) OkToRetryOnAllOperations: true # 是否對全部操做重試 MaxAutoRetriesNextServer: 2 # 同一服務不一樣實例的重試次數 MaxAutoRetries: 1 # 同一實例的重試次數 hystrix: command: default: execution: isolation: thread: timeoutInMillisecond: 6000 # 熔斷超時時長:6000ms
=============================================
參考資料:
SpringCloud系列八:Zuul 路由訪問(Zuul 的基本使用、Zuul 路由功能、zuul 過濾訪問、Zuul 服務降級)
Spring Cloud升級最新Finchley版本的全部坑
end