高可用 React 服务端渲染

郭达峰 Strikingly 联合创始人兼CTO

Strikingly / 上线了

十分钟实现你的互联网+

300万网站
小程序发布量行业领先
YC孵化首家中国团队

总融资1750万美金

React SSR

Server-side Rendering

服务端渲染/同构直出

Browser: React Editor

Node.js: SSR HTML

同构:一份JS代码,前端和后端共享

Why do we need React SSR?

1. SEO and faster initial load

2. no code duplication

3,000,000+网站

网站数目庞大,每个网站都不同

Daily Deployment

每次部署都需要短时间内刷新cache

Crawler DDOS!

几分钟内爬虫扫描成千上万网站

高性能
高可用

React SSR 性能

React SSR is slow!

相比于传统模板渲染,SSR慢很多

renderToString

  • node is single-threaded
  • synchronous rendering
  • CPU bound

Useless client code

  • Virtual DOM
  • Synthetic event

SSR性能优化

测试目标 DivTree

import React from 'react'

// <DivTree depth={5} breadth={5} /> => 5^5 = 3125 React components

class DivTree extends React.Component {
  render() {
    const { depth, breadth } = this.props

    if (depth <= 0) {
      return <div>{'content'}</div>
    }

    let children = []
    for (let i = 0; i < breadth; i++) {
      children.push(<DivTree key={i} depth={depth - 1} breadth={breadth} />)
    }
    return <div>{children}</div>
  }
}



mean: ~400 ms using out of box React 15

Optimization Tricks #1

use NODE_ENV=production

Mean: 400ms -> 105ms

Optimization Tricks #2

Babel-transform:  transform-react-constant-elements

const Hr = () => {
  return <hr className="hr" />;
};
const _ref = <hr className="hr" />;

const Hr = () => {
  return _ref;
};

Static component only need to call React.createElment once

Optimization Tricks #3

Babel-transform:  transform-react-inline-elements

<Baz foo="bar" key="1"></Baz>;
babelHelpers.jsx(Baz, {
  foo: "bar"
}, "1");

/**
 * Instead of
 *
 * React.createElement(Baz, {
 *   foo: "bar",
 *   key: "1",
 * });
 */

Tricks #2 and #3 get us to 70ms

So far

NODE_ENV=production

Babel plugins

We cut from 400ms down to 70ms

Use at your own risk Optimization

Component caching

(state, props) => string

this is essentially what ReactSSR does

(state, props) => string

memoize it!

Typically use LRU cache

https://github.com/electrode-io/electrode-react-ssr-caching

Component caching

3,000,000+网站

每个网站内容和组件都大有不同

400ms -> 70ms

pretty good?

Oversize page!

CPU overload -> Machine died

  • node is single-threaded
  • synchronous rendering
  • CPU bound

User

Web Server

request

response

SSR

High Availability (HA)

高可用集群是指以减少服务中断时间为目的的服务器集群技术

User

Web Server

request

response

SSR

pm2 and autoscaling

Adding SSR Server Layer

  • Dedicated to rendering service
  • Fallback to client rendering if timeout
  • Provide high availability SSR service

User

Web Server

request

response

SSR

Server

Airbnb's Hypernova

https://github.com/airbnb/hypernova

User

Web Server

request

response

SSR

Server

入口Component

const React = require('react')
const renderReact = require('hypernova-react').renderReact

function MyComponent(props) {
  return <div>Hello, {props.name}!</div>
}

module.exports = renderReact('MyComponent.js', MyComponent)

Web Server

const Renderer = require('hypernova-client')

const renderer = new Renderer({
  url: 'http://localhost:3030/batch',
})

const jobs = {
  MyComponent: { name: req.query.name || 'Stranger' },
  Component2: { text: 'Hello World' },
}

renderer.render(jobs).then(html => res.send(html))

SSR Server

var hypernova = require('hypernova/server')

hypernova({
  devMode: true,

  getComponent(name) {
    if (name === 'MyComponent.js') {
      return require('./app/assets/javascripts/MyComponent.js')
    }
    return null
  },

  port: 3030,
})
<div data-hypernova-key="MyComponent.js" data-hypernova-id="684d437b-dda0">
    <div data-reactroot="" data-reactid="1" data-react-checksum="255991580">
        <p data-reactid="2">Hello, FCC Chengdu</p>
    </div>
</div>

<script type="application/json" data-hypernova-key="MyComponent.js" data-hypernova-id="684d437b-dda0">
    <!--{ name: "FCC Chengdu" }-->
</script>
const React = require('react')
const renderReact = require('hypernova-react').renderReact

function MyComponent(props) {
  return <div>Hello, {props.name}!</div>
}

module.exports = renderReact('MyComponent.js', MyComponent)

Component 

SSR Succeed HTML

<div data-hypernova-key="MyComponent.js" data-hypernova-id="684d437b-dda0">
    <!-- SSR failed -->
</div>

<script type="application/json" data-hypernova-key="MyComponent.js" data-hypernova-id="684d437b-dda0">
    <!--{ name: "FCC Chengdu" }-->
</script>

SSR Failure HTML

hypernova-powered HA

  • Dedicated to rendering
  • Fallback to client rendering if timeout
  • Provide high availability SSR service

...

我们成功的把前端的问题交给了后端处理

Cluster

Cluster of small servers with autoscaling policy

Serverless

Event triggered serverless computing service

 

AWS Lambda

Alicloud Function Compute

Serverless

Event triggered serverless computing service

Serverless

Cluster

with Autoscaling

总结

React SSR is slow

  • node is single-threaded
  • synchronous rendering
  • CPU bound
  • Useless client code

Tricks to Optimize

  • NODE_ENV=production
  • transform-react-constant-elements
  • transform-react-inline-elements
  • these tricks get us from 400ms -> 70ms

SSR Service to Provide HA

  • Separate SSR into a service
    • Graceful fallback
    • High availability
  • Airbnb's hypernova
  • Cluster or Serverless

Any further optimization?

CPU Hogging

Async Rendering

Suspense

Time Slicing

上线了招人

  • 帮助中国2000w中小企业上线
  • (微信|支付宝|w+)小程序
  • 前端性能优化
  • 创造出美的产品
  • 推荐成功送1000美金红包

jobs@strikingly.com

React SSR

By dfguo

React SSR

  • 3,519