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


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

為什么需要Zuul

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

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

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

搭建Zuul服務

需要配置好eureka,ribbon,可參考:

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

spring boot 2.0.3+spring cloud (Finchley)2、搭建負載均衡Ribbon (Eureka+Ribbon+RestTemplate)

spring boot 2.0.3+spring cloud (Finchley)3、聲明式調用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(啟動兩個實例端口為8762、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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM