几个月前上线了一个电子商务系统平台,运用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