Spring Cloud:服務(wù)網(wǎng)關(guān) Zuul

為什么要使用微服務(wù)網(wǎng)關(guān)

  • 不同的微服務(wù)一般會有不同的網(wǎng)絡(luò)地址,而客戶端可能需要調(diào)用多個服務(wù)接口才能完成一個業(yè)務(wù)需求
  • 若讓客戶端直接與各個微服務(wù)通信,會有以下問題:
    • 客戶端會多次請求不同微服務(wù),增加了客戶端復(fù)雜性
    • 存在跨域請求,處理相對復(fù)雜
    • 認(rèn)證復(fù)雜,每個服務(wù)都需要獨(dú)立認(rèn)證
    • 難以重構(gòu),多個服務(wù)可能將會合并成一個或拆分成多個


      image
  • 微服務(wù)網(wǎng)關(guān)介于服務(wù)端與客戶端的中間層,所有外部服務(wù)請求都會先經(jīng)過微服務(wù)網(wǎng)關(guān)
  • 客戶只能跟微服務(wù)網(wǎng)關(guān)進(jìn)行交互,無需調(diào)用特定微服務(wù)接口,使得開發(fā)得到簡化
  • 微服務(wù)網(wǎng)關(guān)還具備以下優(yōu)點(diǎn):
    • 易于監(jiān)控,微服務(wù)網(wǎng)關(guān)可收集監(jiān)控?cái)?shù)據(jù)并進(jìn)行分析
    • 易于認(rèn)證,可在微服務(wù)網(wǎng)關(guān)上進(jìn)行認(rèn)證,然后在將請求轉(zhuǎn)發(fā)給微服務(wù),無須每個微服務(wù)都進(jìn)行認(rèn)證
    • 減少客戶端與微服務(wù)之間的交互次數(shù)


      image

Zuul簡介

  • Zuul 是開源的微服務(wù)網(wǎng)關(guān),可與 Eureka、Ribbon、Hystrix 等組件配合使用,Zuul 它的核心是一系列過濾器,這些過濾器可完成下面功能:
    • 身份認(rèn)證與安全:識別每個資源的驗(yàn)證要求,并拒絕那些要求不符合的請求
    • 審核與監(jiān)控:在邊緣位置追蹤有意義的數(shù)據(jù)和統(tǒng)計(jì)結(jié)果,從而帶來精確的生產(chǎn)視圖
    • 動態(tài)路由:動態(tài)的將請求路由到不同的后端集群
    • 壓力測試:逐漸增加指向集群的流量,以了解性能
    • 負(fù)載分配:為每一種負(fù)載類型分配對應(yīng)容量,并棄用超出限定值的請求
    • 靜態(tài)響應(yīng)處理:在邊緣位置直接建立部分響應(yīng),從而避免轉(zhuǎn)發(fā)到內(nèi)部集群
    • 多區(qū)域彈性:跨越 AWS Region 進(jìn)行請求路由,實(shí)現(xiàn) ELB 使用多樣化,讓系統(tǒng)邊緣更貼近使用者
  • Spring Cloud 對 Zuul 進(jìn)行了整合和增強(qiáng),Zuul 默認(rèn)使用的 HTTP 客戶端是 Apache Http Client,也可使用 RestClient 或 okHttpClient

若要使用 RestClient 可設(shè)置

ribbon.restclient.enabled = true

若要使用 okhttp3.OKHttpClient 可設(shè)置

ribbon.okhttp.enabled = true

編寫Zuul微服務(wù)網(wǎng)關(guān)

創(chuàng)建新項(xiàng)目,添加zuul依賴庫

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

在啟動類添加注解 @EnableZuulProxy,聲明一個 Zuul 代理,該代理用 Ribbon 來定位 Eureka Server 中的微服務(wù),同時(shí)整合了 Hystrix,實(shí)現(xiàn)了容錯

編寫配置文件

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

測試

  • 啟動 EurekaServer
  • 啟動 flim-user(spring.application.name 為 flim-user) 和 flim-consumer(spring.application.name 為 flim-consumer)
  • 啟動 zuul,訪問 http://localhost:8040/flim-consumer/user/1 請求會被轉(zhuǎn)發(fā)到 http://localhost:8010/user/1
  • 默認(rèn)情況下 Zuul 會代理所有注冊到 EurekaServer 的微服務(wù),并且 Zuul 路由規(guī)則如下 http://ZUUL_HOST:ZUUL_PORT/微服務(wù)在Eureka上的serviceId/ 會被轉(zhuǎn)發(fā)到 serviceId 對應(yīng)的微服務(wù)**

Zuul 路由端點(diǎn)

  • 當(dāng) @EnableZuulProxy 與 Spring Boot Actuator 配合使用時(shí),Zuul 會暴露一個路由管理端點(diǎn) /routes 借助該斷點(diǎn)可以方便的、直觀地查看以及管理 Zuul 路由
  • 通過 GET 方式訪問該端點(diǎn)即可返回路由列表,通過 POST 方式訪問該端點(diǎn)就會強(qiáng)制刷新 Zuul 當(dāng)前映射的路由列表

路由配置

  • 默認(rèn) Zuul 會代理所有注冊到 EurekaServer 的微服務(wù),若只想代理部分服務(wù),就需要對 URL 進(jìn)行精確的控制

指定微服務(wù)訪問路徑,配置 zuul.routes.微服務(wù)的 serviceId = 指定路徑,當(dāng)訪問 /user/** 時(shí)會轉(zhuǎn)發(fā)到微服務(wù) flim-user

zuul:
    routes:
        flim-user: /user/**

忽略指定微服務(wù),通過 zuul.ignored-services 配置需要忽略的微服務(wù),多個微服務(wù)通過逗號隔開

zuul:
    ignored-services: flim-user,flim-consumer

忽略所有微服務(wù),只路由指定微服務(wù)

zuul:
    ignored-services:'*'  #使用'*'忽略所有微服務(wù)
    routes:
        flim-user: /user/**

指定微服務(wù)的 serviceId 和對應(yīng)路徑

zuul:
    routes:
        user-route:     #該配置方式中,user-route 只是給路由起的一個名稱,可以任意起名
            service-id: provider-flim-user
            path: /user/**

指定 path 和 URL,但會破壞 Ribbon 負(fù)載均衡和 HystrixCommand 執(zhí)行

zuul:
    routes:
        user-route:     #該配置方式中,user-route 只是給路由起的一個名稱,可以任意起名
            url: http://localhost:8000/
            path: /user/**

指定 path 和 URL,不會破壞 Ribbon、Hystrix 特性

zuul:
    routes:
        user-route:     #該配置方式中,user-route 只是給路由起的一個名稱,可以任意起名
            service-id: flim-user
            path: /user/**
ribbon:
    eureka:
        enabled: false  #為Ribbon禁用Eureka
flim-user:
    ribbon:
        listOfServers: localhost:8000,localhost:8001

借助 PatternServiceRouteMapper 使用正則表達(dá)式匹配路由

@Bean 
public PatternServiceRouteMapper serviceRouteMapper(){
    // 調(diào)用構(gòu)造函數(shù) PatternServiceRouteMapper(String servicePattern,String routePattern)
    // servicePattern 指定微服務(wù)的正則
    // routePattern 指定路由正則
    return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)","${version}/${name}");
}

路由前綴

#將 /api/flim-user/1 的請求轉(zhuǎn)發(fā)到 flim-user 的 /api/1
zuul:
    prefix: /api
    strip-prefix: false
    routes:
        flim-user: /user/**
#將 /user/1 請求轉(zhuǎn)發(fā)到 flim-user 的 /user/1
zuul:
    routes:
        flim-user:
            path: /user/**
            strip-prefix: false

忽略某些路徑,通過正則匹配

zuul:
    ignoredPatterns:/**/admin/**  #忽略所有包含 /admin/ 的路徑
    routes:
        flim-user: /user/**

Zuul 安全與 Header

敏感的 Header 設(shè)置

  • 一般來說,同一個系統(tǒng)中的服務(wù)之間共享 Header,不過應(yīng)該防止一些敏感的 Header 外泄,在很多場景下,需要通過路由指定一系列敏感 Header 列表
zuul:
    routes:
        flim-user:
            ptah: /user/**
            sensitive-headers: Cookie,Set-Cookie,Authorization
            url: https://downstream
  • 這樣就可以為 flim-user 微服務(wù)指定敏感 Header 了,也可用 zuul.sensitive-headers 全局指定敏感 Header
zuul:
    sensitive-headers: Cookie,Set-Cookie,Authorization

忽略 Header

  • 可用 zuul.ignoredHeaders 屬性丟棄一些 Header,這樣設(shè)置后 Header1、Header2 將不會傳播到其它微服務(wù)中
zuul:
    ignored-headers: Header1,Header2

使用 Zuul 上傳文件

  • 對于小文件(1M以內(nèi))上傳無需任何處理,對于大文件(10M以上)上傳,需要為上傳路徑添加 /zuul 前綴,也可以使用 zuul.servlet-path 自定義前綴
  • 假設(shè) zuul.routes.file-upload = /file-upload/**,如果 http://{HOST}:{PORT}/uoload 是微服務(wù) file-upload 上傳路徑,則可以使用 Zuul 的 /zuul/file-upload/upload 路徑上傳大文件
  • 如果 Zuul 使用 Ribbon 做負(fù)載均衡,那么對于超大文件(例如500M)需要提升超時(shí)時(shí)間
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
    ConnectTimeout: 3000
    ReadTimeout: 60000

編寫文件上傳微服務(wù)

創(chuàng)建新項(xiàng)目 file-upload,并添加依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

在啟動類上添加 @EnableEurekaClient
編寫 Controller

@RestController
public class FileUploadController {
    @PostMapping(name = "/upload")
    public String handleFileUpLoad(@RequestParam(value = "file") MultipartFile file) throws IOException {
        byte[] bytes = file.getBytes();
        File fileToSave = new File(file.getOriginalFilename());
        FileCopyUtils.copy(bytes, fileToSave);
        return fileToSave.getAbsolutePath();
    }
}

編寫配置文件

server:
  port: 8050
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
    instance:
      prefer-ip-address: true
spring:
  application:
    name: file-upload
  http:
    multipart:
      max-file-size: 2000Mb
      max-request-size: 2500Mb

測試

There was an unexpected error (type=Internal Server Error, status=500).
Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (155106628) exceeds the configured maximum (10485760)
There was an unexpected error (type=Internal Server Error, status=500).
TIMEOUT
  • 需要在 Zuul 配置文件中添加以下內(nèi)容,然后重啟微服務(wù),可正常上傳
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
    ConnectTimeout: 3000
    ReadTimeout: 60000

Zuul 過濾器

  • Zuul 大部分功能都是通過過濾器實(shí)現(xiàn)的,Zuul 中定義了 4 種標(biāo)準(zhǔn)過濾器類型,對應(yīng)了典型的生命周期
    • PRE:在請求被路由之前調(diào)用,可用于身份驗(yàn)證、集群中選擇請求的微服務(wù)、記錄調(diào)試信息等
    • ROUTING:請求路由到微服務(wù)時(shí)執(zhí)行,用于構(gòu)建發(fā)送給微服務(wù)的請求,使用 HttpClient 或 Ribbon 請求微服務(wù)
    • POST:在路由到微服務(wù)后執(zhí)行,可用來為響應(yīng)添加 Header,收集統(tǒng)計(jì)信息和指標(biāo)、將相應(yīng)從微服務(wù)發(fā)送給客戶端等
    • ERROR:在其他階段發(fā)生錯誤時(shí)執(zhí)行該過濾器
    • Zuul 還允許創(chuàng)建自定義過濾器類型,例如定制一種 STATIC 類型過濾器,直接在 Zuul 中生存響應(yīng),而不將請求轉(zhuǎn)發(fā)到后端的微服務(wù)


      image

編寫 Zuul 過濾器

創(chuàng)建過濾器類,繼承抽象類 ZuulFilter 并實(shí)現(xiàn)抽象方法

/**
 * @description: Zuul日志過濾器
 * filterType:返回過濾器類型,有 pre、route、post、error 等幾種取值
 * filterOrder:返回一個 int 值指定過濾器執(zhí)行順序,不同過濾器允許返回相同的數(shù)字
 * shouldFilter:true 表示過濾器執(zhí)行、false表示不執(zhí)行
 * run:過濾器具體邏輯,下面代碼打印了請求方法和URL
 */
public class PreRequestLogFilter extends ZuulFilter{

    private static final Logger LOGGER = LoggerFactory.getLogger(PreRequestLogFilter.class);

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        PreRequestLogFilter.LOGGER.info(String.format("send %s request to %s",request.getMethod(),request.getRequestURL().toString()));
        return null;
    }
}

將過濾器通過 @Bean 注入到 IOC 容器

@Bean
public PreRequestLogFilter preRequestLogFilter() {
    return new PreRequestLogFilter();
}

測試

c.l.zuul.filter.PreRequestLogFilter      : send GET request to http://localhost:8040/flim-user/1

禁用 Zuul 過濾器

  • Spring Cloud 默認(rèn)為 Zuul 啟用了一些過濾器,如 DebugFilter、FormBodyWrapperFilter、PreDecorationFilter 等,這些過濾器存放在 spring-cloud-netflix-core 這個 Jar 包的 org.springframwork.cloud.netflix.zuul.filters 包中
  • 只需配置 zuul.<SimpleClassName>.<filterType>.disable = true 即可禁用 SimpleClassName 對應(yīng)的過濾器,例如
zuul.SendResponseFilter.post.disable = true

為 Zuul 添加回退

  • Zuul 的 Hystrix 監(jiān)控粒度是微服務(wù),而不是某個 API
  • 要為 Zuul 添加回退,需要實(shí)現(xiàn) ZuulFallbackProvider 接口,在實(shí)現(xiàn)類中指定為哪個微服務(wù)提供回退,并提供一個 ClientHttpResponse 作為回退響應(yīng)

編寫 Zuul 回退類

@Component
public class UserFallbackProvider implements ZuulFallbackProvider {

    @Override
    public String getRoute() {
        //表明為哪個微服務(wù)提供回退
        return "flim-user";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                //回退時(shí)的狀態(tài)碼
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                //數(shù)字類型的狀態(tài)碼,本文返回的是200
                return this.getStatusCode().value();
            }

            @Override
            public String getStatusText() throws IOException {
                //狀態(tài)文本,本文返回的是OK
                return this.getStatusCode().getReasonPhrase();
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                //響應(yīng)體
                return new ByteArrayInputStream("用戶微服務(wù)不可用".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                //headers設(shè)定
                HttpHeaders httpHeaders = new HttpHeaders();
                MediaType mt = new MediaType("application","json", Charset.forName("UTF-8"));
                httpHeaders.setContentType(mt);
                return httpHeaders;
            }
        };
    }
}

測試

用戶微服務(wù)不可用

Zuul 高可用

  • 將多個 Zuul 客戶端也注冊到 Eureka Server 上,就可以實(shí)現(xiàn) Zuul 高可用
  • 部署多個 Zuul,Zuul 客戶端會自動從 Eureka Server 中查詢 Zuul Server 列表,并使用 Ribbon 負(fù)載均衡地請求 Zuul 集群
  • 如果 Zuul 客戶端未注冊到 Eureka Server 上,可借助 Nginx 等實(shí)現(xiàn)負(fù)載均衡

修改 ServiceId 與路由映射規(guī)則

  • 可以自定義 serviceId 和路由之間的相互映射,通過正則表達(dá)式進(jìn)行匹配
  • 例如將 serviceId 為 users-v1 的服務(wù)映射到路由 /v1/users/ 的路徑上,當(dāng)請求 /v1/users/ 時(shí)等同于請求 /users-v1/
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
    return new PatternServiceRouteMapper(
        "(?<name>^.+)-(?<version>.+$)",
        "${version}/${name}");
}

---- 待完善 ----

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容