Play! Framework 系列(一):初探 play 框架

本文由 Shaw 發表在 ScalaCool 團隊博客。前端

Play! 是一種高效率的 Java 和 Scala Web 應用程序框架,它可以用來開發「響應式」 Web 應用,同時它也集成了現代 Web 應用程序開發所需的組件和 API。本文將介紹一下 Play! 的基本性質以及利用該框架開發 Web 程序的優點。java

響應式 Web 開發框架


Image of play
Image of play

Play! 拋棄了傳統的 Java Web 框架的模式,而是選擇擁抱「響應式」(Reactive)應用的理念,從頭開始設計,這使得 Play! 能夠夠構建出即便在高負載下也可以對用戶行爲進行實時響應的 Web 應用。 Play! 做爲一個全棧「響應式框架」主要有以下特色:web

  1. 響應式(Responsive)—— 在用戶層面,Play! 可以快速響應用戶的行爲瀏覽器

  2. 可伸縮(Scalable)—— 在負載層面,Play! 能實現良好的水平擴展緩存

  3. 事件驅動(Event-driven)—— Play! 的 HTTP Server 就是基於事件模型實現的tomcat

接下來咱們就 Play!的這幾個主要的特色進行介紹。bash

事件模型 Web 服務器(Evented Web Application Server)

在 Play 2.6.x 以前,Play! 的 HTTP Server 是基於 Netty 實現的,最新版的 Play 2.6.x 是基於 Akka HTTP 實現的,在介紹 Play! 的 HTTP Server 的優點以前,咱們先看一下傳統的 Java Web 框架所採用的 HTTP Server 是怎樣的。服務器

當前比較主流的實現 HTTP Server 的模型主要有兩類——「線程模型」以及「事件模型」。restful

線程模型服務器(THREADED SERVERS)

傳統的 Java Web 框架所採用的服務器就是基於線程模型來實現的,好比很是流行的 Apache Tomcat,該模型的工做方式以下圖所示:session


Image of Thread
Image of Thread

「接收者線程」(Accpter thread)接受客戶端的 HTTP 請求,而後將這些請求分配給「請求處理線程」進行處理。

這種模型的弊端就是,「工做線程」(也就是上面提到的「請求處理線程」)是有限的,而客戶端發來的請求數量每每會大於「工做線程」的數量。當此種狀況發生時,那些沒有獲得處理的線程就會一直處於阻塞和等待狀態,反映到用戶層面就是頁面遲遲得不到響應,若是等待時間過長,耐心的用戶最終會看到請求超時(Request timeout)的信息,急性子的用戶就會關掉這個頁面。

另外採用線程這種方式也很是地耗費資源,若是某個請求很耗費時間,那麼處理該請求的工做線程大概是這樣工做的:


Image of thread-handle
Image of thread-handle

其中綠色是程序運行時間,紅色是等待時間,能夠看到因爲 I/O 操做比較慢,因此這個線程的工做時間大部分都在等待,極大地消耗了資源,若是是採用多線程,那消耗的資源將會多倍增長。

事件模型服務器(EVENTED SERVERS)

Play! 的 HTTP Server 是基於 Netty 或者 Akka HTTP 實現的,這兩個框架都具備異步非阻塞的優勢。咱們先看一下 Play! 的事件模型服務器是如何工做的:


Image of Event
Image of Event

咱們知道,當用戶發送一個請求的時候,每每這個請求包含了許多操做,而 Play! 則能將這些請求分割爲一個一個的事件,而後異步去處理這些事件。例如,當某一個事件正在被操做系統處理的時候,這個過程可能會花費一些時間,以前說過,若是線程一直等待這個事件執行完而後再去執行下一個事件就有點浪費資源了,因此在這個等待時間裏,event loop(消息線程)能夠去執行事件隊列中的其餘事件。當某個事件執行完以後,就會發出一箇中斷,這個中斷也算一個事件,而後加入到事件隊列中,等待執行。這種異步非阻塞的模式使得 Play! 可以以較少的資源應對大流量的訪問。反映在用戶層面就是,Play! 可以快速地對用戶的行爲做出響應。

爲了與「線程模型」進行對比,咱們畫一個相似的圖來解釋爲何「事件模型」消耗的資源更少而處理的請求更多:


Image of Event handle
Image of Event handle

圖中綠色的部分爲事件的執行時間,橙色部分爲「空閒時間」,注意這裏是「空閒時間」而非前面所說的「等待時間」,在這個空閒時間內,event loop 能夠去執行其餘事件而沒必要等待前面某個事件執行完成,當某個事件執行完成以後,會發出中斷,這個中斷也會產生一個新的事件,最終 event loop 也會執行這個事件。這就是「事件模型」處理某個請求的流程,能夠看到,沒有了等待時間,大大提升了程序運行的效率,也使得系統可以以較少的資源處理大量的請求。

異步非阻塞

Play! 經過從新設計並實現了本身 HTTP Server 這使得 Play! 可以以「異步」的方式去處理每個請求。在利用 Play! 進行開發的時候,Play! 默認配置的 controller 就是異步的,因此咱們能夠利用 Play! 很方便地寫出異步非阻塞的代碼。咱們知道,在 Java8 以前,要編寫異步非阻塞的代碼每每須要使用回調,可是當業務邏輯變得複雜,回調變多的時候就會出現傳說中的 「回調地獄」,這使得代碼的可讀性極差。而 Scala 語言引入了 Future ,極大地簡化了多個回調的處理,使代碼看上去優雅不少(關於如何在 Play! 中利用Future實現異步邏輯,咱們將會在後面的文章中進一步的介紹)。因此在 Play! 中利用 scala 編寫異步代碼將會變得很是高效。

無狀態(Stateless)

Play! 框架拋棄了 Servlet/JSP 裏 Session 等概念,內置沒有提供方法將對象與服務器實例進行綁定,在每次 HTTP Request 之間不會在 Server 端存儲狀態,所需的狀態都須要在 HTTP Request 之間傳遞,這樣作的好處就是使得應用在負載層面實現了良好的水平擴展,接下來咱們分別介紹一些有狀態的部署方式與無狀態的部署方式。

有狀態部署


Image of state
Image of state

若是咱們在 session 中保存了大量與客戶端的「狀態信息」的話,爲了防止某臺機器宕機而致使用戶與服務器保存的會話狀態丟失,咱們須要在各個節點之間共享這些「狀態信息」。比較常見的作法是採用集羣,好比採用 tomcat 或者 jboss 的集羣功能,採用此種方式並不能經過增長節點來解決系統負載過大的問題,由於隨着節點增長,各個節點之間 session 的通訊會增長,從而使系統開銷增大。因此採用有狀態的部署方式不能使系統具備良好的伸縮性。

無狀態部署


Image of stateless
Image of stateless

如圖所示,採用無狀態的部署方式,每一個節點不保存諸如 session 之類的狀態信息,各個節點之間也沒有共享狀態,它們彼此都是獨立的。當系統的負載增長時,咱們只須要增長一個節點,而後在前端經過均衡負載就可使系統的性能提升。這樣就使得系統具備良好的伸縮性。固然,沒有了 session,那 Play! 如何來保存狀態呢,咱們可使用 Play! 中基於 Cookie 的客戶端用戶會話以及「外部緩存」(這些在以後的文章中會介紹)。

ROR風格

對於不少公司而言,快速地開發出一款產品並上線很是重要,因爲 ROR (Ruby on Rails) 風格的框架在開發效率上面很是高,因此不少公司在快速構建應用時每每會選擇這類框架,而不是傳統的 Java 框架。


Image of play and java layer
Image of play and java layer

經過上圖能夠對比一下 Play! 與傳統的 Java EE 框架的區別,能夠看到 Play! 在架構上更加清晰簡潔。在 Play! 以前, 相比於 ROR 風格的框架,傳統的 Java Web 框架在開發網頁應用的時候每每耗時比較長,緣由主要有兩個:

一、依賴 Servlet

傳統的 Java Web 框架都是基於 Servlet 來構建的,開發人員開發的應用也須要在 Servlet 容器中運行,可是這就帶來了一個後果,開發人員每次修改完代碼以後,都須要從新啓動 Web 服務器才能看到修改後的效果。若是某一個項目規模較小,那重啓以及編譯的時間還能接受,可是若是項目很大,那開發過程當中所花的大部分時間都浪費在重啓以及編譯上面了。

而 Play! 框架經過 ClassLoader 在源代碼修改的時候動態加載類,解決了修改代碼須要重啓服務器的問題,使得開發效率變高。

二、 複雜的 XML 配置文件

傳統的 Java Web 框架在開發某個 Web 應用的時候須要引入大量的 XML 配置文件,這些文件在配置起來比較麻煩,若是數量不少且分散在不一樣的文件下面會使得維護成本增長。

Play! 框架深諳 ROR 之道,採用 約定優於配置,只有一個全局的配置文件 application.conf,其餘大部分配置都是默認的,咱們只須要按照它約定的去作好了。

RESTFul

傳統的 Java Web 框架利用 Servlet 將 Http協議隱藏了起來,也就是說開發者不能很直觀地看到某一個請求對應的某個操做。而 Play! 在設計上擁抱了 Http 協議,好比咱們要獲取一個用戶列表,咱們就能夠在 route 文件中這樣寫:

GET      /customer/list      controllers.CustomerController.list複製代碼

那麼 /customer/list 這個 URL 對應的就是 CustomerController 中的 list 方法。

這樣看上去更加直觀。

強類型模板

從 Play! 2 開始, Play! 的模板就全面擁抱了 Scala,因此 Play! 的模板都是能夠編譯的 Scala 函數,這就意味着咱們能夠在編譯的時候直接在瀏覽器或者控制檯中看到模板的錯誤信息,而不用等到將應用部署,調用頁面以後才能發現錯誤。

相關文章
相關標籤/搜索