1. 程式人生 > >springcloud 路由閘道器 - zuul

springcloud 路由閘道器 - zuul

spring cloud微服務系統中,一種常見的負載均衡方式是,客戶端的請求首先經過負載均衡(zuul、ngnix),再到達服務閘道器(zuul叢集),然後再到具體的服務。服務統一註冊到高可用的服務註冊中心叢集,服務的所有配置檔案由配置服務管理,配置服務的配置檔案放在git倉庫,方便開發人員隨時改變配置。

簡介

zuul的主要功能是路由轉發和過濾器。路由功能是微服務的一部分。

zuul有以下功能:

(1)Authentication(認證)

(2)Insights

(3)Stress Testing(壓力測試)

(4)Canary Testing(金絲雀測試)

(5)Dynamic Routing(動態路由)

(6)Service Migration(服務遷移)

(7)Load Shedding(負載排程)

(8)Security(案例)

(9)Static Response handling(靜態響應處理)

(10)Active traffic management

zuul中預設實現的filter

型別 順序 過濾器 功能
pre -3 ServletDetectionFilter 標記處理Servlet的型別
pre -2 Servlet3oWrapperFilter 包裝HttpServletRequests請求
pre -1 FormBodyWrapperFilter 包裝請求體
route 1 DebugFilter 標記除錯標誌
route 5 PreDecorationFilter 處理請求上下文供後續使用
route 10 RibbonRoutingFilter serviceId請求轉發
route 100 SimpleHostRoutingFilter url請求轉發
route 500 SendForwardFilter forward請求轉發
post 0 SendErrorFilter 處理有錯誤的請求響應
pos 1000 SendResponseFilter 處理正常的請求響應

可以在application.yml中配置需要禁用的filter,格式 :

zuul:
    FormBodyWrapperFilter:
        pre:
            disable: true

自定義Filter

實現自定義Filter,需要繼承ZuulFilter的類,並覆蓋其中的4個方法。

public class MyFilter extends ZuulFilter {
    @Override
    String filterType() {
        return "pre"; //定義filter的型別,有pre、route、post、error四種
    }

    @Override
    int filterOrder() {
        return 10; //定義filter的順序,數字越小表示順序越高,越先執行
    }

    @Override
    boolean shouldFilter() {
        return true; //表示是否需要執行該filter,true表示執行,false表示不執行
    }

    @Override
    Object run() {
        return null; //filter需要執行的具體操作
    }
}

實踐

1、在原有的工程上,建立一個的新的工程service-zuul

2、其pom.xml檔案如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.forezp</groupId>
    <artifactId>service-zuul</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>service-zuul</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>com.forezp</groupId>
        <artifactId>sc-f-chapter5</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
    </dependencies>
</project>

3、在其入口application類加上註解@EnableZuulProxy,開啟zuul的功能

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
@EnableDiscoveryClient
public class ServiceZuulApplication {

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

4、加上配置檔案application.yml加上以下配置程式碼

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8769
spring:
  application:
    name: service-zuul
zuul:
  routes:
    api-a:
      path: /api-a/**
      serviceId: service-ribbon
    api-b:
      path: /api-b/**
      serviceId: service-feign

服務過濾

zuul不僅只是路由,並且還能過濾,做一些安全驗證。

@Component
public class MyFilter extends ZuulFilter {

    private static Logger log = LoggerFactory.getLogger(MyFilter.class);
    @Override
    public String filterType() {
        return "pre";
    }

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

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

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
        Object accessToken = request.getParameter("token");
        if(accessToken == null) {
            log.warn("token is empty");
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try {
                ctx.getResponse().getWriter().write("token is empty");
            }catch (Exception e){}

            return null;
        }
        log.info("ok");
        return null;
    }
}

1、fiterType: 返回一個字串代表過濾器的型別,在zuul中定義了四種不同生命週期的過濾喊器型別,具體如下:

(1)pre: 路由之前

我們可以利用這種過濾器實現身份驗證、在叢集中執行請求的微服務,記錄呼叫資訊等。

(2)routing:路由之時

(3)post:路由之後

(4)error:傳送錯誤呼叫

2、filterOrder: 過濾順序

3、shouldFilter:這裡可以寫邏輯判斷,是否要過濾。

4、run:過濾器的具體邏輯

5、訪問http://localhost:8769/api-a/hi?name=xxx

6、訪問http://localhost:8769/api-a/hi?name=xxx&token=22

zuul的高可用

因為外部請求到後端服務的流量都會經過zuul,故而在生產環境中,我們一般都需要部署高可用的zuul以避免單點故障。

1、zuul客戶端也註冊到了eureka server上

這種情況下,zuul的高可用非常簡單,只需將多個zuul節點註冊到eureka server上,就可實現zuul的高可用。此時,zuul的高可用與其他微服務的高可用沒什麼區別。

當zuul客戶端也註冊到eureka server上時,只需部署多個zuul節點即可實現其高可用。zuul客戶端會自動從eureka server中查詢zuul server的列表,並使用ribbon負載均衡的請求zuul叢集。

2、zuul客戶端未註冊到eureka server上

現實中,這種場景往往更常見,例如,zuul客戶端是一個手機app,我們不可能讓所有手機端都註冊到eureka server上,這種情況下,我們可藉助一個額外的負載均衡器來實現zuul的高可用,例如nginx、haproxy、f5等。

問題

1、跨域訪問

正常情況下,跨域是這樣的:

微服務配置跨域+zuul不配置=有跨域問題

微服務配置+zuul配置=有跨域問題

微服務不配置+zuul不配置=有跨域問題

微服務不配置+zuul配置=ok

(1)zuul配置忽略頭部資訊

zuul:
  ignored-headers: Access-Control-Allow-Origin,H-APP-Id,Token,APPToken

(2)跨域配置

@Component
@Configuration
public class GateWayCorsConfig
{
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);//允許cookie跨域
//允許訪問的頭資訊,*表示全部
        corsConfiguration.addAllowedHeader("*");
//允許向伺服器提交請求的URI,*表示全部允許,
        corsConfiguration.addAllowedOrigin("*");
//允許提交請求的方法,*表示全部允許
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setMaxAge(18000L);// 預檢請求的快取時間(秒),即在這個時間段裡,對於相同的跨域請求不會再預檢了
        source.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(source);
    }
}

只要閘道器配置好了,底下的服務不需要配置。

2、中文亂碼

解決辦法:

(1)注意編碼,全站api和前端全部使用utf-8,zuul中強制編碼為utf-8.

spring:
  http:
    encoding:
      charset: UTF-8
      enabled: true
      force: true

3、post json中獲取引數token

RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String method = request.getMethod();

        String token = null;
        if(method.equalsIgnoreCase("GET")){
            token = request.getParameter("token");
        }else if(method.equalsIgnoreCase("POST")){
            try {
                InputStream inputStream = request.getInputStream();
                String body = StreamUtils.copyToString(inputStream, Charset.forName("UTF-8"));
                JSONObject jsonObject = JSONObject.parseObject(body);
                token = jsonObject.getString("token");
            }catch (Exception e){
                e.printStackTrace();
                token = null;
            }
        }