網關是什麼
是一個網絡整體系統中的前置門戶入口。請求首先通過網關,進行路徑的路由,定位到具體的服務節點上。是進入服務的統一大門。
網關分類
api開放網關
開放api(openApi) 企業需要将自身資料、能力等作為開發平台向外開放,通常會以rest的方式向外提供,最好的例子就是淘寶開放平台、騰訊公司的QQ開發平台、微信開放平台。 Open API開放平台必然涉及到客戶應用的接入、API權限的管理、調用次數管理等,必然會有一個統一的入口進行管理,這正是API網關可以發揮作用的時候。
微服務網關
微服務的概念最早在2012年提出,在Martin Fowler的大力推廣下,微服務在2014年後得到了大力發展。 在微服務架構中,有一個元件可以說是必不可少的,那就是微服務網關,微服務網關處理了負載均衡,緩存,路由,通路控制,服務代理,監控,日志等。API網關在微服務架構中正是以微服務網關的身份存在。
API服務管理平台
上述的微服務架構對企業來說有可能實施上是困難的,企業有很多遺留系統,要全部抽取為微伺服器改動太大,對企業來說成本太高。但是由于不同系統間存在大量的API服務互相調用,是以需要對系統間服務調用進行管理,清晰地看到各系統調用關系,對系統間調用進行監控等。 API網關可以解決這些問題,我們可以認為如果沒有大規模的實施微服務架構,那麼對企業來說微服務網關就是企業的API服務管理平台。
網關作用
- 統一入口:未全部為服務提供一個唯一的入口,網關起到外部和内部隔離的作用,保障了背景服務的安全性。
- 鑒權校驗:識别每個請求的權限,拒絕不符合要求的請求。
- 動态路由:動态的将請求路由到不同的後端叢集中。
- 減少用戶端與服務端的耦合:服務可以獨立發展,通過網關層來做映射。
- 限流控制等
網關與過濾器差別
網關是攔截所有伺服器請求進行控制
過濾器攔截某單個伺服器請求進行控制
Nginx與Zuul的差別
Nginx是采用伺服器負載均衡進行轉發
Zuul依賴Ribbon和eureka實作本地負載均衡轉發
相對來說Nginx功能比Zuul功能更加強大,能夠整合其他語言比如lua腳本實作強大的功能,同時Nginx可以更好的抗高并發,Zuul網關适用于請求過濾和攔截等。
Zuul 網關的原理
springcloud 整合了 zuul,其實作是類似 過濾器和aop 的整合,一系列Filter展開的,這些Filter在整個HTTP請求過程中執行一連串的操作,zuul 整合了ribbon 和hystrix 提供服務。
Zuul 網關使用
- 啟動 eureka 服務 : http://localhost:1008;
-
啟動product 服務: http://localhost:3008
product 服務是注冊到 eureka 的名稱
product 服務接口: http://localhost:3008/product/test1
@RequestMapping("/product/test1")
public String test1(){
System.out.println("------test1 接口被調用啦,正在執行");
return "success return test1";
}
作為我們的測試服務,接下來是網關服務
maven 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<!-- SpringBoot整合eureka用戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<!-- zuul網關的重試機制,不是使用ribbon内置的重試機制
是借助spring-retry元件實作的重試
開啟zuul網關重試機制需要增加下述依賴
-->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!-- 內建lombok 架構 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
啟動類
/**
*
* @EnableZuulProxy - 開啟Zuul網關。
* 目前應用是一個Zuul微服務網關。會在Eureka注冊中心中注冊目前服務。并發現其他的服務,不用添加其他的eureka注解了
*/
@SpringBootApplication
@EnableZuulProxy
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
配置路由
首先端口和eureka配置
server.port=4008
spring.application.name=zuul
eureka.client.serviceUrl.defaultZone = http://localhost:1008/eureka/
增加路由,先采用名稱路由
zuul.routes.webshop-product.path=/api-product/**
zuul.routes.webshop-product.service-id=product
通路的方式是這樣
http://zuulHostIp:port/要通路的服務名稱/服務中的URL,
例如: http://localhost:3008/product/test1
通路: http://localhost:4008/api-product/product/test1
過濾器
- 前置過濾:是請求進入Zuul之後,立刻執行的過濾邏輯。
- 路由後過濾:是請求進入Zuul之後,并Zuul實作了請求路由後執行的過濾邏輯,
- 路由後過濾,是在遠端服務調用之前過濾的邏輯。
- 後置過濾:遠端服務調用結束後執行的過濾邏輯。
- 異常過濾:是任意一個過濾器發生異常或遠端服務調用無結果回報的時候執行的過濾邏輯。無結果回報,就是遠端服務調用逾時。
實作父類來定義,具體如下
@Component
@Slf4j
public class PreFilter extends ZuulFilter {
/**
* 過濾器的類型。可選值有:
* pre - 前置過濾
* route - 路由後過濾
* error - 異常過濾
* post - 遠端服務調用後過濾
*/
@Override
public String filterType() {
return "pre";
}
/**
* 同種類的過濾器的執行順序。
* 按照傳回值的自然升序執行。
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 傳回boolean類型。代表目前filter是否生效。
* 預設值為false。
* 傳回true代表開啟filter。
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* run方法就是過濾器的具體邏輯。
* return 可以傳回任意的對象,目前實作忽略。(spring-cloud-zuul官方解釋)
* 直接傳回null即可。
*
* 使用過濾器驗證用戶端是否有登陸
*/
@Override
public Object run() throws ZuulException {
System.out.println("Pre 網關過濾執行");
// 通過zuul,擷取請求上下文
RequestContext rc = RequestContext.getCurrentContext();
HttpServletRequest request = rc.getRequest();
log.info("LogFilter1.....method={},url={}",
request.getMethod(),request.getRequestURL().toString());
// 可以記錄日志、鑒權,給維護人員記錄提供定位協助、統計性能
String userToken = request.getParameter("userToken");
// if (StringUtils.isEmpty(userToken)) {
// rc.setSendZuulResponse(false);
// rc.setResponseStatusCode(401);
// rc.setResponseBody("userToken is null");
// return null;
// }
// 否則正常執行業務邏輯.....
return null;
}
}
現在啟動 zuul 服務。通路 http://localhost:4008/api-product/product/test1,傳回正常,已經路由到了product 服務。
容錯機制
路由到目标服務有時候會出現網絡不通等原因,造成失敗,需要注意的是,隻要有傳回,就不會觸發容錯機制,不論是不是異常。
跳轉 product服務,延時3秒傳回
@RestController
public class TestController1 {
@RequestMapping("/product/test1")
public String test1(){
try {
Thread.sleep(3000); // 模拟故障,逾時 3 秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------test1 接口被調用啦,正在執行");
return "success return test1";
}
}
現在設定zuul 路由到product 時間是 2秒,增加配置
zuul.host.connect-timeout-millis=2000
增加容錯代碼
@Component
public class TestFallbackProvider implements FallbackProvider {
/**
* return - 傳回fallback處理哪一個服務。傳回的是服務的名稱,
* 推薦 - 為指定的服務定義特性化的fallback邏輯。
* 推薦 - 提供一個處理所有服務的fallback邏輯。
* 好處 - 服務某個服務發生逾時,那麼指定的fallback邏輯執行。如果有新服務上線,未提供fallback邏輯,有一個通用的。
* 可以使用通配符‘*’代表為全部的服務提供容錯處理。
*/
@Override
public String getRoute() {
return "product";
}
// 降級處理
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("進入 fallbackResponse");
// 設定降級給用戶端傳回的資訊
return new ClientHttpResponse(){
@Override
public HttpHeaders getHeaders() {
HttpHeaders header = new HttpHeaders();
// 傳回json資料
MediaType mt = new MediaType("application","json", Charset.forName("utf-8"));
header.setContentType(mt);
return header;
}
// 設定響應的内容
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("product服務不可用,請于管理者聯系!".getBytes("utf-8"));
}
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.BAD_REQUEST;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.BAD_REQUEST.value();
}
// 傳回狀态碼
@Override
public String getStatusText() throws IOException {
return HttpStatus.BAD_REQUEST.getReasonPhrase();
}
@Override
public void close() {
}
};
}
}
通路 http://localhost:4008/api-product/product/test1,
前台傳回 友好提示
product服務不可用,請于管理者聯系!
zuul 限流
首先簡單說一下 spring cloud zuul-ratelimit,他是外國人專門針對 zuul 編寫的限流庫,提供來4種限流政策,如下。
限流粒度/類型 | 說明 |
---|---|
User | 針對請求的使用者進行限流 |
Origin | 針對請求的Origin進行限流 |
URL | 針對請求的接口限流 |
ServerId | 針對服務限流,預設的 |
多種粒度臨時變量儲存方式
存儲方式 | 說明 |
---|---|
IN_MEMORY | 基于本地記憶體,底層是ConcurrentHashMap,預設的 |
REDIS | 基于redis存儲,使用時必須搭建redis |
JPA | spring data jpa,基于資料庫 |
BUKET4J | 使用一個Java編寫的基于令牌桶算法的限流庫 |
CONSUL | consul 的kv存儲 |
如果 zuul 需要多節點部署,那就不能用 IN_MEMORY 存儲方式,比較常用的就是用Redis。
增加依賴
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>1.3.4.RELEASE</version>
</dependency>
配置檔案增加
# 全局限流配置
# 開啟限流保護
zuul.ratelimit.enabled=true
# 60s内請求超過3次,服務端就抛出異常,60s後可以恢複正常請求
zuul.ratelimit.default-policy.limit=3
zuul.ratelimit.default-policy.refresh-interval=60
簡單的設定了全局的限流
然後通路服務,當第四次的時候,傳回
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sat May 09 15:35:08 CST 2020
There was an unexpected error (type=Too Many Requests, status=429).
429
這個地方,可以統一處理下異常,傳回友好提示。
-
補充:局部限流
針對product 服務限流
# 開啟限流保護
zuul.ratelimit.enabled=true
# hystrix-application-client服務60s内請求超過3次,服務抛出異常。
zuul.ratelimit.policies.product.limit=3
zuul.ratelimit.policies.product.refresh-interval=60
# 針對IP限流。
zuul.ratelimit.policies.product.type=origin