天天看點

SpringCloud Zuul網關實作路由和自動發現路由

我們做分布式系統,為了不暴露具體的服務,以及實作各種統一處理,常常使用網關來管理接口。SpringCloud分布式系統中常用zuul來實作網關功能。zuul最基本的功能,就是把所有的接口都收到自己這裡,按照規則和負載均衡的配置分發。

zuul實作路由最常用的方法是在屬性檔案properties或者 yml中進行配置。

我們首先建立幾個必要的服務:

1. eureka注冊中心

按照正常方式建立,無需多餘内容。application.yml配置:

server:
  port: 8101
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
spring:
  application:
    name: eureka
           

2. 兩個微服務user和 student

依賴

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

application.yml

server:
  port: 8107
spring:
  application:
    name: user
eureka:
  instance:
    hostname: studio.chris.com
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:8101/eureka
           

server:
  port: 8108
spring:
  application:
    name: student
eureka:
  instance:
    hostname: studio.chris.com
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:8101/eureka
           

并沒有什麼差别,隻是服務名不同而已。這裡的服務名設計的比較簡單,主要為友善進行服務發現路由的時候使用簡潔。要注意避免歧義,也就是說解析的時候不要出現歧義,就是一個微服務名稱不能是另一個微服務名稱頭部的子字元串,否則可能在某些情況下會導緻路由錯誤。比如stu和student就不合适。

在每個微服務下面寫個接口,如

@RestController
@RequestMapping("/api")
public class StudentController {
    @Value("${server.port}")
    String port;

    @GetMapping("/test")
    public String test() {
        return "Called test api from student module.port: " + port;
    }
}
           

我們用port做标記來差別不同的執行個體,以驗證zuul給我們做了負載均衡。

3. zuul

pom依賴

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

application.yml

server:
  port: 8080
spring:
  application:
    name: zuul
eureka:
  instance:
    hostname: studio.chris.com
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:8101/eureka
           

可以看出yml的配置基本上都是一樣的,無非差別了個端口和服務名稱。而那個端口,在我們進行多執行個體部署的時候又是需要更改的。

啟動類注解

@EnableEurekaClient
@EnableDiscoveryClient
@EnableZuulProxy
           

好了,所有服務我們已經建構好,現在需要配置zuul

zuul路由的最常見方式實在application.yml檔案中配置

添加:

zuul:
  routes:
    user-manage:
      path: /user/**
      serviceId: user
    student-manage:
      path: /student/**
      serviceId: student
           

user-manage和student-manage是兩個路由,這個名稱隻要差別開就好了。serviceId和path是配對的,比如user-manage的配置,凡是/user開頭的接口調用都會被分發到在注冊中心注冊為user的服務中去均衡調用。

如:請求localhost:8080/user/api/test,結果會出現類似以下這樣的結果

Called test api from user module.port: 8107
           

這是的zuul也算是配置完成了,我們把各個服務啟動起來測試。注冊中心和zuul啟動起來,其他兩個微服務要啟動多個執行個體,我們在Idea中打開多個終端,運作以下指令:

mvn spring-boot:run -e -Dmaven.test.skip=true -Dserver.port=8211
           

最後的端口号每次運作的時候都要修改。

我們在浏覽器中打開注冊中心的監控頁面:

http://localhost:8101/
           

可以看到我們啟動起來的微服務,其中user和student是多執行個體的。

SpringCloud Zuul網關實作路由和自動發現路由

我們通過網關8080調用接口,會發現傳回的字元串端口号在變化,這就是因為負載均衡分發到了不同的執行個體。

其實這種路由還可以這樣配置

zuul:
    user: /user/**
    student: /student/**
           

這種鍵值對的方式更簡潔,key為注冊服務名稱,value為比對的路徑表達式。

注意:這種方法最常見,但是也很笨拙,如果我們增加了一個微服務,都要在yml檔案中配置,也就要重新開機網關。但是一般我們不會這樣做,網關不要經常重新開機,配置檔案也不需要經常修改。我們把yml中關于zuul的配置全部删除幹淨。

接着,我們在pom中依賴一個包

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    <version>1.3.5.RELEASE</version>
</dependency>
           

好了,這就完了,既不用配置,也不用寫代碼,直接啟動就好了。就算新添加一個微服務,也就是照常從網關調用就好了。zuul會根據注冊中心中的發現的服務來自動進行路由。我們的zuul就再也不用關了。

最後需要補充的一個點,網關就是個關,大鬼小鬼都要走這裡。它的作用除了路由、負載均衡之外,還有統一處理。由于網關在接口方法調用之前,是以可以做一些路由前的預處理。比如,我們分布式架構中最重視權限,一般的權限驗證都是在微服務中根據自己的需求去做驗證邏輯,但是架構層面統一要求的邏輯可以在網關處理,以節省資源。比如,我們要求所有的網絡請求都要攜帶身份驗證資訊,沒有就不允許進行任何接口調用,我們就可以在zuul過濾器中來實作。

實作方式就是寫一個過濾器,來繼承ZuulFilter

package com.chris.zuul;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created by Chris Chan
 * 2020/3/14 12:52
 * Use for:
 * Explain:
 */
@Component
@WebFilter(urlPatterns = "/*")
public class ChrisZuulFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return "pre";
    }

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

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

    @Override
    public Object run() throws ZuulException {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        HttpServletResponse response = context.getResponse();

        //授權過濾 不帶身份證不讓進場 立即退出
        String token = request.getHeader("Authorization");
        if (StringUtils.isEmpty(token)) {
            try {
                response.sendError(401, "Authorization is empty");
            } catch (Exception e) {

            }
            return null;
        }

        return null;
    }
}
           

這樣我們請求的時候就一定要帶一個Basic或者Bearer類似的Authorization頭資訊,不帶的不再進行路由分發。至于這些頭資訊對不對,可以在這裡進行檢測,也可以放行到微服務中自己進行處理。

SpringCloud Zuul網關實作路由和自動發現路由