Play! Framework 系列(一):初探 play 框架
Play! 是一種高效率的 Java 和 Scala Web 應用程序框架,它能夠用來開發「響應式」 Web 應用,同時它也集成了現代 Web 應用程序開發所需的組件和 API。本文將介紹一下 Play! 的基本性質以及利用該框架開發 Web 程序的優勢。
響應式 Web 開發框架
Play! 拋棄了傳統的 Java Web 框架的模式,而是選擇擁抱「響應式」(Reactive)應用的理念,從頭開始設計,這使得 Play! 可以夠構建出即使在高負載下也能夠對用戶行為進行實時響應的 Web 應用。 Play! 作為一個全棧「響應式框架」主要有如下特點:
接下來我們就 Play!的這幾個主要的特點進行介紹。
事件模型 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 的模型主要有兩類——「線程模型」以及「事件模型」。
線程模型服務器(THREADED SERVERS)
傳統的 Java Web 框架所采用的服務器就是基于線程模型來實現的,比如非常流行的 Apache Tomcat,該模型的工作方式如下圖所示:
「接收者線程」(Accpter thread)接受客戶端的 HTTP 請求,然后將這些請求分配給「請求處理線程」進行處理。
這種模型的弊端就是,「工作線程」(也就是上面提到的「請求處理線程」)是有限的,而客戶端發來的請求數量往往會大于「工作線程」的數量。當此種情況發生時,那些沒有得到處理的線程就會一直處于阻塞和等待狀態,反映到用戶層面就是頁面遲遲得不到響應,如果等待時間過長,耐心的用戶最終會看到請求超時(Request timeout)的信息,急性子的用戶就會關掉這個頁面。
另外采用線程這種方式也非常地耗費資源,如果某個請求很耗費時間,那么處理該請求的工作線程大概是這樣工作的:
其中綠色是程序運行時間,紅色是等待時間,可以看到由于 I/O 操作比較慢,所以這個線程的工作時間大部分都在等待,極大地消耗了資源,如果是采用多線程,那消耗的資源將會多倍增加。
事件模型服務器(EVENTED SERVERS)
Play! 的 HTTP Server 是基于 Netty 或者 Akka HTTP 實現的,這兩個框架都具有異步非阻塞的優點。我們先看一下 Play! 的事件模型服務器是如何工作的:
我們知道,當用戶發送一個請求的時候,往往這個請求包含了許多操作,而 Play! 則能將這些請求分割為一個一個的事件,然后異步去處理這些事件。例如,當某一個事件正在被操作系統處理的時候,這個過程可能會花費一些時間,之前說過,如果線程一直等待這個事件執行完然后再去執行下一個事件就有點浪費資源了,所以在這個等待時間里,event loop(消息線程)可以去執行事件隊列中的其他事件。當某個事件執行完之后,就會發出一個中斷,這個中斷也算一個事件,然后加入到事件隊列中,等待執行。這種異步非阻塞的模式使得 Play! 能夠以較少的資源應對大流量的訪問。反映在用戶層面就是,Play! 能夠快速地對用戶的行為作出響應。
為了與「線程模型」進行對比,我們畫一個類似的圖來解釋為什么「事件模型」消耗的資源更少而處理的請求更多:
圖中綠色的部分為事件的執行時間,橙色部分為「空閑時間」,注意這里是「空閑時間」而非前面所說的「等待時間」,在這個空閑時間內,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 之間傳遞,這樣做的好處就是使得應用在負載層面實現了良好的水平擴展,接下來我們分別介紹一些有狀態的部署方式與無狀態的部署方式。
有狀態部署
如果我們在 session 中保存了大量與客戶端的「狀態信息」的話,為了防止某臺機器宕機而導致用戶與服務器保存的會話狀態丟失,我們需要在各個節點之間共享這些「狀態信息」。比較常見的做法是采用集群,比如采用 tomcat 或者 jboss 的集群功能,采用此種方式并不能通過增加節點來解決系統負載過大的問題,因為隨著節點增加,各個節點之間 session 的通信會增加,從而使系統開銷增大。所以采用有狀態的部署方式不能使系統具有良好的伸縮性。
無狀態部署
如圖所示,采用無狀態的部署方式,每個節點不保存諸如 session 之類的狀態信息,各個節點之間也沒有共享狀態,它們彼此都是獨立的。當系統的負載增加時,我們只需要增加一個節點,然后在前端通過均衡負載就可以使系統的性能提高。這樣就使得系統具有良好的伸縮性。當然,沒有了 session,那 Play! 如何來保存狀態呢,我們可以使用 Play! 中基于 Cookie 的客戶端用戶會話以及「外部緩存」(這些在之后的文章中會介紹)。
ROR風格
對于很多公司而言,快速地開發出一款產品并上線非常重要,由于?ROR (Ruby on Rails)?風格的框架在開發效率上面非常高,所以很多公司在快速構建應用時往往會選擇這類框架,而不是傳統的 Java 框架。
通過上圖可以對比一下 Play! 與傳統的 Java EE 框架的區別,可以看到 Play! 在架構上更加清晰簡潔。在 Play! 之前,相比于 ROR 風格的框架,傳統的 Java Web 框架在開發網頁應用的時候往往耗時比較長,原因主要有兩個:
1、依賴 Servlet
傳統的 Java Web 框架都是基于 Servlet 來構建的,開發人員開發的應用也需要在 Servlet 容器中運行,但是這就帶來了一個后果,開發人員每次修改完代碼之后,都需要重新啟動 Web 服務器才能看到修改后的效果。如果某一個項目規模較小,那重啟以及編譯的時間還能接受,但是如果項目很大,那開發過程中所花的大部分時間都浪費在重啟以及編譯上面了。
而 Play! 框架通過 ClassLoader 在源代碼修改的時候動態加載類,解決了修改代碼需要重啟服務器的問題,使得開發效率變高。
2、復雜的 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 函數,這就意味著我們可以在編譯的時候直接在瀏覽器或者控制臺中看到模板的錯誤信息,而不用等到將應用部署,調用頁面之后才能發現錯誤。
總結
以上是生活随笔為你收集整理的Play! Framework 系列(一):初探 play 框架的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图片上传功能实现
- 下一篇: Play! Framework 系列(二