服務網關也就是API網關,服務網關能夠做爲服務的統一入口,提供身份校驗、動態路由、負載均衡、安全管理、統計、監控、流量管理、灰度發佈、壓力測試等功能java
服務網關/API網關並非微服務體系所特有的,而是微服務流行起來以後,服務網關基本上成了微服務架構的標配。服務網關一般用於向客戶端或者合做夥伴應用提供統一的服務接入方式,例如:App網關、開放平臺(OpenAPI)等等。git
Zuul是Netflix開源的服務網關/API網關,提供動態路由、監控、彈性、安全性等功能。github
Zuul is an edge service that provides dynamic routing, monitoring, resiliency, security, and more. Please view the wiki for usage, information, HOWTO, etc https://github.com/Netflix/zuul/wikiweb
Zuul+Eureka交互示意圖spring
這裏只列舉了單個網關集羣,實際上互聯網公司一般會有多個網關集羣,App網關、HTML5網關、OpenAPI網關等等apache
框架 | 版本 |
---|---|
Spring Boot | 2.0.0.RELEASE |
Spring Cloud | Finchley.RELEASE |
Zuul | 1.3.1 (Spring Cloud-Finchley還沒整合2.x版本) |
JDK | 1.8.x |
參考上一篇:https://ken.io/note/spring-cloud-hystrix-dashboard-turbine-quickstart
基於源碼:https://github.com/ken-io/springcloud-course/tree/master/chapter-06api
啓動Eureka Server: http://localhost:8800
啓動Test Service:http://localhost:8602,http://localhost:8603安全
按照慣例,使用maven-archtype-quickstart模板建立項目架構
項 | 說明 |
---|---|
GroupId | io.ken.springcloud.zuul |
ArtifactId | zuul |
<?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>io.ken.springcloud.zuul</groupId> <artifactId>zuul</artifactId> <version>1.0-SNAPSHOT</version> <name>zuul</name> <url>http://ken.io</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <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>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> </project>
修改\src\main\java\io\ken\springcloud\zuul\App.javaapp
@EnableZuulProxy:啓用Zuul
package io.ken.springcloud.zuul; 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; @EnableZuulProxy @EnableEurekaClient @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
在\src\main下建立文件夾resources文件夾並設置爲Resources Root
在resources文件夾下建立application.yml文件並配置Zuul
server: port: 8888 spring: application: name: zuul eureka: client: serviceUrl: defaultZone: http://localhost:8800/eureka/
啓動zuul項目後,訪問:http://localhost:8888/testservice
會交替顯示如下信息
{ "code": 0, "message": "hello", "content": null, "serviceName": "testservice", "host": "localhost:8602" } /*********分割線*********/ { "code": 0, "message": "hello", "content": null, "serviceName": "testservice", "host": "localhost:8603" }
經過配置文件咱們知道,Spring Cloud Zuul 將本身做爲一個服務註冊到了Eureka。這也就意味着Zuul能夠拿到全部註冊到Eureka的其餘服務的信息。Zuul爲這些服務建立了默認的路由規則:/{servicename}/**
因此,咱們訪問 http://localhost:8888/testservice
就至關於訪問:(testservice)http://localhost:8602
因爲咱們註冊了兩個testservice的實例,並且Zuul自己具有負載均衡的功能。因此會交替顯示不一樣實例的響應內容。
根據上一篇FeignClient項目代碼調整
在\src\main\java\io\ken\springcloud\feignclient\service
增長TestServiceZuul.java
package io.ken.springcloud.feignclient.service; import io.ken.springcloud.feignclient.model.Result; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @FeignClient(value = "zuul") public interface TestServiceZuul { @RequestMapping(value = "/testservice/", method = RequestMethod.GET) String indexService(); @RequestMapping(value = "/testservice/plus", method = RequestMethod.GET) Result plusService(@RequestParam(name = "numA") int numA, @RequestParam(name = "numB") int numB); }
簡單來講,就是將直接ServiceName配置爲zuul,請求的url前增長目標服務的ServiceName便可。
引用示例:
@RestController public class TestController { @Autowired private TestServiceZuul testServiceZuul; @RequestMapping("/ti-zuul") public Object ti_zuul() { return testServiceZuul.indexService(); } }
配置項 | ken.io 的說明 |
---|---|
zuul.routes.{routename} | 路由名稱,自定義,支持小寫字母、- |
zuul.routes.{routename}.path | 要路由的路徑,支持通配符:?、 、* |
zuul.routes.{routename}.serviceId | 註冊在Eureka的ServiceName |
zuul.routes.{routename}.url | 若是應用沒有註冊在Eureka,也能夠經過指定Url來路由 |
zuul.ignored-services | 忽略指定的服務,能夠配置多個,以,間隔 |
zuul.ignored-patterns | 忽略指定的路徑,能夠配置多個,以,間隔。一樣支持通配符 |
通配符 | 說明 | path舉例 | 匹配示例 |
---|---|---|---|
? | 匹配單個任意字符 | /ts/? | /ts/a、/ts/b |
* | 匹配任意字符 | ts/* | /ts/a、/ts/b、/ts/ab |
** | 匹配任意字符且支持多級目錄 | ts/** | /ts/a、/ts/b、/ts/ab、/ts/ab/c |
對於通配符來講,沒有特定的需求,直接用**就好
zuul: routes: ts: path: /ts/** serviceId: testservice
一般適用於:一個服務隨着業務的發展不斷擴大須要拆分,這時候咱們能夠經過api兼容+路由配置來適配,能夠對上游無感知。例如用戶服務(userservice)有三個核心模塊,auth、info、safe,通過拆分拆出來兩個服務:authservice、safeservice。那麼拆分後的路由配置:
zuul: routes: user-auth: path: /user/auth/** serviceId: authservice user-safe: path: /user/safe/** serviceId: safeservice user: path: /user/** serviceId: userservice
由於路由規則匹配順序是按配置順序來的,因此未拆出去的配置在最後。
當一個應用並無註冊到服務註冊中心(Eureka),咱們又須要將它掛到網關上能夠經過指定url的這種方式。
zuul: routes: ken-io: path: /ken/** url: https://ken.io/
一般適用於某些通用的接口不暴露給外部,例如每一個應用都會有一個健康檢查入口,可是這個入口是不該該暴露給外部的,就能夠經過忽略規則屏蔽掉。
zuul: ignored-patterns: /**/hs,/**/health
zuul: ignored-services: aservice,bservice
在src\main\java\io\ken\springcloud\zuul建立package:filter
而後建立Filter類:AuthFilter.java
實現ZuulFilter並標記爲Component
package io.ken.springcloud.zuul.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.util.logging.Logger; @Component public class AuthFilter extends ZuulFilter { private static Logger log = Logger.getLogger(AuthFilter.class.toString()); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info(String.format("header-token:%s,param-token:%s", request.getHeader("token"), request.getParameter("token"))); String token_header = request.getHeader("token") == null ? "" : request.getHeader("token"); String token_param = request.getParameter("token") == null ? "" : request.getParameter("token"); if (token_header.equals("") && token_param.equals("")) { try { ctx.setSendZuulResponse(false); ctx.getResponse().getWriter().write("{\"code\": 9999,\"message\": \"token is empty.\"}"); } catch (Exception e) { log.warning("system error"); } } else if (!token_header.equals("")) { log.warning(String.format("token is %s", token_header)); } else if (!token_param.equals("")) { log.warning(String.format("token is %s", token_param)); } return null; } }
從新啓動Zuul,而後訪問:http://localhost:8888/testservice
因爲缺乏token,會返回以下信息:
{ "code": 9999, "message": "token is empty." }
這裏只是作一個示例,須要完成身份校驗,還要完善代碼。
方法名 | 說明 |
---|---|
filterType() | 過濾器類型:pre、routing、post、error |
filterOrder | 過濾器順序,用於指定過濾器執行順序 |
shouldFilter | 是否要過濾,能夠根據當前請求信息判斷是否過濾,也能夠默認返回true |
run | 過濾器執行邏輯,執行具體的過濾操做。 |
type | 說明 |
---|---|
pre | 在路由以前執行過濾 |
routing | 在路由時執行過濾 |
post | 在路由以後執行過濾 |
error | 在發生錯誤時執行過濾 |