Spring Cloud微服務中網關服務是如何實現的?(Zuul篇)



導讀

我們知道在基於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



 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM