【Spring Cloud 筆記和總結】七、使用Zuul構建微服務網關

一、簡單微服務網關搭建

maven依賴

<?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>
    <parent>
        <groupId>com.lxt</groupId>
        <artifactId>springcloud</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <groupId>com.lxt.gateaway</groupId>
    <artifactId>zuul</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>zuul</name>
    <description>Demo project for Spring Boot</description>
    <dependencies>
        <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>
    </dependencies>

</project>

配置文件

spring:
  application:
    name: gateway-zuul
server:
  port: 8006
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8000/eureka/   #註冊中心eurka地址

啓動類添加@EnableZuulProxy

package com.lxt.gateaway.zuul;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {

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


Spring Cloud Zuul默認配置說明

  • 默認情況下,Zuul會代理所有註冊到Eureka Server的微服務,並且Zuul的路由規則如下:http://ZUUL_HOST:ZUUL_PORT/微服務在Eureka上的serviceId/**會被轉發到serviceId對應的微服務
  • eg:http://localhost:8006/spring-cloud-consumer/hello/lxt =>http://spring-cloud-consumer/hello/lxt=>http://localhost:9001/hello/lxt
  • 也可手動配置指定
    • 自定義微服務訪問路徑
      zuul:
        routes:
          spring-cloud-consumer-hystrix: /test/**
      
    • 忽略指定微服務
      zuul:
        ignored-services: spring-cloud-consumer-hystrix,spring-cloud-consumer
      
    • 同時指定微服務ServiceId和對應路徑
      zuul:
        routes:
          config-client:
            path: /lxt/**
            serviceId:  spring-cloud-consumer-hystrix
      
    • 等等…

啓動測試

  • 分別啓動exureka-server、service-consumer、service-provider和zuul
  • 瀏覽器輸入http://localhost:8006/spring-cloud-consumer/hello/lxt
  • 返回hello lxt,this is first messge,測試成功

二、Zuul路由端點

  • 由於 endpoints 中會包含很多敏感信息,除了 health 和 info 兩個支持 web 訪問外,其他的默認不支持 web 訪問,需手動添加配置暴露routes路由端點
management:
  endpoints:
    web:
      exposure:
        # 2.x手動開啓  這個是用來暴露 endpoints 的。由於 endpoints 中會包含很多敏感信息,除了 health 和 info 兩個支持 web 訪問外,其他的默認不支持 web 訪問
        include: routes
  • 瀏覽器http://localhost:8006/actuator/routes,返回如下
// 20191121213009
// http://localhost:8006/actuator/routes

{
  "/spring-cloud-consumer/**": "spring-cloud-consumer",
  "/spring-cloud-provider/**": "spring-cloud-provider"
}

三、Zuul中的Filter使用

Zuul中默認實現的Filter
在這裏插入圖片描述

  • PRE: 這種過濾器在請求被路由之前調用。我們可利用這種過濾器實現身份驗證、在集羣中選擇請求的微服務、記錄調試信息等。
  • ROUTING:這種過濾器將請求路由到微服務。這種過濾器用於構建發送給微服務的請求,並使用Apache HttpClient或Netfilx Ribbon請求微服務。
  • POST:這種過濾器在路由到微服務以後執行。這種過濾器可用來爲響應添加標準的HTTP Header、收集統計信息和指標、將響應從微服務發送給客戶端等。
  • ERROR:在其他階段發生錯誤時執行該過濾器。 除了默認的過濾器類型,Zuul還允許我們創建自定義的過濾器類型。例如,我們可以定製一種STATIC類型的過濾器,直接在Zuul中生成響應,而不將請求轉發到後端的微服務。

自定義Filter示例

必須包含token的參數纔可訪問,否則直接返回,如下:

package com.lxt.gateaway.zuul;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;

public class TokenFilter extends ZuulFilter {

    private final Logger logger = LoggerFactory.getLogger(TokenFilter.class);

    @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 ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        logger.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString());

        String token = request.getParameter("token");// 獲取請求的參數

        if (StringUtils.isNotBlank(token)) {
            ctx.setSendZuulResponse(true); //對請求進行路由
            ctx.setResponseStatusCode(200);
            ctx.set("isSuccess", true);
            return null;
        } else {
            ctx.setSendZuulResponse(false); //不對其進行路由
            ctx.setResponseStatusCode(400);
            ctx.setResponseBody("token is empty");
            ctx.set("isSuccess", false);
            return null;
        }
    }
}

註冊Bean


    @Bean
    public TokenFilter tokenFilter() {
        return new TokenFilter();
    }

運行測試

  • 瀏覽器輸入http://localhost:8006/spring-cloud-consumer/hello/lxt,返回token is empty
  • 瀏覽器輸入http://localhost:8006/spring-cloud-consumer/hello/lxt?token=2,返回hello lxt,this is first messge

四、Zuul中的路由熔斷和重試

路由熔斷

實現FallbackProvider接口,重寫fallbackResponse方法

package com.lxt.gateaway.zuul;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

public class ProducerFallback implements FallbackProvider{
    private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class);

    //指定要處理的 service。
    @Override
    public String getRoute() {
        return "spring-cloud-provider";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        if (cause != null && cause.getCause() != null) {
            String reason = cause.getCause().getMessage();
            logger.info("Excption {}",reason);
        }
        return fallbackResponse();
    }

    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("The service is unavailable.".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

註冊bean

    @Bean
    public ProducerFallback producerFallback() {
        return new ProducerFallback();
    }

運行測試

  • 再重啓網關zuul,啓動務提供者service-provider1
  • 瀏覽多次器輸入http://localhost:8006/spring-cloud-provider/foo?foo=lxt&token=2
  • 交替返回hello lxt,this is first messgehello lxt,this is two messge
  • 關閉第二個服務提供者,繼續刷新瀏覽器
  • 交替返回hello lxt,this is first messgeThe service is unavailable.
路由重試

添加依賴

<dependency>
	<groupId>org.springframework.retry</groupId>
	<artifactId>spring-retry</artifactId>
</dependency>

修改配置文件

zuul:
  retryable: true #是否開啓重試功能
ribbon:
  MaxAutoRetries: 2 #對當前服務的重試次數
  MaxAutoRetriesNextServer: 0 #切換相同Server的次數

修改service-provider1的hello方法如下

    @RequestMapping(value ="/hello", method = RequestMethod.GET)
    public String index(String name) {
        logger.info("request two name is "+name);
        try{
            Thread.sleep(1000000);
        }catch ( Exception e){
            logger.error(" hello two error",e);
        }
        return "hello "+name+",this is two messge";
    }

運行測試

  • 重啓網關zuulservice-provider1
  • 瀏覽器輸入http://localhost:8006/spring-cloud-provider/foo?foo=lxt&token=2
  • 返回The service is unavailable.時,查看控制如下:
2019-11-21 22:38:31.049  INFO 11240 --- [nio-9002-exec-3] c.l.s.controller.HelloController         : request two name is lxt
2019-11-21 22:38:32.054  INFO 11240 --- [nio-9002-exec-4] c.l.s.controller.HelloController         : request two name is lxt
2019-11-21 22:38:33.060  INFO 11240 --- [nio-9002-exec-5] c.l.s.controller.HelloController         : request two name is lxt
  • 打印了三次,重試了兩次。

五、相關

  • 父模塊介紹傳送門
  • 源碼地址傳送門
  • 參考
    • http://www.ityouknow.com/spring-cloud.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章