爲何要有服務網關:前端
咱們都知道在微服務架構中,系統會被拆分爲不少個微服務。那麼做爲客戶端要如何去調用這麼多的微服務呢?難道要一個個的去調用嗎?很顯然這是不太實際的,咱們須要有一個統一的接口與這些微服務打交道,這就是咱們須要服務網關的緣由。java
咱們已經知道,在微服務架構中,不一樣的微服務能夠有不一樣的網絡地址,各個微服務之間經過互相調用完成用戶請求,客戶端可能經過調用N個微服務的接口完成一個用戶請求。好比:用戶查看一個商品的信息,它可能包含商品基本信息、價格信息、評論信息、折扣信息、庫存信息等等,而這些信息獲取則來源於不一樣的微服務,諸如產品系統、價格系統、評論系統、促銷系統、庫存系統等等,那麼要完成用戶信息查看則須要調用多個微服務,這樣會帶來幾個問題:nginx
以下圖所示:git
咱們該如何解決這些問題呢?咱們能夠嘗試想一下,不要讓前端直接知道後臺諸多微服務的存在,咱們的系統自己就是從業務領域的層次上進行劃分,造成多個微服務,這是後臺的處理方式。對於前臺而言,後臺應該仍然相似於單體應用同樣,一次請求便可,因而咱們能夠在客戶端和服務端之間增長一個API網關,全部的外部請求先經過這個微服務網關。它只需跟網關進行交互,而由網關進行各個微服務的調用。web
這樣的話,咱們就能夠解決上面提到的問題,同時開發就能夠獲得相應的簡化,還能夠有以下優勢:spring
這裏能夠聯想到一個概念,面向對象設計中的門面模式,即對客戶端隱藏細節,API網關也是相似的東西,只不過叫法不一樣而已。它是系統的入口,封裝了應用程序的內部結構,爲客戶端提供統一服務,一些與業務自己功能無關的公共邏輯能夠在這裏實現,諸如認證、鑑權、監控、緩存、負載均衡、流量管控、路由轉發等等。示意圖:bootstrap
總結一下,服務網關大概就是四個功能:統一接入、流量管控、協議適配、安全維護。而在目前的網關解決方案裏,有Nginx+ Lua、Kong、Tyk以及Spring Cloud Zuul等等。這裏以Zuul爲例進行說明,它是Netflix公司開源的一個API網關組件,Spring Cloud對其進行二次封裝作到開箱即用。同時,Zuul還能夠與Spring Cloud中的Eureka、Ribbon、Hystrix等組件配合使用。後端
能夠說,Zuul實現了兩個功能,路由轉發和過濾器: api
服務網關的要素:跨域
Zuul的四種過濾器API:
zuul先後置過濾器的典型應用場景:
Zuul的核心是一系列過濾器,開發者經過實現過濾器接口,能夠作大量切面任務,即AOP思想的應用。Zuul的過濾器之間沒有直接的相互通訊,而是經過本地ThreadLocal變量進行數據傳遞的。Zuul架構圖:
在Zuul裏,一個請求的生命週期:
本小節咱們來學習如何使用服務網關,也就是Spring Cloud Zuul這個組件,首先新建一個項目,選擇以下模塊:
pom.xml配置的依賴以下:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
項目建立好後,將application.properties改成bootstrap.yml,編輯內容以下:
spring: application: name: api-gateway cloud: config: discovery: enabled: true service-id: CONFIG profile: dev eureka: client: service-url: defaultZone: http://localhost:8761/eureka/
注:我這裏使用了配置中心,若對此不熟悉的話,能夠參考我另外一篇文章:Spring Cloud Config - 統一配置中心
在啓動類中,加上@EnableZuulProxy
註解,代碼以下:
package org.zero.springcloud.apigateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @SpringBootApplication @EnableZuulProxy public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } }
完成以上配置後啓動這個項目,我這裏項目啓動是正常的。而後咱們來經過這個網關訪問一下商品服務中獲取商品列表的接口。以下:
訪問地址說明:
/product
是須要訪問的服務的名稱/buyer/product/list
是商品服務中獲取商品列表的接口地址只要是在eureka上註冊的服務都可以經過zuul進行轉發,例如我經過zuul來訪問config的配置文件:
如上,能夠看到,報錯了,網關超時。這是由於默認狀況下,zuul的熔斷機制超時時間是2秒,當一個服務響應的時間較長就會報網關超時錯誤。
咱們在配置文件中,加上以下超時時間的配置便可:
ribbon.ReadTimeout, ribbon.SocketTimeout這兩個就是ribbon超時時間設置,當在yml寫時,應該是沒有提示的,給人的感受好像是否是這麼配的同樣,其實不用管它,直接配上就生效了。
還有zuul.host.connect-timeout-millis, zuul.host.socket-timeout-millis這兩個配置,這兩個和上面的ribbon都是配超時的。區別在於,若是路由方式是serviceId的方式,那麼ribbon的生效,若是是url的方式,則zuul.host開頭的生效。(此處重要!使用serviceId路由和url路由是不同的超時策略)
若是你在zuul配置了熔斷fallback的話,熔斷超時也要配置,即hystrix那段配置。否則若是你配置的ribbon超時時間大於熔斷的超時,那麼會先走熔斷,至關於你配的ribbon超時就不生效了。
如今重啓項目,再次訪問以前的地址,就不會出現網關超時的錯誤了:
以前咱們訪問的都是GET類型的接口,咱們來看看POST類型的是否可以正常訪問。以下:
每次請求某個服務的接口,都須要帶上這個服務的名稱。有沒有辦法能夠自定義這個規則呢?答案是有的,在配置文件中,增長路由的自定義配置:
zuul: routes: myProduct: path: /myProduct/** serviceId: product
說明:
重啓項目,測試以下:
在項目啓動的時候,咱們也能夠在控制檯中查看到zuul全部的路由規則:
若是咱們有些服務的接口不但願對外暴露,只但願在服務間調用,那麼就能夠在配置文件中,增長路由排除的配置。例如我不但願listForOrder
被外部訪問,則在配置文件中,增長以下配置便可:
zuul: ... ignored-patterns: - /myProduct/buyer/product/listForOrder - /product/buyer/product/listForOrder
重啓項目,這時訪問就會報404了。以下:
還可使用通配符進行匹配。以下示例:
zuul: ... ignored-patterns: - /**/buyer/product/listForOrder
咱們在web開發中,常常會利用到cookie來保存用戶的登陸標識。但咱們使用了zuul組件後,默認狀況下,cookie是沒法直接傳遞給服務的,由於cookie默認被列爲敏感的headers。因此咱們須要在配置文件中,將sensitiveHeaders的值置空。以下:
zuul: ... routes: myProduct: path: /myProduct/** serviceId: product sensitiveHeaders: # 置空該屬性的值便可
咱們每次配置路由信息都須要重啓項目,顯得很麻煩,在線上環境也不能這樣隨便重啓項目。因此咱們得實現動態路由的功能,實現動態路由其實就利用一下咱們以前實現的動態刷新配置文件的功能便可。首先把Zuul路由相關的配置剪切到git上,以下:
注:我這裏使用了配置中心,若對此不熟悉的話,能夠參考我另外一篇文章:Spring Cloud Config - 統一配置中心
在pom.xml文件中,增長以下依賴項:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency>
而後在bootstrap.yml中,增長rabbitmq的配置。以下:
spring: application: name: api-gateway cloud: config: discovery: enabled: true service-id: CONFIG profile: dev rabbitmq: host: 127.0.0.1 port: 5672 username: admin password: admin eureka: client: service-url: defaultZone: http://localhost:8761/eureka/
最後在項目中建立一個config包,在該包中建立一個ZuulConfig配置類,用於加載配置文件中的配置。代碼以下:
package org.zero.springcloud.apigateway.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.stereotype.Component; /** * @program: api-gateway * @description: 網關路由配置類 * @author: 01 * @create: 2018-08-25 15:51 **/ @Component public class ZuulConfig { @RefreshScope @ConfigurationProperties("zuul") public ZuulProperties zuulProperties(){ return new ZuulProperties(); } }
完成以上配置後,重啓項目,便可實現動態路由了,例如我如今把myProduct改爲yourProduct,以下:
此時無需重啓項目,訪問新的地址便可。以下: