本章節,我們講解springcloud重要組件:微服務網關Zuul。如果有同學從第一章看到本章的,會發現我們已經講解了大部分微服務常用的基本組件。
已經講解過的:
一起來學Spring Cloud | 第一章 :如何搭建一個多模塊的springcloud項目
一起來學Spring Cloud | 第二章:服務注冊和發現組件 (Eureka)
一起來學Spring Cloud | 第三章:服務消費者 (負載均衡Ribbon)
一起來學Spring Cloud | 第四章:服務消費者 ( Feign )
一起來學Spring Cloud | 第五章:熔斷器 ( Hystrix)
本章正在講解的:一起來學Spring Cloud | 第六章:服務網關 ( Zuul)
下章即將講解的: 一起來學Spring Cloud | 第七章:分布式配置中心(Spring Cloud Config)
剛入門的同學,如果把前面這七章都理解清楚,並且自己搭建一遍,在工作中,我們已經可以搭建一個最簡單的微服務項目了,我曾經看過一個創業公司,他們使用微服務框架時,就用以上的組件在生產上運行着簡單的后台業務系統。
一、Zuul簡介:
Zuul是Netflix開源的微服務網關,它可以和Eureka、Feign、hystrix等組件配合使用,Zuul的核心是一系列過濾器,它主要功能是路由轉發和過濾器。
在實際項目中,一個復雜的業務系統后台,少則幾十個服務模塊,多則成百上千,隨着業務場景的不斷變更,我們的系統也會不斷在演變,就會遇到如下的幾個問題:
1. 如果存在跨域請求,多個微服務在一定的場景下處理相對復雜。
2. 客戶端多次請求不同的微服務,增加了客戶端的復雜性。
3. 認證復雜,每個微服務都需要獨立認證。
4. 難以重構,隨着項目的迭代,可能需要重新划分微服務。例如,可能將多個微服務合並成一個或者將一個微服務拆分成多個。如果客戶端直接與微服務通信,那么重構將會很能實施。
5. 某些微服務可能使用了防火牆/瀏覽器不友好的協議,直接訪問會有一定困難。
Zuul提供的作用:
1. 提供統一服務入口,微服務對前台透明
2. 聚合后台服務,節省流量,提升性能
3. 安全,過濾,流控等API管理功能
4. 提供統一服務出口,解耦
二、Zuul實現路由功能:
1. 在前面2章講解的兩個服務模塊上,新增兩個方法,模擬前端請求,做為本次zuul的測試接口
springcloud-ribbon-client模塊的RibbonController類,增加/testzuul接口,具體模塊信息參考:一起來學Spring Cloud | 第三章:服務消費者 (負載均衡Ribbon)
package com.haly.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.haly.service.RibbonService;
@RestController
public class RibbonController {
@Autowired
RibbonService ribbonService;
@GetMapping(value = "/getHello")
public String getHello(@RequestParam String name) {
return ribbonService.getHello(name);
}
@GetMapping(value = "/testzuul")
public String testzuul(@RequestParam String name) {
return name +"這是springcloud-ribbon-clientd的服務接口";
}
}
springcloud-feign-client模塊的FeignController類,增加/testzuul接口,具體模塊信息參考:一起來學Spring Cloud | 第四章:服務消費者 ( Feign )
package com.haly.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.haly.romote.FeignRemoteService;
@RestController
public class FeignController {
@Autowired
FeignRemoteService feignRemoteService;
@GetMapping(value = "/getHello")
public String getHello(@RequestParam String name) {
return feignRemoteService.hello(name);
}
@GetMapping(value = "/testzuul")
public String testzuul(@RequestParam String name) {
return name +",這是springcloud-feign-client的服務接口";
}
}
2. 新建一個新的zuul服務工程,名稱為:springcloud-zuul-server
①:修改pom.xml文件,parent標簽引用的是父文件,具體父文件配置,參考:一起來學Spring Cloud | 第一章 :如何搭建一個多模塊的springcloud項目
<?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.haly</groupId>
<artifactId>springcloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.haly</groupId>
<artifactId>springcloud-zuul-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springcloud-zuul-server</name>
<description>新建一個zuuld項目</description>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
②:新增模塊啟動類SpringcloudZuulServerApplication
注解@EnableZuulProxy,表示開啟zuul的功能,它默認也具有@EnableCircuitBreaker和@EnableDiscoveryClient兩個注解的功能
package com.haly;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringBootApplication
public class SpringcloudZuulServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudZuulServerApplication.class, args);
}
}
③:application.properties加上以下的配置代碼:
server.port=9700 spring.application.name=springcloud-zuul-server eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/ zuul.ignored-services: "*" zuul.routes.a.path = /api/a/** zuul.routes.a.serviceId = springcloud-feign-client zuul.routes.b.path = /api/b/** zuul.routes.b.serviceId = springcloud-feign-client
先解釋下配置含義
zuul.ignored-services: "*" : 之前我們說過可以用服務名直接訪問接口,如果我們不想向外界暴露除了application.properties配置映射的服務接口,配置這個屬性,只能通過zuul映射的路徑訪問。
zuul.routes.a.path = /api/a/**
zuul.routes.a.serviceId = springcloud-feign-client
當我們訪問zuul服務模塊時,只要包含 /api/a/ 路徑的服務請求,默認請求到springcloud-ribbon-client模塊上的接口
zuul.routes.b.path = /api/b/**
zuul.routes.b.serviceId = springcloud-feign-client
同理,當我們訪問zuul服務模塊時,只要包含 /api/b/ 路徑的服務請求,默認請求到springcloud-feign-client模塊上的接口
3. 運行項目
啟動 注冊中心 springcloud-eureka-server,啟動springcloud-ribbon-client服務模塊,啟動springcloud-feign-client服務模塊,啟動springcloud-zuul-server模塊
在這里首先我要表達歉意,在第一章搭建多模塊的微服務項目時,我使用的springcloud和springboot的版本會有問題,所以本章節啟動springcloud-zuul-server模塊時報錯,具體報錯如下:

原因是springboot與springcloud的版本不一致導致的,以后有同學遇到同樣問題,記得將對應的版本號改成一致
在實際開發過程中,我們詳細的版本對應關系:

現在我們將父pom中springcloud的版本號修改為:Greenwich.SR1 ,再啟動springcloud-zuul-server服務模塊,可以啟動成功了,eureka上服務信息如下:

打開瀏覽器訪問訪問zuul服務的端口9700:http://localhost:9700/api/a/testzuul?name=young碼農,我們發現/api/b/*的請求路由到 springcloud-ribbon-client模塊

打開瀏覽器訪問zuul服務的端口9700:http://localhost:9700/api/b/testzuul?name=young碼農,我們發現/api/b/*的請求路由到 springcloud-feign-client模塊

三、Zuul實現服務過濾:
zuul不僅只是路由,並且還能過濾,可以用來做一些安全驗證和日志記錄,我寫一個簡單的接口執行時間記錄的功能
新建一個類:BaseZuulFilter
package com.haly.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class BaseZuulFilter extends ZuulFilter {
protected final Logger logger = LoggerFactory.getLogger(getClass());
// 單例多線程 開始時間綁定在線程上
private ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
@Override
public String filterType() {
// 在請求被處理之后,會進入該過濾器
return "post";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
// 請求開始計時
long startTime = System.currentTimeMillis();
startTimeThreadLocal.set(startTime);
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
String requestURI = String.valueOf(context.get("requestURI"));
// 請求結束時間
Long startTime = startTimeThreadLocal.get();
Long endTime = System.currentTimeMillis();
logger.info("[進入zuul日志記錄功能] RequestURI:{}, {}:ms", requestURI, endTime - startTime);
return null;
}
}
routing:路由之時
post: 路由之后
error:發送錯誤調用
shouldFilter:這里可以寫邏輯判斷,是否要過濾,本文true,永遠過濾。
run:過濾器的具體邏輯。可用很復雜,包括查sql,nosql去判斷該請求到底有沒有權限訪問。
四、總結:
當前為止,項目結構:

