服務網關Spring Cloud Zuul

Spring Cloud Zuul

開發環境

  • idea 2019.1.2
  • jdk1.8.0_201
  • Spring Boot 2.1.9.RELEASE
  • Spring Cloud Greenwich SR3

Zuul介紹

Zuul是Netflix開發的一款提供動態路由、監控、彈性、安全的網關服務,他可以和Eureka,Ribbon,Hystrix等組件配合使用。還可以通過創建過濾器對校驗過濾提供支持,使微服務應用更專注于業務邏輯的開發。

使用Zuul網關服務帶來的好處是統一向外系統提供REST API,并額外提供了權限控制、負載均衡等功能,并且這些功能是從原先的服務中抽離出來并單獨存在的。

Zuul提供了不同類型的filter用于處理請求,這些filter可以讓我們實現以下功能

  • 權限控制和安全性:可以識別認證需要的信息和拒絕不滿足條件的請求
  • 監控:監控請求信息
  • 動態路由:根據需要動態地路由請求到后臺的不同服務集群
  • 壓力測試:逐漸增大到集群的流量,以便進行性能評估
  • 負載均衡:為每種類型的請求分配容量并丟棄超過限額的請求
  • 限流
  • 黑白名單過濾
  • 靜態資源處理:直接在zuul處理靜態資源的響應而不需要轉發這些請求到內部集群中

過濾器

ZuulFilter是一個基礎的抽象類,定義了一些抽象方法

  • filterType方法: filter的類型,有”pre”, “route”, “post”, “error”, “static”
    • pre:在請求被路由之前執行
    • route:在請求被路由時執行
    • post:在請求被路由之后執行
    • error:在請求發生錯誤時執行
    • static:特殊的 Filter 具體的可以看 StaticResponseFilter,它允許從 Zuul 本身生成響應,而不是將請求轉發到源
  • filterOrder方法:優先級,級別越高,越快被執行(數值越小表示級別越高)

  • shouldFilter方法:開關,如果是true,run方法會執行,否則不會執行

  • run方法:filter執行的邏輯操作

代碼實現

1.創建服務注冊中心

創建 zuul-eureka-server 項目,引入eureka-server依賴,項目完整源碼可以查看:

以下貼幾段關鍵代碼

pom添加依賴

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

啟動類增加 @EnableEurekaServer 注解

@EnableEurekaServer
@SpringBootApplication
public class ZuulEurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulEurekaServerApplication.class, args);
    }
}

yml配置

server:
  port: 8761

spring:
  application:
    name: zuul-eureka-server

eureka:
  instance:
    hostname: localhost   # eureka 實例名稱
  client:
    register-with-eureka: false # 不向注冊中心注冊自己
    fetch-registry: false       # 是否檢索服務
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/  # 注冊中心訪問地址

2.創建服務提供者1

創建 zuul-server-provider 項目,引入eureka-client依賴,項目完整源碼可以查看:

以下貼幾段關鍵代碼

pom添加依賴

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

編寫HelloController服務

@RestController
@Slf4j
public class HelloController {

    @RequestMapping("/hello")
    public String index(@RequestParam String name) {
        log.info("request one  name is " + name);
        return "hello " + name + ",this is first messge";
    }
}

啟動類增加 @EnableDiscoveryClient 注解

@SpringBootApplication
@EnableDiscoveryClient
public class ZuulServerProviderApplication {

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

yml配置

spring:
  application:
    name: zuul-server-provider
server:
  port: 9000
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

3.創建服務提供者2

創建 zuul-server-provider2 項目,引入eureka-client依賴,其它同服務提供者1項目,項目完整源碼可以查看:

以下貼出差異部分代碼

編寫服務,這里為了做服務降級測試,為當前線程設置了一個超長休眠時間

@RestController
@Slf4j
public class HelloController {
    @RequestMapping("/hello")
    public String index(@RequestParam String name) {
        log.info("request two name is " + name);
        try{
            //為做服務降級測試,設置一個超長休眠時間,故意導致該服務訪問超時
            Thread.sleep(1000000);  
        }catch ( Exception e){
            log.error(" hello two error",e);
        }
        return "hello " + name + ",this is two messge";
    }
}

4.創建zuul服務網關

創建 zuul-server-gateway 項目,引入netflix-zuul及eureka-client依賴,項目完整源碼可以查看:

以下貼幾段關鍵代碼

pom.xml配置

        <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>

創建過濾器TokenFilter.java

package com.easy.zuulServerGateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import javax.servlet.http.HttpServletRequest;

@Slf4j
public class TokenFilter extends ZuulFilter {

    @Override
    public String filterType() {
        //可以在請求被路由之前調用
        return "pre";
    }

    @Override
    public int filterOrder() {
        //filter執行順序,通過數字指定 ,優先級為0,數字越大,優先級越低
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        //是否執行該過濾器,此處為true,說明需要過濾
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        log.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString());

        //獲取請求的參數
        String token = request.getParameter("token");

        if (StringUtils.isNotBlank(token)) {
            //對請求進行路由
            ctx.setSendZuulResponse(true);
            ctx.setResponseStatusCode(200);
            ctx.set("isSuccess", true);
            return null;
        } else {
            //不對其進行路由
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(400);
            ctx.setResponseBody("token is empty");
            ctx.set("isSuccess", false);
            return null;
        }
    }
}

創建 zuul-server-provider 服務對應的熔斷器(這里針對整個服務熔斷,也可以對單個服務接口做熔斷處理),ProviderFallback.java

package com.easy.zuulServerGateway.fallback;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

@Slf4j
@Component
public class ProviderFallback implements FallbackProvider {

    @Override
    public String getRoute() {
        return "zuul-server-provider";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        if (cause != null) {
            String reason =cause.getMessage();
            log.info("Excption {}", reason);
        }
        return fallbackResponse();
    }

    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() {
                return HttpStatus.OK;
            }

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

            @Override
            public String getStatusText(){
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() {
                return new ByteArrayInputStream("The service is unavailable.".getBytes());
            }

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

yml配置

spring:
  application:
    name: zuul-service-gateway
server:
  port: 8888

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

  #是否開啟重試功能
zuul:
  retryable: true
  #對當前服務的重試次數
ribbon:
  MaxAutoRetries: 2
  #切換相同Server的次數
  MaxAutoRetriesNextServer: 0

啟動類增加 @EnableZuulProxy 注解,來啟動服務網關
ZuulServerGatewayApplication.java

package com.easy.zuulServerGateway;

import com.easy.zuulServerGateway.filter.TokenFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableZuulProxy
public class ZuulServerGatewayApplication {

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

    @Bean
    public TokenFilter tokenFilter() {
        return new TokenFilter();
    }
}

至上,示例的四個服務創建完畢,接下來運行示例查看效果

使用

現有四個項目如下

zuul-eureka-server:服務注冊中心,服務名:zuul-eureka-server,端口:8761
zuul-server-provider:服務提供者1,服務名:zuul-server-provider,端口:9000
zuul-server-provider2:服務提供者,服務名:zuul-server-provider,端口:9001
zuul-server-gateway:服務網關,服務名:zuul-server-gateway,端口:8888

運行測試

分別啟動zuul-eureka-server、zuul-server-gateway、zuul-server-provider三個服務

  • 訪問地址:http://localhost:8888/zuul-server-provider/hello?name=yuntian,返回:token is empty ,請求被攔截返回。
  • 訪問地址:http://localhost:8888/zuul-server-provider/hello?name=yuntian&token=xx,返回:hello yuntian,this is first messge,說明請求正常響應。

啟動zuul-server-provider2

  • 多次訪問http://localhost:8888/zuul-server-provider/hello?name=yuntian&token=xx,此時會交替返回
hello yuntian,this is first messge
The service is unavailable
...

從返回結果可以看出:zuul-server-provider2項目已經啟用了熔斷,返回:The service is unavailable.

資料

posted @ 2019-10-14 12:26 云天 閱讀(...) 評論(...) 編輯 收藏