Spring Cloud:服務網關 Zuul

為什么要使用微服務網關

  • 不同的微服務一般會有不同的網絡地址,而客戶端可能需要調用多個服務接口才能完成一個業務需求
  • 若讓客戶端直接與各個微服務通信,會有以下問題:
    • 客戶端會多次請求不同微服務,增加了客戶端復雜性
    • 存在跨域請求,處理相對復雜
    • 認證復雜,每個服務都需要獨立認證
    • 難以重構,多個服務可能將會合并成一個或拆分成多個


      image
  • 微服務網關介于服務端與客戶端的中間層,所有外部服務請求都會先經過微服務網關
  • 客戶只能跟微服務網關進行交互,無需調用特定微服務接口,使得開發得到簡化
  • 微服務網關還具備以下優點:
    • 易于監控,微服務網關可收集監控數據并進行分析
    • 易于認證,可在微服務網關上進行認證,然后在將請求轉發給微服務,無須每個微服務都進行認證
    • 減少客戶端與微服務之間的交互次數


      image

Zuul簡介

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

若要使用 RestClient 可設置

ribbon.restclient.enabled = true

若要使用 okhttp3.OKHttpClient 可設置

ribbon.okhttp.enabled = true

編寫Zuul微服務網關

創建新項目,添加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 中的微服務,同時整合了 Hystrix,實現了容錯

編寫配置文件

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

測試

Zuul 路由端點

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

路由配置

  • 默認 Zuul 會代理所有注冊到 EurekaServer 的微服務,若只想代理部分服務,就需要對 URL 進行精確的控制

指定微服務訪問路徑,配置 zuul.routes.微服務的 serviceId = 指定路徑,當訪問 /user/** 時會轉發到微服務 flim-user

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

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

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

忽略所有微服務,只路由指定微服務

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

指定微服務的 serviceId 和對應路徑

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

指定 path 和 URL,但會破壞 Ribbon 負載均衡和 HystrixCommand 執行

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 使用正則表達式匹配路由

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

路由前綴

#將 /api/flim-user/1 的請求轉發到 flim-user 的 /api/1
zuul:
    prefix: /api
    strip-prefix: false
    routes:
        flim-user: /user/**
#將 /user/1 請求轉發到 flim-user 的 /user/1
zuul:
    routes:
        flim-user:
            path: /user/**
            strip-prefix: false

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

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

Zuul 安全與 Header

敏感的 Header 設置

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

忽略 Header

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

使用 Zuul 上傳文件

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

編寫文件上傳微服務

創建新項目 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 配置文件中添加以下內容,然后重啟微服務,可正常上傳
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
    ConnectTimeout: 3000
    ReadTimeout: 60000

Zuul 過濾器

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


      image

編寫 Zuul 過濾器

創建過濾器類,繼承抽象類 ZuulFilter 并實現抽象方法

/**
 * @description: Zuul日志過濾器
 * filterType:返回過濾器類型,有 pre、route、post、error 等幾種取值
 * filterOrder:返回一個 int 值指定過濾器執行順序,不同過濾器允許返回相同的數字
 * shouldFilter:true 表示過濾器執行、false表示不執行
 * 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 默認為 Zuul 啟用了一些過濾器,如 DebugFilter、FormBodyWrapperFilter、PreDecorationFilter 等,這些過濾器存放在 spring-cloud-netflix-core 這個 Jar 包的 org.springframwork.cloud.netflix.zuul.filters 包中
  • 只需配置 zuul.<SimpleClassName>.<filterType>.disable = true 即可禁用 SimpleClassName 對應的過濾器,例如
zuul.SendResponseFilter.post.disable = true

為 Zuul 添加回退

  • Zuul 的 Hystrix 監控粒度是微服務,而不是某個 API
  • 要為 Zuul 添加回退,需要實現 ZuulFallbackProvider 接口,在實現類中指定為哪個微服務提供回退,并提供一個 ClientHttpResponse 作為回退響應

編寫 Zuul 回退類

@Component
public class UserFallbackProvider implements ZuulFallbackProvider {

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

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

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

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

            @Override
            public void close() {

            }

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

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

測試

用戶微服務不可用

Zuul 高可用

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

修改 ServiceId 與路由映射規則

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

---- 待完善 ----

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,957評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,248評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,081評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,485評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,720評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,263評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,025評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,204評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,461評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,945評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,205評論 2 375

推薦閱讀更多精彩內容