Вы открываете свое производственное приложение и замечаете, что оно останавливается. Интерфейс не отвечает. Время ожидания серверных API истекло. Запросы MongoDB выполняются бесконечно. Ваш почтовый ящик переполнен жалобами пользователей. Ваша команда собирается вместе, пытаясь разобраться в ситуации.
Были там? Да, я тоже.
Я старший Full Stack разработчик, и мне надоели приложения, которые хороши, пока вы используете их только как один пользователь или когда проблемное пространство простое, но затем просто вянут и разрушаются под реальным трафиком или немного более сложная задача.
Останьтесь со мной, и я расскажу вам, как я решил эти проблемы с помощью React, Node.js и MongoDB.
Я не просто дам вам еще одно старое простое руководство, я поделюсь историей. История о том, как решать реальные проблемы и как создать быстрое и масштабируемое приложение, способное выдержать испытание временем и всем, что ему мешает.
1: Когда React стал узким местом
На моей работе мы только что выпустили обновление для нашего веб-приложения, разработанного с помощью React. Мы были полны уверенности, полагая, что пользователи оценят новые функции.
Однако вскоре мы начали получать жалобы: приложение загружалось очень медленно, переходы подтормаживали, а пользователи все больше разочаровывались. Несмотря на то, что новые функции были полезны, они непреднамеренно привели к проблемам с производительностью. Наше расследование выявило проблему: приложение объединяло все свои компоненты в один пакет, что вынуждало пользователей загружать все при каждом доступе к приложению.
Исправление: мы реализовали очень полезную концепцию под названием «Отложенная загрузка». Я уже сталкивался с этой идеей раньше, но это было именно то, что нам нужно. Мы полностью обновили структуру приложения, гарантируя, что оно загружает только необходимые компоненты при необходимости.
Вот как мы реализовали это решение:
const Dashboard = React.lazy(() => import('./Dashboard')); const Profile = React.lazy(() => import('./Profile'));Loading...}>
Результат: Влияние этого изменения было поистине выдающимся. Мы увидели колоссальное сокращение нашего пакета на 30 %, и пользователи почувствовали гораздо более быструю начальную загрузку. Самое приятное то, что пользователи даже не подозревали, что некоторые части приложения все еще загружаются. Мы разумно использовали приостановку и показывали простое, ненавязчивое сообщение о загрузке.
2: Укрощение зверя государственного управления в React
Прошло несколько месяцев вперед, и наша команда разработчиков добилась успехов и выпустила множество новых функций. Но вместе с ростом мы случайно начали создавать то, что я называю более сложным приложением. Redux быстро стал обузой, а не помощником в упрощении простого взаимодействия.
Итак, я потратил некоторое время на создание POC для лучшей альтернативы. Я чертовски задокументировал это и провел несколько встреч по обмену знаниями о том, как может выглядеть этот подход. В конце концов мы всей группой решили попробовать React Hooks (и, в частности, useReducer) в качестве предложенного нами решения для управления состоянием, потому что в конечном итоге мы хотели более простой код и меньшую нагрузку во время выполнения, которую увеличивали накладные расходы в новых версиях Redux из-за множества небольших автономных приложений. говорится.
Последующая трансформация была поистине революционной. Мы обнаружили, что заменили десятки строк шаблонного кода краткой и простой для понимания логикой перехватчиков. Вот наглядный пример того, как мы реализовали этот новый подход:
const initialState = { count: 0 }; function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } } const CounterContext = React.createContext(); function CounterProvider({ children }) { const [state, dispatch] = useReducer(reducer, initialState); return ({children} ); }
Результат: Влияние этого перехода было глубоким и далеко идущим. Наше приложение стало значительно более предсказуемым и понятным. Кодовая база, ставшая теперь более компактной и интуитивно понятной, позволила нашей команде выполнять итерации гораздо быстрее. Возможно, самое важное то, что наши младшие разработчики сообщили о заметном улучшении своих способностей ориентироваться в кодовой базе и понимать ее. Конечным результатом стала беспроигрышная ситуация: меньше кода, который нужно поддерживать, меньше ошибок, которые нужно устранять, и заметно более счастливая и продуктивная команда разработчиков.
3: Победа на поле битвы с серверной частью — оптимизация API-интерфейсов Node.js для достижения максимальной производительности
Хотя нам удалось внести множество улучшений в наш интерфейс, вскоре после этого у нас возникло множество проблем с серверной частью. Производительность нашего API стала ужасной, и лишь немногие конечные точки начали работать ужасно. Эти конечные точки совершают последовательность вызовов к различным сторонним службам, и с ростом базы пользователей система не может справиться с этой нагрузкой.
Совершенно очевидно, что было не так: мы НЕ были параллельными! т. е. запросы к каждой конечной точке обрабатывались последовательно, т. е. каждый следующий вызов будет ждать завершения предыдущего вызова. В такой крупномасштабной (сотни тысяч запросов) системе это оказалось катастрофой.
Решение. Чтобы исправить это, мы решили переписать большую часть нашего кода и использовать возможности Promise.all() для одновременного выполнения запросов API. Это означает, что вы запускаете несколько запросов, и вам не нужно ждать завершения каждого вызова, чтобы запустить следующий.
Для этого мы не запускаем вызов API, не дожидаемся его завершения, не делаем еще один и так далее…
Вместо того, чтобы просто использовать Promise.all(), все запускалось сразу и гораздо быстрее.
Вот как мы реализовали это решение:
const getUserData = async () => { const [profile, posts, comments] = await Promise.all([ fetch('/api/profile'), fetch('/api/posts'), fetch('/api/comments') ]); return { profile, posts, comments }; };
Результат: Эффект от этой оптимизации был немедленным и существенным. Мы наблюдали значительное сокращение времени отклика на 50 %, а наша серверная часть продемонстрировала значительно улучшенную устойчивость под большой нагрузкой. Пользователи больше не испытывали неприятных задержек, и мы увидели резкое снижение количества тайм-аутов сервера. Это усовершенствование не только улучшило взаимодействие с пользователем, но и позволило нашей системе обрабатывать гораздо больший объем запросов без ущерба для производительности.
4: Квест MongoDB — укрощение информационного зверя
По мере того как наше приложение набирало обороты, а наша пользовательская база росла на порядки, нам пришлось столкнуться с новым препятствием: как масштабировать данные? Наш когда-то отзывчивый экземпляр MongoDB начал захлебываться при работе с миллионами документов. Запросы, которые раньше выполнялись в миллисекундах, выполнялись за секунды или истекало время ожидания.
Мы потратили несколько дней на изучение инструментов анализа производительности MongoDB и выявили главного злодея: неиндексированные запросы. Некоторые из наших наиболее распространенных запросов (например, запросы профилей пользователей) касались сканирования целых коллекций, для которых можно было использовать надежные индексы.
Решение: Имея под рукой информацию, мы знали, что все, что нам нужно сделать, — это создать составные индексы для наиболее востребованных полей, и что это навсегда сократит время поиска в теле нашей базы данных. Вот как мы это сделали с полями «имя пользователя» и «электронная почта».
db.users.createIndex({ "username": 1, "email": 1 });
Результат: Эффект от этой оптимизации был поистине выдающимся. Запросы, выполнение которых раньше занимало до 2 секунд, теперь выполнялись менее чем за 200 миллисекунд — десятикратное улучшение производительности. Наша база данных снова стала быстрее реагировать, что позволяет нам обрабатывать значительно больший объем трафика без каких-либо заметных замедлений.
Однако на этом мы не остановились. Понимая, что траектория нашего быстрого роста, вероятно, продолжится, мы приняли упреждающие меры для обеспечения долгосрочной масштабируемости. Мы внедрили шардинг для распределения наших данных по нескольким серверам. Это стратегическое решение позволило нам масштабироваться по горизонтали, гарантируя, что наши возможности обработки данных росли вместе с расширяющейся базой пользователей.
5. Использование микросервисов — решение проблемы масштабируемости
Поскольку наша база пользователей продолжала увеличиваться, становилось все более очевидным, что нам необходимо не только масштабировать нашу инфраструктуру, но и развивать наше приложение, чтобы иметь возможность уверенно масштабироваться. Монолитная архитектура хорошо нам подошла, когда мы были небольшой командой, но со временем она стала довольно громоздкой. Мы знали, что нам нужно сделать рывок и начать переход к архитектуре микросервисов — задача пугающая для любой команды инженеров, но с большим потенциалом масштабируемости и надежности.
Одной из самых больших проблем была связь между сервисами. HTTP-запросы действительно не работают в нашем случае, и это оставило нас с еще одним узким местом в системе, поскольку огромное количество операций беспокойно ждало ответа и убивало программу, если нужно было слишком много сделать. На этом этапе мы поняли, что использование RabbitMQ является очевидным ответом, поэтому мы применили его, не слишком задумываясь.
Вот как мы реализовали это решение:
const amqp = require('amqplib/callback_api'); amqp.connect('amqp://localhost', (err, conn) => { conn.createChannel((err, ch) => { const queue = 'task_queue'; const msg = 'Hello World'; ch.assertQueue(queue, { durable: true }); ch.sendToQueue(queue, Buffer.from(msg), { persistent: true }); console.log(`Sent ${msg}`); }); });
Результат: Сам переход вместе с общением через RabbitMQ выглядел с нашей точки зрения как волшебство… и цифры это подтверждали!!! Мы стали счастливыми обладателями слабосвязанных микросервисов, где каждый сервис можно масштабировать самостоятельно. Внезапно реальные всплески трафика в конкретной DNS-зоне не вызывали опасений, что система выйдет из строя (независимо от того, какая операция службы задает одно и то же, потому что они всегда каскадные), но работали хорошо, поскольку оставшиеся части/операции просто подняли руки, спокойно говоря: « Я могу спать, моя дорогая». Обслуживание также стало проще и менее проблематичным, а добавление новых функций или обновлений стало более быстрым и более уверенным в работе.
Вывод: прокладываем курс на будущие инновации
Каждый шаг в этом захватывающем путешествии был уроком, напоминавшим нам, что полноценная разработка — это больше, чем написание кода. Это понимание и последующее решение сложных взаимосвязанных проблем — от ускорения наших интерфейсов и построения серверов, способных противостоять сбоям, до работы с базами данных, которые масштабируются по мере роста вашей пользовательской базы.
Что касается второй половины 2024 года и последующих лет, то растущий спрос на веб-приложения не будет замедляться. Если мы сосредоточимся на создании масштабируемых, оптимизированных по производительности и хорошо спроектированных приложений, то мы сможем решить любую проблему сегодня — и воспользоваться преимуществами других проблем в нашем будущем. Этот реальный опыт сильно повлиял на мой подход к полнофункциональной разработке — и мне не терпится увидеть, как эти влияния будут продолжать подталкивать нашу отрасль!
А как насчет тебя? Сталкивались ли вы с подобными препятствиями или вам повезло с другими творческими способами преодоления этих проблем? Мне бы хотелось услышать ваши истории или идеи — дайте мне знать в комментариях или свяжитесь со мной!
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3