分散式專案(九)zuul 第一代閘道器
書接上回,這個接的有點久(手動滑稽)。。。
上次說道使用Nginx對coap接入做負載均衡,讓協議伺服器可以橫向擴充套件,但我們的裝置管理系統在訪問時還是訪問的我們伺服器的地址,如果現在有其它服務要呼叫我們的裝置管理系統如果直接使用其ip地址會有服務地址暴露的風險,並且後期如果做鑑權,都不好做,所以這裡我們引出了spring cloud另外一個元件,zuul閘道器。
Zuul是什麼
zuul是netfilx開源的一個api gateway伺服器,記住是api 伺服器,它只能做api請求閘道器,或者說是http請求的閘道器。像上面我們用Nginx做udp路由,使用的是埠監聽,它是做不了的。
為啥要用zuul 它能幹啥
為啥要用zuul,因為統一入口呀,統一入口能幹啥,那可就多了哦
- 審查與監控
- 動態路由,負載均衡
- 靜態響應處理
- 驗證與安全保障
為啥它能幹這麼多事呢?
上面說zuul是一個api伺服器,它本質上是一個web servlet伺服器,或者說是個filter應用,專幹攔截過濾的勾當((^o^)/~)。
zuul核心原理
zuul的工作流程是一系列的filters,和servlet的filter,spring中的aop是一樣一樣兒的。
一個http請求要走的路
http請求->zuul servlet->zuulFilter Runner -> filter1->filter2->.... ->業務伺服器-> filter2->filter1->http response
這有點向什麼,Status2的攔截器,也是請求的時候一個關卡一個關卡的過去,回來的時候在從後面的關卡一個一個的回來。
知道工作流程,突然發現一個問題,filter之間是如何傳遞資料的,一般這種問題就兩種解決方案,一是我自己走哪兒帶哪兒,這個好理解,就想我們帶錢一樣,就揣我兜裡,想用的時候就用,二是放在一個地方我用的時候在去拿,這就比如錢太多了拿不動,咋辦,放銀行,用的時候我再去取。
zuul採用的是第二種方式,filter之前通過一個requestContext的靜態類對進行資料的傳遞,那我把資料放進去了我取得時候咋知道是我的呢,錢存銀行,銀行會給你發張卡,你憑卡取錢,requestContext可沒有卡,但它有另一個東西,ThreadLocal執行緒資料快取器,這資料是A執行緒存的,那只有你A執行緒才能取出來,其它執行緒只能瞅著,錯,連瞅都瞅不了。
剛才我們說閘道器是一個服務的入口,或者說是一個服務叢集的入口,它的核心是filter,但我們又時候需要加一些新的filter,來滿足我們新的需求,但是我們又不能把閘道器給停的,閘道器停了不整個服務就乾瞪眼了麼,所以我們需要它能動態地載入filter。
zuul的過濾器是由groovy寫成的,這些過濾器被放在了zull server上特定的目錄下面,zuul會定期輪訓這些目錄,修改過的過濾器會動態地載入到zuul server中以便過濾請求使用。這就滿足了我們動態增過濾器的需求。
zuul過濾器型別
- zuul定義了四種過濾器型別,這些過濾器型別對應請求的處於什麼週期
- pre:請求路由之間呼叫,可以做許可權檢測
- routing:這種過濾器將請求,這種過濾器用於構建傳送到微服務的請求,並使用Apache httpClient或ribbon請求微服務
- post:這種過濾器在路由到微服務以後執行,可以用來新增標準的http header,收集統計資訊和指標,將響應從微服務傳送給客戶端
- error:在其它階段發生錯誤是執行
這就有點想aop的請求前,請求後,發生錯誤執行了。
zuul還內建了兩個特殊的過濾器
staticResponseFilter:允許從zull本身生成響應,而不是將請求轉發,閘道器不是做轉發麼,它自己響應請求是個什麼鬼,看名字靜態?是噻,像那種百年不變的js,css,圖片這種靜態資源完全可以由閘道器自己響應。
surgicalDebugFilter:允許將特定請求路由到分隔的除錯叢集和主機上
關於zuul就簡單介紹一下,下面開始編寫程式碼。
建立protocol-zuul
maven配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>cn.le</groupId>
<artifactId>consul-discovery-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
#application配置
spring.application.name=protocol-zuul
server.port=8888
#健康檢查路徑
spring.cloud.consul.discovery.health-check-path=/actuator/health
#eureka.client.service-url.defaultZone=http://127.0.0.1:1111/eureka/
spring.cloud.consul.discovery.prefer-ip-address=true
spring.cloud.consul.discovery.instance-id=${spring.application.name}:${server.port}
spring.cloud.consul.discovery.hostname=${spring.application.name}
spring.cloud.consul.discovery.service-name=${spring.application.name}
#配置路由到此伺服器
zuul.routes.api-a.service-id=iot-manage
#配置路由規則
zuul.routes.api-a.path=/manage/**
zuul.routes.api-a.stripPrefix=false
zuul關於路由對映的方式兩種一種是使用ip對映,一種是現在使用的這種zuul.routes.api-a.service-id的方法,ip對映的方式可以在獨立使用zuul的時候使用,我們這裡使用了註冊中心,所以使用id名稱對映的方式,這種方式對動態擴容,服務發現、註冊友好。
啟動zuul服務
zuul啟動後,在consul的管理頁面就可以看到zuul的服務了。
前面我們直接訪問一下http://127.0.0.1:8081/manage/swagger-ui.html 可以正常開啟裝置管理系統的swagger的介面,但是現在我們有的服務閘道器,需要從服務閘道器去訪問, http://127.0.0.1:8888/manage/swagger-ui.html 8888是zuul服務的埠,利用訪問zuul的方式去訪問裝置管理系統看看能不能成功開啟swagger。
一樣能正常開啟,這就表示zuul成功了。
自定義過濾器
剛才我們只用利用zuul去訪問manager系統,但是沒做任何處理,如果我們需要在訪問之前對請求做處理,比如驗證是否登入。
建立AuthFilter類,並繼承ZuulFilter,宣告AuthFilter是一個ZuulFilter,並且可以讓ZuulFilter Runner呼叫。
package cn.le.pre;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
[@Component](https://my.oschina.net/u/3907912)
/**
* 自定義過濾器,繼承zuulFilter即可
*/
public class AuthFilter extends ZuulFilter {
//pre型別,請求路由之前呼叫
[@Override](https://my.oschina.net/u/1162528)
public String filterType() {
return "pre";
}
//過濾器的執行順序,數字越小表示越先執行
[@Override](https://my.oschina.net/u/1162528)
public int filterOrder() {
return 0;
}
//判斷此過濾器是否執行,true執行,false不執行
[@Override](https://my.oschina.net/u/1162528)
public boolean shouldFilter() {
return true;
}
//過濾器執行邏輯
[@Override](https://my.oschina.net/u/1162528)
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
System.out.println("AuthFilter----"+request);
return null;
}
}
這裡只打印了請求資訊,並沒有寫關於登入的邏輯,因為登入是一個綜合元件配套的邏輯,這裡篇幅有限就不實現了。
重新訪問一下http://192.168.0.105:8888/manage/swagger-ui.html
從控制日誌我們可以看出對請求進行的過濾。