經過以前幾篇Spring Cloud中幾個核心組件的介紹,咱們已經能夠構建一個簡略的(不夠完善)微服務架構了。好比下圖所示:html
咱們使用Spring Cloud Netflix中的Eureka實現了服務註冊中心以及服務註冊與發現;而服務間經過Ribbon或Feign實現服務的消費以及均衡負載;經過Spring Cloud Config實現了應用多環境的外部化配置以及版本管理。爲了使得服務集羣更爲健壯,使用Hystrix的融斷機制來避免在微服務架構中個別服務出現異常時引發的故障蔓延。前端
在該架構中,咱們的服務集羣包含:內部服務Service A和Service B,他們都會註冊與訂閱服務至Eureka Server,而Open Service是一個對外的服務,經過均衡負載公開至服務調用方。本文咱們把焦點彙集在對外服務這塊,這樣的實現是否合理,或者是否有更好的實現方式呢?java
先來講說這樣架構須要作的一些事兒以及存在的不足:web
讓客戶端直接與各個微服務通信,會有如下的問題:spring
面對相似上面的問題,咱們要如何解決呢?下面進入本文的正題:服務網關!apache
使用網關優勢:後端
爲了解決上面這些問題,咱們須要將權限控制這樣的東西從咱們的服務單元中抽離出去,而最適合這些邏輯的地方就是處於對外訪問最前端的地方,咱們須要一個更強大一些的均衡負載器,它就是本文未來介紹的:服務網關。api
服務網關是微服務架構中一個不可或缺的部分。經過服務網關統一貫外系統提供REST API的過程當中,除了具有服務路由、均衡負載功能以外,它還具有了權限控制等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,爲微服務架構提供了前門保護的做用,同時將權限控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務集羣主體可以具有更高的可複用性和可測試性。跨域
下面咱們經過實例例子來使用一下Zuul來做爲服務的路有功能。瀏覽器
在使用Zuul以前,咱們先構建一個服務註冊中心、以及兩個簡單的服務,好比:我構建了一個compute-service,一個compute-service-B。而後啓動eureka-server和這兩個服務。經過訪問eureka-server,咱們能夠看到compute-service和compute-service-B已經註冊到了服務中心。
compute-service:見《服務註冊發現Eureka之一:Spring Cloud Eureka的服務註冊與發現》
compute-service-B:將compute-service拷貝一份,修改下項目名和服務名
若是您還不熟悉如何構建服務中心和註冊服務,請先閱讀見《服務註冊發現Eureka之一:Spring Cloud Eureka的服務註冊與發現》。
若是是多實例的話,用逗號分隔
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.dxz.zuul</groupId> <artifactId>api-gateway</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>api-gateway</name> <description>zuul project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.5.RELEASE</version> <!--配合spring cloud版本 --> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <!--設置字符編碼及java版本 --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!--增長zuul的依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> </dependency> <!--用於測試的,本例可省略 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <!--依賴管理,用於管理spring-cloud的依賴 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-parent</artifactId> <version>Brixton.SR3</version> <!--官網爲Angel.SR4版本,可是我使用的時候老是報錯 --> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <!--使用該插件打包 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
啓動類:
package com.dxz.zuul; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @EnableZuulProxy @SpringBootApplication public class ApiGatewayApplication { public static void main(String[] args) { new SpringApplicationBuilder(ApiGatewayApplication.class).web(true).run(args); } }
配置:
spring.application.name=api-gateway server.port=5555 zuul.routes.api-test.path=/api-test/** zuul.routes.api-test.url=http://localhost:2223/,http://localhost:2221/
測試:
啓動相關服務:
瀏覽器訪問,結果以下:
pom中增長:
<!--增長eureka-server的依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency>
路由配置以下:
spring.application.name=api-gateway
server.port=5555
zuul.routes.api-test.path=/api-test/**
zuul.routes.api-test.url=http://localhost:2223/
zuul.routes.api-a.path=/api-a/** zuul.routes.api-a.serviceId=COMPUTE-SERVICE zuul.routes.api-b.path=/api-b/** zuul.routes.api-b.serviceId=COMPUTE-SERVICE-B eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
結果:
Zuul配置
完成上面的工做後,Zuul已經能夠運行了,可是如何讓它爲咱們的微服務集羣服務,還須要咱們另行配置,下面詳細的介紹一些經常使用配置內容。
經過服務路由的功能,咱們在對外提供服務的時候,只須要經過暴露Zuul中配置的調用地址就可讓調用方統一的來訪問咱們的服務,而不須要了解具體提供服務的主機信息了。
在Zuul中提供了兩種映射方式:
spring.application.name=api-gateway
server.port=5555
zuul.routes.api-test.path=/api-test/**
zuul.routes.api-test.url=http://localhost:2223/
其中,配置屬性zuul.routes.api-a-url.path中的api-a-url部分爲路由的名字,能夠任意定義,可是一組映射關係的path和url要相同,下面講serviceId時候也是如此。該配置,定義了,全部到Zuul的中規則爲:/api-a-url/**
的訪問都映射到http://localhost:2222/
上,也就是說當咱們訪問http://localhost:5555/api-a-url/add?a=1&b=2
的時候,Zuul會將該請求路由到:http://localhost:2222/add?a=1&b=2
上。
spring.application.name=api-gateway
server.port=5555
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=COMPUTE-SERVICE
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=COMPUTE-SERVICE-B
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
推薦使用serviceId的映射方式,除了對Zuul維護上更加友好以外,serviceId映射方式還支持了斷路器,對於服務故障的狀況下,能夠有效的防止故障蔓延到服務網關上而影響整個系統的對外服務
在完成了服務路由以後,咱們對外開放服務還須要一些安全措施來保護客戶端只能訪問它應該訪問到的資源。因此咱們須要利用Zuul的過濾器來實現咱們對外服務的安全控制。
在服務網關中定義過濾器只須要繼承ZuulFilter
抽象類實現其定義的四個抽象函數就可對請求進行攔截與過濾。
好比下面的例子,定義了一個Zuul過濾器,實現了在請求被路由以前檢查請求中是否有accessToken
參數,如有就進行路由,若沒有就拒絕訪問,返回401 Unauthorized
錯誤。
package com.dxz; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; public class AccessFilter extends ZuulFilter { private static Logger log = Logger.getLogger(AccessFilter.class); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString())); Object accessToken = request.getParameter("accessToken"); if (accessToken == null) { log.warn("access token is empty"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); return null; } log.info("access token ok"); return null; } }
filterType
:返回一個字符串表明過濾器的類型,在zuul中定義了四種不一樣生命週期的過濾器類型,具體以下:自定義過濾器的實現,須要繼承ZuulFilter
,須要重寫實現下面四個方法:
pre
:能夠在請求被路由以前調用routing
:在路由請求時候被調用post
:在routing和error過濾器以後被調用error
:處理請求時發生錯誤時被調用filterOrder
:經過int值來定義過濾器的執行順序shouldFilter
:返回一個boolean類型來判斷該過濾器是否要執行,因此經過此函數可實現過濾器的開關。在上例中,咱們直接返回true,因此該過濾器老是生效。run
:過濾器的具體邏輯。須要注意,這裏咱們經過ctx.setSendZuulResponse(false)
令zuul過濾該請求,不對其進行路由,而後經過ctx.setResponseStatusCode(401)
設置了其返回的錯誤碼,固然咱們也能夠進一步優化咱們的返回,好比,經過ctx.setResponseBody(body)
對返回body內容進行編輯等。在實現了自定義過濾器以後,還須要實例化該過濾器才能生效,咱們只須要在應用主類中增長以下內容:
package com.dxz; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.context.annotation.Bean; @EnableZuulProxy @SpringCloudApplication public class SCzullApplication { public static void main(String[] args) { new SpringApplicationBuilder(SCzullApplication.class).web(true).run(args); } @Bean public AccessFilter accessFilter() { return new AccessFilter(); } }
啓動該服務網關後,訪問:
http://localhost:5555/api-a/add?a=1&b=2
:返回401錯誤http://localhost:5555/api-a/add?a=1&b=2&accessToken=token
:正確路由到server-A,並返回計算內容對於其餘一些過濾類型,這裏就不一一展開了,根據以前對filterType
生命週期介紹,能夠參考下圖去理解,並根據本身的須要在不一樣的生命週期中去實現不一樣類型的過濾器。
最後,總結一下爲何服務網關是微服務架構的重要部分,是咱們必需要去作的緣由: