1. 程式人生 > >八、Zuul構建微服務閘道器

八、Zuul構建微服務閘道器

1、為什麼要使用微服務閘道器

    1.1、沒有閘道器的話,存在的問題

  •         客戶端要多次請求不同微服務,增加客戶端複雜性
  •         存在跨域請求問題
  •         認證複雜,每個專案都要單獨一套認證
  •         難以重構,如果微服務做修改,所有呼叫的客戶端都要改一遍
  •         某些微服務使用防火牆/不友好協議,直接訪問比較困難

    使用微服務閘道器就可以解決以上問題

    1.2、使用微服務閘道器的有點

  •         易於監控
  •         易於認證,不需要每個微服務都進行認證
  •         減少客戶端與微服務端互動次數

2、Zuul簡介

    Zuul是微服務閘道器外掛,本身已經集成了Ribbon和Hystrix,並且可以和Eureka配合使用。Zuul的核心是一系列過濾器,這些過濾器可以完成以下功能:

  •         身份認證與安全
  •         審查和監控
  •         負載分配和動態路由:動態分配到不同的後端叢集
  •         靜態響應處理

3、編寫Zuul微服務閘道器

    3.1、新增依賴

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

    3.2、啟動類添加註解@EnableZuulProxy

        宣告Zuul代理,已經整合Ribbon定位微服務,整合Hystrix實現容錯

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

    3.3、編寫配置檔案application.yml

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

4、Zuul的路由端點

    @EnableZuulProxy與SpringBootActuator配合使用時,Zuul會暴露一個路由管理端點/routes。直觀地檢視及管理Zuul的路由。(zuul裡面已經整合了actuator,所以不需要再增加依賴)

    訪問http://localhost:8040/routes,即可獲得如下結果

{
    "/microservice-provider-user/**" : "microservice-provider-user",
    "/microservice-consumer-movie/**": "microservice-consumer-movie"
}

5、路徑配置詳解

    5.1、自定義指定微服務的訪問路徑

zuul: 
    routes: 
        microservice-provider-user: /user/**

    只要是/user/**的請求都會自動在microservice-provider-user對應的服務中請求

    5.2、忽略指定微服務

zuul: 
    ignored-services: microservice-provider-user,microservice-consumer-movie


#zuul: 
#    ignored-services: '*'        #忽略所有,只路由指定微服務
#    routes: 
#        microservice-provider-user: /user/**

5.3、同時指定微服務的serviceId和對應路徑

zuul: 
    routes: 
        user-route:             #任意起的名字,作為該配置的標識
            service-id: provider-microservice-user
            path: /user/**

    本例配置效果同5.1

5.4、同時指定path和URL

zuul: 
    routes: 
        user-route:    #任意起的名字,作為該配置的標識
            url: http://localhost:8000/
            path: /user/**

6、Zuul的安全與Header

7、Zuul上傳檔案

8、Zuul的過濾器

    過濾器是Zuul的核心元件

    8.1、過濾器的型別與請求生命週期

  •     pre:這種過濾器在請求路由之前被呼叫,可做身份驗證
  •     routing:將請求路由到微服務,用於構建傳送給微服務的請求,使用HttpClient/Ribbon
  •     post:在路由到微服務以後執行,可為響應新增指定內容
  •     error:在其他階段發生錯誤時執行該過濾器

 

過濾器型別

    8.2、編寫Zuul過濾器

        8.2.1、過濾器類程式碼

public class PreRequestLogFilter extends ZuulFilter {
  private static final Logger LOGGER = LoggerFactory.getLogger(PreRequestLogFilter.class);
  private static int n = 1;

  @Override
  public String filterType() {
    return FilterConstants.PRE_TYPE;  //過濾器型別
  }

  @Override
  public int filterOrder() {
    return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;//過濾器執行順序
  }

  @Override
  public boolean shouldFilter() {
    return true;                    //過濾器是否執行
  }

  @Override
  public Object run() {        //過濾器業務邏輯,該例列印了請求的HTTP方法及請求地址
    RequestContext ctx = RequestContext.getCurrentContext();
    HttpServletRequest request = ctx.getRequest();
    PreRequestLogFilter.LOGGER.info(String.format("send %s request to %s", request.getMethod(), request.getRequestURL().toString()));
    PreRequestLogFilter.LOGGER.info("——————————————————————攔截到第"+(n++)+"次請求!");
    return null;
  }
}
  •         filterType:過濾器型別——pre、route、post、error
  •         filterOrder:返回int值來指定過濾器的執行順序,不同過濾器可相同數字
  •         shouldFilter:返回boolean確定該過濾器是否執行
  •         run:過濾器具體的業務邏輯

        8.2.2、啟動類新增

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

  @Bean
  public PreRequestLogFilter preRequestLogFilter() {
    return new PreRequestLogFilter();
  }
}

    8.3、禁用過濾器

        zuul.<SimpleClassName>.<filterType>.disable=true

        例如:zuul.PreRequestLogFilter.pre.disable=true

9、Zuul的容錯與回退

    Zuul中已經預設整合了Hystrix

    9.1、編寫Zuul的回退類

@Component
public class UserFallbackProvider implements ZuulFallbackProvider {
  @Override
  public String getRoute() {
    // 表明是為哪個微服務提供回退
    return "microservice-provider-user";
  }

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

      @Override
      public int getRawStatusCode() throws IOException {
        // 數字型別的狀態碼,本例返回的其實就是200,詳見HttpStatus
        return this.getStatusCode().value();
      }

      @Override
      public String getStatusText() throws IOException {
        // 狀態文字,本例返回的其實就是OK,詳見HttpStatus
        return this.getStatusCode().getReasonPhrase();
      }

      @Override
      public void close() {
      }

      @Override
      public InputStream getBody() throws IOException {
        // 響應體
        return new ByteArrayInputStream("使用者微服務不可用,請稍後再試。".getBytes());
      }

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

        return headers;
      }
    };
  }
}

之後,當microservice-provider-user微服務無法正常響應時,將返回——“使用者微服務不可用,請稍後再試”

10、Zuul的高可用

    啟動多個Zuul的閘道器服務,均註冊到EurekaServer中即可。然後前置nginx將請求分發到各個閘道器即可!

11、使用Sidecar整合非JVM微服務

12、使用zuul聚合微服務

    如果客戶端需要訪問多個微服務,如果通過閘道器分別呼叫、分別返回的話,網路開銷、耗時、流量消耗都會很不好。這時我們可以在Zuul整合客戶端的請求,客戶端只給Zuul閘道器傳送一個請求,閘道器中分別請求各個微服務,然後給客戶端返回一個最終結果!

    12.1、啟動類程式碼

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

  @Bean
  @LoadBalanced
  public RestTemplate restTemplate() {
    return new RestTemplate();
  }
}

    12.2、Service層程式碼

@Service
public class AggregationService {
  @Autowired
  private RestTemplate restTemplate;

  @HystrixCommand(fallbackMethod = "fallback")
  public Observable<User> getUserById(Long id) {
    // 建立一個被觀察者
    return Observable.create(observer -> {
      // 請求使用者微服務的/{id}端點
      User user = restTemplate.getForObject("http://microservice-provider-user/{id}", User.class, id);
      observer.onNext(user);
      observer.onCompleted();
    });
  }

  @HystrixCommand(fallbackMethod = "fallback")
  public Observable<User> getMovieUserByUserId(Long id) {
    return Observable.create(observer -> {
      // 請求電影微服務的/user/{id}端點
      User movieUser = restTemplate.getForObject("http://microservice-consumer-movie/user/{id}", User.class, id);
      observer.onNext(movieUser);
      observer.onCompleted();
    });
  }

  public User fallback(Long id) {
    User user = new User();
    user.setId(-1L);
    return user;
  }
}

    12.3、Controller層程式碼

@RestController
public class AggregationController {
  public static final Logger LOGGER = LoggerFactory.getLogger(ZuulApplication.class);

  @Autowired
  private AggregationService aggregationService;

  @GetMapping("/aggregate/{id}")
  public DeferredResult<HashMap<String, User>> aggregate(@PathVariable Long id) {
    Observable<HashMap<String, User>> result = this.aggregateObservable(id);
    return this.toDeferredResult(result);
  }

  public Observable<HashMap<String, User>> aggregateObservable(Long id) {
    // 合併兩個或者多個Observables發射出的資料項,根據指定的函式變換它們
    return Observable.zip(
            this.aggregationService.getUserById(id),
            this.aggregationService.getMovieUserByUserId(id),
            (user, movieUser) -> {
              HashMap<String, User> map = Maps.newHashMap();
              map.put("user", user);
              map.put("movieUser", movieUser);
              return map;
            }
    );
  }

  public DeferredResult<HashMap<String, User>> toDeferredResult(Observable<HashMap<String, User>> details) {
    DeferredResult<HashMap<String, User>> result = new DeferredResult<>();
    // 訂閱
    details.subscribe(new Observer<HashMap<String, User>>() {
      @Override
      public void onCompleted() {
        LOGGER.info("完成...");
      }

      @Override
      public void onError(Throwable throwable) {
        LOGGER.error("發生錯誤...", throwable);
      }

      @Override
      public void onNext(HashMap<String, User> movieDetails) {
        result.setResult(movieDetails);
      }
    });
    return result;
  }
}