導讀
我們知道在基於Spring Cloud的微服務體系中,各個微服務除了在內部提供服務外,有些服務接口還需要直接提供給客戶端,如Andirod、IOS、H5等等。
而一個很尷尬的境地是,如果直接將提供外部接口的微服務暴露給公網,那么意味着為了增強這個微服務的安全性,需要做很多額外的安全性措施,如報文數字簽名、加密等;而大部分場景下,微服務本身又是提供給內部其他微服務調用的,即便所有的微服務都會不同程度地直接面向App客戶端提供公網服務,那么為了這確保這些微服務的安全性,涉及的微服務也都需要實現接口的安全性,這樣不僅會造成重復開發,也會增加微服務體系的復雜性。所以,我們通常會在微服務體系的外部邊界架設一個網關服務,俗稱Gateway。
Gateway這個服務本身不做任何具體的業務邏輯,業務邏輯還是由相應地微服務去實現。因為在微服務開發中,為了簡化內部服務之間調用的繁瑣程度,大部分實踐是不會在微服務內部設防,如微服務本身是不會做報文層面的簽名、加密。而在面向外部提供服務時,則由Gateway服務進行統一的安全認證,認證通過后才會把請求路由到具體的微服務。
在這種模式下,微服務之間的調用因為都在內部網絡,而不直接向外暴露服務接口及IP,所以微服務的安全問題都依賴於內部網絡安全。(PS:如果網絡被攻破,那么微服務就會直接暴露在攻擊者面前,此時調用微服務的接口是可以直接影響業務的,如進行盜刷等,這個問題我們在下一篇文章中討論!)
當然Gateway除了支持做簡單的安全認證,如會話認證外,還具有服務限流,接口監控數據統一上報等其他功能和用途。在基於Spring Cloud的微服務架構體系中,目前提供了兩套方案供我們實現Gateway,分別是Netflix的Zuul以及Spring Cloud自身提供的 Spring Cloud Gateway(構建在Spring 5以及Spring Boot 2.0基礎之上)。本篇文章的主要內容是基於Zuul來介紹Api Gateway的搭建。
GateWay的位置
在我們具體分析基於Zuul的Gateway實現原理之前,我們先從整體架構上來了解下Gateway在整體微服務體系中所處的位置吧,這樣會有利於我們更深刻的理解Gateway在微服務架構體系中的作用。如下圖所示:
Gateway是一個處於內部微服務與外部公網請求之間的銜接性服務,它需要通過域名接收到用戶客戶端通過公網發出的服務請求,然后再路由至內部對應的微服務。因此Gateway本身既處於服務注冊中心的管理之下,如注冊到Consul,通過Consul來獲取其他微服務的地址列表,並進行請求路由轉發;又需要在被外部訪問的過程中,被諸如Nginx這樣的反向代理服務器進行服務代理,從而實現Gateway的負載均衡及高可用。
這里的一個技術細節是,Gateway大部分情況下是通過容器動態進行部署的,這一點與其他Spring Cloud微服務一樣是扁平的,但是又因為網關服務的特殊性,其IP端口需要被Nginx識別從而進行反向代理及負載均衡。這里的問題是Nginx如何能夠從茫茫的微服務中識別到那些是需要被外部訪問,從而進行反向代理的呢?
畢竟如果將Gateway直接部署到固定的IP及端口機器的話,會失去一定的彈性,從架構層面來說會有些蹩腳。作者之前所在的公司是通過將Gateway服務打tag,如需要被外部訪問的Gateway服務將其tag標簽打成api,這樣就可以借助一些工具如consul-template的方式,就可以做一些自動識別的流程來自動進行反向代理了!如果你有更好的實現方式,也歡迎給我留言哦!
到這里,相信你應該對Gateway在微服務架構體系中的位置有一個足夠清晰地認識和了解了,下面我們就將重點介紹Zuul的功能與實現原理。
Zuul簡介
Zuul是Neflix開源的Api Gateway服務器,它本質上是一個Servlet應用,其核心是通過一系列filters的實現來為整個微服務體系提供路由、安全、監控等邊界服務。Zuul目前分為兩個大版本Zuul1和Zuul2,它們的區別在於Zuul1的IO模型還是BIO的方式,而Zuul2則是使用NIO對Zuul1進行了重構,所以性能上要優於Zuul1。
正因為Zuul1的IO采用的是BIO,所以在Spring Cloud基於Spring Boot2.0的版本中才自己推出了基於NIO模型的Spring Cloud Gateway來取代Zuul,此時雖然Zuul2已經發布,但是因為發布的時間太晚,所以Spring Cloud對其已經不再默認集成了!
因此如果要升級Zuul的版本至Zuul2的話,你需要將Spring Cloud對應的版本升級到基於Spring Boot2.0的版本,並單獨引入Zuul2的版本依賴。而在這種情況下,直接選用Spring Cloud Gateway作為網關服務可能會是一個更合適的選擇。以下為基於Spring boot2.0的Spring Cloud版本依賴:
<parent> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-parent</artifactId> <version>Finchley.SR3</version> <relativePath /> </parent>
而考慮到歷史項目原因,目前不少基於Spring Cloud的項目還是構建在基於Spring boot1.0的版本之上,所以在本篇文章中對Zuul的分析還是基於Zuul1的版本,這一點請大家知悉!
Zuul原理
在前面我們提到過Zuul本質上是一個Servlet應用,其核心就是通過定義一系列的filters來對用戶的請求進行過濾處理,從而實現一些定制化的邊緣服務功能,如安全認證,打點監控等。類似於Servlet的Filter,只不過Zuul是提供了一個框架,來實現定義不同類型的filter,並對這些filter進行加載、編譯、運行。下面我們來看一下Zuul的核心類圖:
因為Zuul本身就是一個Servlet應用,而其要做的事情就是攔截所有用戶請求來進行過濾,在Zuul框架中ZuulServlet就是這樣一個類型,它類似於SpringMvc的DispatcherServlet,所有最終需要路由到微服務的請求,都會由它進行處理。
ZuulServlet中有3個核心的方法,它們分別是:preRoute(),route(), postRoute()。Zuul對所有request的處理邏輯都在這三個方法里面,而這些方法分別對應了Zuul中定義的幾種標准過濾器類型:
-
PRE:這種過濾器會在請求被路由之前調用。我們可以利用這種過濾器類型實現身份認證等前置邏輯;
-
ROUTE:這種過濾器將請求路由到微服務,用於構建發送給微服務的請求。這種類型的過濾器Zuul已經幫我們實現,用於實現Gateway到內部微服務調用的路由、負載均衡、限流等功能;
-
POST:這種過濾器在路由到微服務以后執行。可以用來為響應添加標准的Http Header、收集統計信息和指標,並將響應從微服務發送給客戶端;
-
ERROR:在請求處理階段發生錯誤時執行該過濾器;
從代碼層面看,以上方法的實際執行則是由具體實現了ZuulFilter接口的過濾器來實現的,這些過濾器會返回自身定義的類型如"pre",這樣通過Zuul框架相應的設計就會最終調用到過濾器的對應方法了。(PS:篇幅有限,更細節的實現,需要大家去閱讀源碼)
Zuul實踐示例
使用Zuul搭建一個Gateway非常簡單,只需要基於Spring Boot項目,在主類加上注解@EnableZuulProxy即可。如:
@EnableZuulProxy @SpringBootApplication @EnableAutoConfiguration(exclude = {RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class, DataSourceAutoConfiguration.class, MongoAutoConfiguration.class, MongoRepositoriesAutoConfiguration.class}) public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
而在具體定制相應的Filter時只需要實現ZuulFilter接口,確定濾器類型后實現run方法就可以了。
public class LoginFilter extends ZuulFilter { @Autowired private GatewayLoginFilterConfig loginFilterConfig; @Override public String filterType() { return "pre"; //pre在請求真實url之前做處理 } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest req = ctx.getRequest(); ... return null; }
以上就是本文的基本內容了,希望能夠讓您能有所收獲,歡迎轉發+點贊!
參考資料:
https://github.com/Netflix/zuul
https://github.com/Netflix/zuul/wiki
http://www.sohu.com/a/221110905_467759
https://www.jianshu.com/p/af1554553b5c