Со временем мы многому научились на своих ошибках и практиках при создании наших игр. В настоящее время у нас есть два основных мобильных приложения: CallBreak и Marriage. Мы рады обслуживать 1,3 млн пользователей. В ближайшие годы мы надеемся привлечь и связать больше друзей и семей с помощью карточных игр. Однако скоро мы удивим вас играми, отличными от карт. (Интересные вещи на конвейере)

Поскольку мы начинаем наш 7-й год, мы хотели бы поделиться нашим семи основным опытом обучения за эти годы.

JavaScript > Flow > TypeScript

JavaScript стал повсеместным языком, используемым для создания приложений всех типов. В Bhoos мы используем его для создания широкого спектра приложений, от интерфейсных и нативных приложений до серверных служб и внутренних инструментов.

По мере усложнения наших приложений мы сталкивались с проблемами обновления и поддержки безошибочного кода. Динамическая типизация JavaScript затрудняла обнаружение ошибок типов во время разработки, которые можно было обнаружить только во время выполнения, что приводило к сбоям для наших пользователей. Чтобы решить эту проблему, мы интегрировали инструмент статической проверки типов в наш процесс разработки. Это помогает выявлять ошибки на ранних этапах процесса разработки и упрощает анализ кода и его сопровождение.

Изначально мы использовали Flow от Facebook, так как он уже был интегрирован в нашу экосистему React. Тем временем TypeScript также набирал популярность. TypeScript добавил привлекательные функции, такие как интерфейсы, пространства имен, декораторы и асинхронность/ожидание, а также аннотацию типа. Это, наряду с его более крупной и активной экосистемой с большим количеством библиотек, инструментов и интеграций, побудило нас принять TypeScript вместо Flow.

Реагировать на крючки

Прежде чем реагировать на хуки, мы использовали компоненты на основе классов для разработки приложений. Мы столкнулись с двумя серьезными проблемами. Первая проблема заключается в том, что нам нужно было создать одинаковую логику для компонентов, которые можно было бы повторно использовать в других компонентах. Во-вторых, бурение и визуализация собственности были трудными. Чтобы преодолеть эти проблемы, мы использовали шаблоны проектирования, которые усложняли чтение, написание, улучшение, сопровождение и совместное перемещение кода.

С помощью React Hooks, таких как useContext, мы могли легко выполнять сверление и рендеринг реквизита. Это делает код короче и проще для понимания. Встроенные хуки реагирования уменьшают сложность кода. Кроме того, можно отделить логическую часть, чтобы использовать ее в нескольких компонентах, что уменьшает дублирование кода.

Встроенные хуки, такие как хуки useState, useEffect, useRef и useContext, помогли создать настраиваемые хуки. Пользовательские хуки помогают разделять проблемы, извлекая логику, связанную с конкретной проблемой. Хуки могут быть только логическими частями, поэтому их можно повторно использовать на любой платформе. (Оба мобильных приложения, т. е. нативные и веб-приложения React, React Js).

Поскольку хуки — это единицы логики, их можно тестировать отдельно. Более того, возможно составление нескольких хуков. Можно создать несколько хуков под хуком, чтобы создать или решить любую сложную проблему и поведение.

Например, хук, управляющий состоянием, и другой пользовательский хук, выполняющий анимацию.

UDP-связь

В Bhoos мы обычно не используем легкодоступные библиотеки и сервисы. Вместо этого мы создаем что-то свое, что мы можем настроить и улучшить по мере необходимости. При разработке игр мы впервые подумали о Websocket для наших многопользовательских игр. Однако режим точки доступа в самой игре требовал, чтобы одно из устройств выступало в качестве сервера. Нам нужно будет реализовать WebSocket Server внутри самого мобильного устройства. Это было бы огромной задачей, если бы мы делали это сами, и мы не могли найти достойный вариант для мобильных устройств. Итак, нашим следующим вариантом было использование протокола связи более низкого уровня — TCP или UDP, что позволило бы нам написать собственный сервер с минимальными функциями. Мы выбрали UDP, потому что:

  • Он поддерживает вещание в локальной сети, что улучшит работу пользователей с точками доступа.
  • Это намного проще, но это ненадежный протокол связи. Нам было проще создать надежность вокруг UDP, поскольку наш игровой движок уже был построен с использованием шаблона проектирования управления состоянием, основанного на действиях, который уже включал в себя последовательность и надежность, поддерживать это через UDP было намного проще.
  • Это очень быстро и использует намного меньше ресурсов. Имея всего 2 ГБ ОЗУ и 2 виртуальных процессора, мы одновременно обслуживали около 10 000 пользователей в нашей Брачной игре.

Серверы на основе сокетов

Мы использовали RESTful API для связи между нашими играми Call Break и Marriage и сервером. После раундов нагрузочного тестирования и анализа пламенных графиков мы столкнулись с проблемой. Express, который обслуживает API, сам потреблял много времени выполнения во время каждого вызова API. Для обычных вызовов, таких как синхронизация данных после завершения игры, промежуточное ПО Express имеет больше накладных расходов, чем выполнение остальных функций. Это было основной причиной перехода от REST к веб-сокетам.

Производительность: библиотека uWebSockets работала молниеносно. Библиотека на основе Typescript демонстрирует обслуживание почти миллиона открытых сокетов на старом потребительском ноутбуке. По сравнению с Express, накладные расходы, добавляемые этой библиотекой, практически отсутствовали.

Двунаправленность: REST управляется запросом со стороны клиента. Мы использовали различные способы отправки событий, инициированных сервером. Переключившись на двунаправленные веб-сокеты, мы упрощаем многие из таких потребностей и обходим требование использования внешней платформы, такой как firebase. Это согласуется с нашей философией минимизации использования сторонних библиотек и сервисов, за исключением случаев крайней необходимости.

Состояние: REST не имеет состояния, что проще для большинства приложений. Однако многие из наших вариантов дизайна привели нас к серверной структуре, которая казалась не имеющей состояния для отдельных вызовов. Тем не менее, с точки зрения семантики, все еще существовало некоторое состояние потока. Например, вы не могли выполнять другие вызовы API без сеанса, а это означало, что вход в систему был обязательным условием практически для любого другого действия. Конечно, мы могли бы иметь необходимую информацию для выполнения API в каждом отдельном вызове, но это добавило бы еще больше накладных расходов.

Время покажет, как будет происходить переход с REST на Websocket, но мы рады этому изменению.

Монорепо в систему с несколькими репо

Монрепо (сокращение от «монолитный репозиторий») — это репозиторий, содержащий несколько проектов в одном репозитории. Мультирепозиторий (сокращение от множественного репозитория) — это репозиторий с контролем версий, который содержит отдельные репозитории для каждого проекта.

Вот некоторые из преимуществ мультирепо:

  • С большой командой работать проще, так как разные члены команды могут работать над разными репозиториями одновременно, не мешая друг другу.
  • Развернуть код проще, так как каждый репозиторий можно развернуть отдельно.
  • Его легче масштабировать, поскольку каждым репозиторием можно управлять и масштабировать независимо.

Сервер управления и Bhoos CLI

Скорость и уверенность в процессе разработки и развертывания имеют для нас решающее значение. Однако во время разработки приложение может зависеть от нескольких внутренних пакетов, и в разработке участвуют несколько человек. Было бы легко опубликовать изменения в пакетах в репозитории с увеличенным номером версии. Однако из-за частых версий и промежуточных версий с ошибками это может показаться пугающим. Кроме того, у всех не должно быть прав на публикацию пакетов, потому что промежуточные версии с ошибками могут попасть в следующую производственную сборку из-за спецификаций зависимостей, таких как: ^1.0.1. Отправка обновлений тоже пугает, что, если есть какие-то проблемы, связанные с запущенной средой? Что делать, если приложение даже не запускается?

Развертывание в рабочей среде — это не конец процесса. Нам нужно постоянно проверять серверы и приложения, чтобы они не зависали и могли справляться с нагрузкой.

Management Server и интерфейс командной строки Bhoos решают эти проблемы. Они обеспечивают механизм для

  • Публикация и размещение пакетов npm и узловых приложений в комплекте со всеми зависимостями (например, реестр npm)
  • Контролируйте другие серверы, собирайте журналы и уведомляйте о проблемах на серверах или ошибках в приложениях.
  • Мягко обновляйте запущенные серверные приложения, не нарушая существующие соединения.

Это повышает скорость разработки и развертывания, повышает уверенность в развертывании и позволяет быстро откатиться в случае возникновения проблем.

Распределенные и масштабируемые серверы

Наша бэкенд-система состоит из двух типов серверов — серверов Game API и серверов Game Engine. Мы заботимся о высокой доступности и отказоустойчивости обоих этих серверов.

API-серверы игр

Как упоминалось ранее, большая часть внеигровых запросов раньше обрабатывалась серверами RESTful API. Мы переходим на серверы на основе сокетов. Серверы RESTful довольно часто распределяются путем добавления балансировщика нагрузки и перенаправления запросов. Горизонтальное масштабирование веб-сокетов — это немного более сложная задача, но все же достижимая благодаря слою виртуальных сокетов, выступающему в роли балансировщика нагрузки. Используя один и тот же кеш и базу данных, мы можем обслуживать запросы, используя несколько серверов. Они могут быть введены в эксплуатацию, выведены из эксплуатации и модернизированы по мере необходимости. Выводя из эксплуатации серверы более старых версий после выполнения всех входящих запросов и поддерживая обратную совместимость в общедоступных API, мы можем беспрепятственно обновлять наши серверы.

Серверы игрового движка

Серверы игрового движка отвечают за запуск наших многопользовательских онлайн-игр. Вместо того, чтобы использовать центральное хранилище для данных многопользовательской игры, мы храним данные только на сервере, на котором запущена игра. В этом сценарии мы редко сталкивались со случаями сбоев. Кроме того, это еще и рентабельно.

Если нагрузка на серверы API или движка резко возрастет, мы сможем плавно создавать новые экземпляры и использовать их для обслуживания запросов или управления многопользовательскими играми. Мы также экспериментируем с архитектурой распределенной базы данных. До сих пор мы не сталкивались с узкими местами в вертикально масштабируемом кэше и серверах баз данных.

Это сообщение изначально было опубликовано в Bhoos Blogs.