幾個月前上線了一個電子商務系統平臺,運用React開發前web前端,Groovy開發後端 REST API,應用性能及前端交互的響應非常好,但是有一個非常大的痛點。
整個React應用包括庫與應用代碼在minfy之後仍然超過2MB
。當用戶第一次訪問應用瀏覽器無緩存時,頁面一片空白,原因是瀏覽器需要下載JavaScript文件。即使已經使用webpack進行代碼分割,訪問頁面仍需要下載1.5MB
以上Javascript。在一個250KB/s
的下載帶寬下,頁面可能需要~8秒
才能首次渲染。這嚴重影響用戶體驗。
解決單頁應用的首次渲染方案很明顯是進行Server-Side渲染。在google了一些解決方案後,大多數文章及demo都是基於NodeJS,這也很自然,前後端都是JavaScript技術棧。因爲web端採用了React,手機端採用了React Native,所以使用NodeJS也行。不過因爲後端技術棧主要是Groovy運行於JVM上,所以採用NodeJS會增加部署的複雜度,尤其是當每個用戶企業需要部署一個應用實例的話,每個用戶企業都要部署一個NodeJS與JVM,從擴展角度來看不夠經濟。
JDK 1.7
開始增加了Nashorn Script Engine
,可以在JVM上運行JavaScript。網上關於運用Nshorn進行Server-Side Rendering的文章很少。有一篇非常好,Project Nashorn – JavaScript on the JVM,詳細解釋了Nashorn Script Engine
的特性。
React
包括Angular 2
與VueJS
,都不直接操作DOM,而是操作所謂Virtual DOM
(一種實際DOM的中間表示),通過定期的reconciliation比較上次渲染的差異後批量進行DOM操作。 在Server端渲染時可直接生成html,因此不需要DOM的環境。
由下圖可見
引自 https://blog.codecentric.de/files/2014/06/specifications.png
Nashorn僅實現了ECMAScript
,如同Chrome的JavaScript內核一樣。因此如果在Nashorn上進行React應用渲染,我們需要自己提供XMLHttpRequest spec
與HTML 5 Spec(section 6)
的polyfill。在JavaScript中經常使用的Promise
, Fetch
等可以通過JavaScript進行polyfill,如很多人使用的core-js
。
Github上有段代碼關於polyfill Nashorn。我下載下來,運行正常。但過了段時間後遇到一個問題,偶爾會有一些請求在服務器端掛起直到timeout。如果通過apache bench
進行併發測試在100
請求40
併發下大約有7%
的請求掛起直到timeout。這肯定無法進行生產運用。
幾天前突然在Youtube上看到一段視頻。Philip Roberts詳細了Event Loop
如何工作的。於是我基於之前的代碼增加了nashornEventLoop
(Deque
),Timer Task
運行時往nashornEventLoop
尾部增加一個function callback
的對象。Nshorne Engine
線程調用nashornEventLoop.process()
從隊列頭部取function callback
的對象進行函數回調。結果很驚人,掛起問題解決,即使大併發測試也無問題。
經過仔細理解與驗證,原來在之前版本的polyfill中使用了Timer Task
運行function callback
,實際上它是運行於另外一個線程(不同於Nashorn Engine
的線程),可能會同時操作一個JavaScript對象從而損壞Nashorn Engine
線程的Javascript調用棧。在後面版本Timer
的線程只是往nashornEventLoop
隊列上增加function callback
對象,從而不會損壞Nashorn Engine
線程JavaScript調用棧,同時通過Phaser
協調Nashorn Engine
線程與Timer
線程同一時間只有一個能訪問nashornEventLoop
隊列。
性能測試顯示,在Macbook Pro (2.3G 4核,16G內存,SSD), 能夠實現~40
請求/秒,JVM內存佔用600MB~1.1GB
,服務器上理應更高
現在Server-Side Rendering解決方案除了NodeJS
,JVM Nashorn Engine
也是可靠的一種。
相關github庫:
- https://github.com/zloirock/core-js
- https://github.com/morungos/java-xmlhttprequest/blob/develop/src/main/resources/polyfill.nashorn.js
- https://github.com/shendepu/nashorn-polyfill
- https://github.com/shendepu/react-ssr-starter-kit
- https://github.com/shendepu/moqui-react-ssr
- https://github.com/shendepu/moqui-react-ssr-demo