我使用react-boilerplate
(使用react-router,sagas,express.js)作为我的React应用程序,除此之外我还添加了SSR逻辑,以便在收到HTTP后请求它根据URL呈现将组件反应为字符串,并将HTML字符串发送回客户端。
虽然在服务器端发生了反应呈现,但它也会通过传真向fetch
请求某些API(基于URL的最多5个端点)来获取组件的数据,然后才能将组件实际呈现为字符串
如果我同时向Node服务器发出几个请求,那么一切都工作得很好,但是一旦我模拟了100多个并发请求的加载并且它开始处理它,那么在某些时候它会崩溃而没有任何异常的迹象。
我在尝试调试应用时注意到的是,一旦节点服务器开始处理100多个传入请求,它会同时向API发送请求,但在停止之前不会收到任何实际响应堆叠这些请求。
用于在服务器端呈现的代码:
async function renderHtmlDocument({ store, renderProps, sagasDone, assets, webpackDllNames }) {
// 1st render phase - triggers the sagas
renderAppToString(store, renderProps);
// send signal to sagas that we're done
store.dispatch(END);
// wait for all tasks to finish
await sagasDone();
// capture the state after the first render
const state = store.getState().toJS();
// prepare style sheet to collect generated css
const styleSheet = new ServerStyleSheet();
// 2nd render phase - the sagas triggered in the first phase are resolved by now
const appMarkup = renderAppToString(store, renderProps, styleSheet);
// capture the generated css
const css = styleSheet.getStyleElement();
const doc = renderToStaticMarkup(
<HtmlDocument
appMarkup={appMarkup}
lang={state.language.locale}
state={state}
head={Helmet.rewind()}
assets={assets}
css={css}
webpackDllNames={webpackDllNames}
/>
);
return `<!DOCTYPE html>\n${doc}`;
}
// The code that's executed by express.js for each request
function renderAppToStringAtLocation(url, { webpackDllNames = [], assets, lang }, callback) {
const memHistory = createMemoryHistory(url);
const store = createStore({}, memHistory);
syncHistoryWithStore(memHistory, store);
const routes = createRoutes(store);
const sagasDone = monitorSagas(store);
store.dispatch(changeLocale(lang));
match({ routes, location: url }, (error, redirectLocation, renderProps) => {
if (error) {
callback({ error });
} else if (renderProps) {
renderHtmlDocument({ store, renderProps, sagasDone, assets, webpackDllNames })
.then((html) => {
callback({ html });
})
.catch((e) => callback({ error: e }));
} else {
callback({ error: new Error('Unknown error') });
}
});
}
&#13;
所以我的假设是,一旦接收到太多的HTTP请求就会出现问题,这反过来会产生更多的请求到API端点来呈现反应组件。
我注意到它在renderAppToString()
之后为每个客户端请求阻止了300ms的事件循环,所以一旦有100个并发请求,它就会阻塞大约10秒。我不确定这是不是正常或坏事。
是否值得尝试限制对节点服务器的同时请求?
我无法找到有关SSR + Node崩溃主题的更多信息。因此,如果有人在过去经历过类似的问题,我会感谢任何关于在哪里查看以确定问题或可能的解决方案的建议。
答案 0 :(得分:3)
<强> 1。在群集中运行express
Node.js的单个实例在单个线程中运行。采取 多核系统的优势,用户有时会想要 启动一个Node.js进程集群来处理负载。
由于Node是单线程的,因此如果您正在初始化express,问题也可能出现在堆栈中较低的文件中。
在运行反应线程中通常没有提到的节点应用程序时,有许多最佳实践。
提高运行多核的服务器性能的简单解决方案是使用内置节点集群模块
https://nodejs.org/api/cluster.html
这将在您服务器的每个核心上启动应用程序的多个实例,从而为并发请求提供显着的性能提升(如果您有多核服务器)
有关快速表现的更多信息,请参阅 https://expressjs.com/en/advanced/best-practice-performance.html
您可能还想限制传入连接,因为当线程启动上下文切换响应时间快速下降时可以通过在应用程序前添加NGINX / HA Proxy之类的东西来完成
<强> 2。在将渲染变为字符串
之前,请等待商店保持水分您不希望在您的商店完成更新之前渲染您的布局,因为其他评论请注意这是渲染时线程的阻塞。
以下是从saga repo中获取的示例,其中显示了如何运行sagas而不需要渲染模板,直到它们全部解析为止
store.runSaga(rootSaga).done.then(() => {
console.log('sagas complete')
res.status(200).send(
layout(
renderToString(rootComp),
JSON.stringify(store.getState())
)
)
}).catch((e) => {
console.log(e.message)
res.status(500).send(e.message)
})
https://github.com/redux-saga/redux-saga/blob/master/examples/real-world/server.js
第3。确保正确设置节点环境
同时确保在捆绑/运行代码时正确使用NODE_ENV=production
,因为快递和反应优化
答案 1 :(得分:3)
在上图中,我正在做ReactDOM.hydrate(...)我也可以加载我的初始和必需状态并将其以水合物形式发送。
我已经编写了中间件文件,我正在使用此文件根据我应该发送哪个URL来响应哪个文件来决定。
以上是我的中间件文件,我创建了基于URL请求的文件的HTML字符串。然后我添加这个HTML字符串并使用express的res.render返回它。
上图是我将请求的URL路径与路径文件关联字典进行比较的地方。一旦找到它(即URL匹配),我使用ReactDOMserver渲染到字符串以将其转换为HTML。如上所述,此html可用于使用res.render发送带有句柄栏文件。
这样我就可以在使用MERN.io堆栈构建的大多数Web应用程序上进行SSR。
希望我的回答对您有所帮助,并为讨论撰写评论
答案 2 :(得分:2)
对renderToString()
的调用是同步的,因此它们在运行时阻塞线程。所以毫不奇怪,当你有100多个并发请求时,你的队列被阻塞了大约10秒钟。
编辑:有人指出React v16本身支持流式传输,但您需要使用renderToNodeStream()
方法将HTML流式传输到客户端。它应该返回与renderToString()
完全相同的字符串,但会对其进行流式传输,因此您不必等待在开始向客户端发送数据之前呈现完整的HTML。