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是多實例的。

我們通過網關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就再也不用關了。

最後需要補充的一個點,網關就是個關,大鬼小鬼都要走這裏。它的作用除了路由、負載均衡之外,還有統一處理。由於網關在ji節後方法調用之前,所以可以做一些路由前的預處理。比如,我們分佈式架構中zu最重視權限,一般的權限驗證都是在微服務中根據自己的需求去做驗證邏輯,但是架構層面統一要求的邏輯可以在網關處理,以節省資源。比如,我們要求所有的網絡請求都要攜帶身份驗證信息,沒有就不允許ji進行任何接口調用,我們就可以在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頭信息,不帶的不再進行路由分發。至於這些頭信息對不對,可以在這裏進行檢測,也可以放行到微服務中自己進行處理。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章