Я использую react-boilerplate
(с react-router, sagas, express.js) для своего приложения React, и поверх него я добавил логику SSR, чтобы после получения HTTP-запроса он отображал компоненты реакции в строку на основе URL и отправляет строку HTML обратно клиенту.
В то время как рендеринг реакции происходит на стороне сервера, он также делает fetch
запрос через саги к некоторым API (до 5 конечных точек на основе URL-адреса), чтобы получить данные для компонентов, прежде чем он фактически отобразит компонент в строку.
Все работает отлично, если я делаю только несколько запросов к серверу Node одновременно, но как только я имитирую нагрузку из 100+ одновременных запросов, и он начинает их обрабатывать, то в какой-то момент он падает без каких-либо указаний на какое-либо исключение.
Когда я пытался отладить приложение, я заметил, что как только более 100 входящих запросов начинают обрабатываться сервером Node, он одновременно отправляет запросы к 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') });
}
});
}
Итак, я предполагаю, что что-то идет не так, когда он получает слишком много HTTP-запросов, которые, в свою очередь, генерируют еще больше запросов к конечным точкам API для рендеринга реагирующих компонентов.
Я заметил, что он блокирует цикл событий на 300 мс после renderAppToString()
для каждого клиентского запроса, поэтому, когда есть 100 одновременных запросов, он блокирует его примерно на 10 секунд. Хотя я не уверен, что это нормально или плохо.
Стоит ли пытаться ограничить одновременные запросы к серверу Node?
Я не смог найти много информации на тему сбоев SSR + Node. Поэтому я был бы признателен за любые предложения относительно того, где искать, чтобы определить проблему или возможные решения, если кто-то сталкивался с подобной проблемой в прошлом.