spring boot 2.0.3+spring cloud (Finchley)五、路由網關Spring Cloud Zuul

 Zuul做爲微服務系統的網關組件,用於構建邊界服務,致力於動態路由、過濾、監控、彈性伸縮和安全。html

爲何須要Zuuljava

Zuul、Ribbon以及Eureka結合能夠實現智能路由和負載均衡的功能;網關將全部服務的API接口統一聚合,統一對外暴露。外界調用API接口時,不須要知道微服務系統中各服務相互調用的複雜性,保護了內部微服務單元的API接口;網關能夠作用戶身份認證和權限認證,防止非法請求操做API接口;網關能夠實現監控功能,實時日誌輸出,對請求進行記錄;網關能夠實現流量監控,在高流量的狀況下,對服務降級;API接口從內部服務分離出來,方便作測試。git

Zuul經過Servlet來實現,經過自定義的ZuulServlet來對請求進行控制。核心是一系列過濾器,能夠在Http請求的發起和響應返回期間執行一系列過濾器。Zuul採起了動態讀取、編譯和運行這些過濾器。過濾器之間不能直接通訊,而是經過RequestContext對象來共享數據,每一個請求都會建立一個RequestContext對象。github

Zuul生命週期以下圖。 當一個客戶端Request請求進入Zuul網關服務時,網關先進入」pre filter「,進行一系列的驗證、操做或者判斷。而後交給」routing filter「進行路由轉發,轉發到具體的服務實例進行邏輯處理、返回數據。當具體的服務處理完成後,最後由」post filter「進行處理,該類型的處理器處理完成以後,將Request信息返回客戶端。 web

搭建Zuul服務spring

須要配置好eureka,ribbon,可參考:apache

spring boot 2.0.3+spring cloud (Finchley)一、搭建Eureka 以及構建高可用Eureka Server集羣 api

spring boot 2.0.3+spring cloud (Finchley)二、搭建負載均衡Ribbon (Eureka+Ribbon+RestTemplate)瀏覽器

spring boot 2.0.3+spring cloud (Finchley)三、聲明式調用Feign安全

 新建工程eureka-zuul-client,pom文件引入相關依賴,包括主maven工程的pom文件,eureka client起步依賴,Zuul的起步依賴,web的起步依賴(已在主maven工程pom中配置)。

<?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.cralor</groupId>
    <artifactId>eureka-zuul-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-zuul-client</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>com.cralor</groupId>
        <artifactId>chap9-zuul</artifactId>
        <version>0.0.1-SNAPSHOT</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>
    </properties>
    <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>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

在啓動類加上註解@EnableZuulProxy

@EnableZuulProxy
@SpringBootApplication
public class EurekaZuulClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaZuulClientApplication.class, args);
    }
}

在配置文件作相關配置,包括端口號5000,服務註冊中心地址http://localhost:8761/eureka/,程序名service-zuul,其中zuul路由配置:zuul.routes.hiapi爲」/hiapi/**「,zuul.routes.serviceId爲」eureka-client「,這兩個配置就能夠將以」/hiapi「開頭的Url路由到eureka-client服務,zuul.routes.hiapi中的」hiapi「是本身定義的須要指定它的path和serviceId,二者配合使用,就能夠將指定類型的請求Url路由到指定的serviceId。同理,知足」/ribbonapi「開頭的請求Url都會被分發到eureka-ribbon-client,知足」/feignapi「開頭的請求Url都會被分發到eureka-feign-client服務。若是服務存在多個實例,zuul會結合ribbon作負載均衡。

server:
  port: 5000
spring:
  application:
    name: service-zuul

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

zuul:
  routes:
    hiapi:
      path: /hiapi/**
      serviceId: eureka-client
    ribbonapi:
      path: /ribbonapi/**
      serviceId: eureka-ribbon-client
    feignapi:
      path: /feignapi/**
      serviceId: eureka-feign-client

依次啓動工程eureka-server、eureka-client(啓動兩個實例端口爲876二、8763)、eureka-ribbon-client、eureka-feign-client和eureka-zuul-client。瀏覽器屢次訪問http://localhost:5000/hiapi/hi?name=cralor,會交替顯示

   

可見zuul在路由轉發作了負載均衡。同理屢次訪問http://localhost:5000/ribbonapi/hi?name=cralor和http://localhost:5000/feignapi/hi?name=cralor也能夠看到相似內容。

 在Zuul上配置API接口的版本號

若是想給每一個服務的API接口加前綴,可以使用zuul.prefix配置,例如http://localhost:5000/v1/hiapi/hi?name=cralor,即在全部的API接口上加一個v1做爲版本號。

zuul:
  prefix: /v1
  routes:
    hiapi:
      path: /hiapi/**
      serviceId: eureka-client
    ribbonapi:
      path: /ribbonapi/**
      serviceId: eureka-ribbon-client
    feignapi:
      path: /feignapi/**
      serviceId: eureka-feign-client

重啓eureka-zuul-client,訪問http://localhost:5000/v1/hiapi/hi?name=cralor,結果如上。

在Zuul上配置熔斷器

Zuul做爲Netflix組件,能夠與Ribbon、Eureka和Hystrix等組件相結合,實現負載均衡、熔斷器的功能。默認狀況下Zuul和Ribbon相結合,實現了負載均衡。實現熔斷器功能須要實現FallbackProvider接口,實現該接口的兩個方法,一個是getRoute(),用於指定熔斷器功能應用於哪些路由的服務;另外一個方法fallbackResponse()爲進入熔斷器功能時執行的邏輯。

@Component
public class MyFallbackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        return "eureka-client";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        System.out.println("route:"+route);
        System.out.println("exception:"+cause.getMessage());
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "ok";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("oooops!error,i'm the fallback.".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

重啓eureka-zuul-client,關閉eureka-client的全部實例,訪問http://localhost:5000/v1/hiapi/hi?name=cralor,瀏覽器顯示

若是須要全部的路由服務都加熔斷功能,須要在getRoute()方法上返回」*「的匹配符

    @Override
    public String getRoute() {
        return "*";
    }

在Zuul使用過濾器

Zuul包括如下4中過濾器

PRE過濾器:是在請求路由到具體服務以前執行的,能夠作安全驗證,如身份驗證,參數驗證。

ROUTING過濾器:它用於將請求 路由到具體的微服務實例。默認使用Http Client進行網絡請求。

POST過濾器:在請求已被路由到微服務後執行的。可用做收集統計信息、指標,以及將響應傳輸到客戶端。

ERROR過濾器:在其餘過濾器發生錯誤時執行。

zuul中默認實現的filter

類型 順序 過濾器 功能
pre -3 ServletDetectionFilter 標記處理Servlet的類型
pre -2 Servlet30WrapperFilter 包裝HttpServletRequest請求
pre -1 FormBodyWrapperFilter 包裝請求體
route 1 DebugFilter 標記調試標誌
route 5 PreDecorationFilter 處理請求上下文供後續使用
route 10 RibbonRoutingFilter serviceId請求轉發
route 100 SimpleHostRoutingFilter url請求轉發
route 500 SendForwardFilter forward請求轉發
post 0 SendErrorFilter 處理有錯誤的請求響應
post 1000 SendResponseFilter 處理正常的請求響應

禁用指定的Filter

能夠在 application.yml 中配置須要禁用的 filter,格式爲zuul.<SimpleClassName>.<filterType>.disable=true
好比要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter就設置

zuul:
  SendResponseFilter:
    post:
      disable: true

實現自定義濾器須要繼承ZuulFilter,實現ZuulFilter中的抽象方法,包括filterType(),filterOrder()以及IZuulFilter的shouldFilter()和run()方法。filterType()爲過濾器類型,有4中類型:pre、post、routing和error。filterOrder()是過濾順序,它爲一個int類型的值,值越小,越早執行該過濾器。shouldFilter()表示是否須要執行該過濾器邏輯,true表示執行,false表示不執行,若是true則執行run()方法。run()方法是具體的過濾的邏輯。本例中檢查請求的參數中是否傳了token這個參數,若是沒有傳,則請求不被路由到具體的服務實例,直接返回 響應,狀態碼爲401。

@Component
public class MyFilter extends ZuulFilter {

    private static Logger log=LoggerFactory.getLogger(MyFilter.class);

    @Override
    public String filterType() {
        return "pre"; //定義filter的類型,有pre、route、post、error四種
    }

    @Override
    public int filterOrder() {
        return 0;//定義filter的順序,數字越小表示順序越高,越先執行
    }

    @Override
    public boolean shouldFilter() {
        return true;//表示是否須要執行該filter,true表示執行,false表示不執行
    }

    @Override
    public Object run() throws ZuulException {
        //filter須要執行的具體操做
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String token = request.getParameter("token");
        System.out.println(token);
        if(token==null){
            log.warn("token is empty");
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try {
                ctx.getResponse().getWriter().write("token is empty");
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
        log.info("ok");
        return null;
    }
}

重啓服務,訪問http://localhost:5000/v1/hiapi/hi?name=cralor,顯示

 地址欄輸入http://localhost:5000/v1/hiapi/hi?name=cralor&token=ccc,顯示

可見,MyFilter這個Bean注入IOC容器後,對請求進行了過濾,並在請求路由轉發以前進行了邏輯判斷。

 

案例代碼地址:https://github.com/cralor7/springcloud

 

相關文章
相關標籤/搜索