Хорошая статья. Мне Duolingo понравился и нравится до сих пор тем, что с ним именно тренируешь грамматику и составление слов в предложения (какие слоги где использовать, что куда ставить), и да, как подметил автор, приложение старается сделать так, чтобы автор сам расшифровал подобное правила, хотя можно порой и мануал прочитать к уроку. Единственный минус для меня состоял в том, что Duolingo - это про короткие предложения, поэтому как-только начиналась непрерывная речь или большой кусок текста то все, мозг не мог удерживать контекст, так как он привык к коротким фразам, без контекста. Поэтому да, согласен с автором, что Duolingo хорош как дополненительный инструмент. Для больших текстов с контекстом можно искать упражнения по аудированию (где что-то рассказывают 2-3 минуты), а для действительно частых и полезных фраз (которым Дуо, как подмечено в статье, не учит) можно завести онлайн друга и слушать, как он соединяет предложения, какие слова паразиты использует и тд, чтобы самим брать это на вооружение.
Пример, описанный в начале статьи, имеет ряд допущений, т.к. главный смысл был в том, чтобы показать, что нам нужна та архитектура, где все будут подчинятся одному источнику правды, чтобы избежать любых конфликтов, т.к. любая случайная рассинхронизация выльется в разные вычисления физики движка на разных машинах.
Согласен, хорошая замена моему велосипеду в виде передачи строк, однако я об этом способе узнал только совсем недавно и лично его не проверял, может там есть свои моменты (буду рад услышать чей-то опыт).
У вас (насколько я понял из описания) отдельный процесс для каждого подключения, которые складывают команды от игрока в очередь, откуда её процесс игры забирает.
Ну, это скорее потоки, нежели чем процессы (процессом тут является сама игровая сессия, в которой потоки игры и игроков, а вернее их сокетов крутятся), но сама мысль верная, да.
ну и все корутины имеют общую память и очередь не нужна
Вау, а вот это кстати интересеный момент, действительно можно было в каком-то плане упростить архитектуру, поскольку все корутины в одном потоке, а значит действительно им должно быть гораздо легче общаться. Хорошая идея.
я имел в виду конкретно ваше сжатие информации от клиента из JSON в строку "110000", а ведь это просто битовое представление цифры 48.
Аа, ну да, интересный способ :) Но это то, что клиент передает серверу (нажатие клавиш), эта строка и так очень маленькая. А вот сервер передаёт клиенту уже большой массив данных (`1.2.0.0.800.0.10.20.0.5;1.3.1200.0.2000.0.10.20.0.5;1.4.0.1200.2000.1200.10.20.0.5;...), это координаты объектов, их радиус, наклон, айди раскраски и прочее, и я вот думал, как такую строку представить в виде int (binary) :D
Не воспринимайте меня как специалиста в этой области, я просто люблю асинхронное программирование.
Оно и видно :) Если решите написать свой игровой сервер с асинхронным подходом, желаю только успеха!
Спасибо огромное! Обязательно почитаю про Stackless Python и спасибо за наводку касаемо Go. По поводу UDP также согласен, стоило и его попробовать перед TCP.
По поводу asyncio, я пробовал писать сервера на асинхронных Python фреймворках (aiohttp и FastAPI) и, если честно, могу согласится, что это весело, но затрудняюсь сказать, что это упрощает вещи, писать везде async/await и думать над каждый раз над тем, где корутина должна вернуть управление в event loop как-раз таки делали процесс разработки тяжелее и менее очевидным (как по мне). Ну и плюс тут нужно будет все библиотеки на asyncio переделать, может с built-in сокетами такое и получится, а вот на счёт того же pygame/pymunk не очень уверен. Да и как кстати от очередей в этом случае можно отказаться для меня тоже пока не очень очевидно. Впрочем, об использовании asyncio в данном проекте я в-прицнипе даже не задумывался, но как эксперимент - звучит весело.
На счёт того, чтобы отправлять binary вместо строки, я так понимаю тут идёт речь о допустим передаче b'110000' вместо '110000'? Как идея интересная, это получается не нужно будет декодить сообщение по получению, а можно сразу его использовать. Хотя вы уточнили, что речь идёт об int (binary) и я не очень понял, что тут имелось в виду, если передавать чисто цифры, то допустим в случае, когда серверу нужно передать состояние игры клиенту, там ведь много данных, там есть и float с плавающей точкой, и разделители между значениями и группой значений, тут непонятно, как такое обработать.
Тут возможно небольшое недопонимание. Сейчас в-принципе так оно и есть, на каждую сессию создается свой отдельный процесс описанный в "Архитектура игровой сессии: общая картина" и в нём, всё верно, 4 потока (3 io-bound сокета и сама cpu-bound игра). Процессы с сессиями никак друг с другом не общаются, они просто кладут информацию о себе в DynamoDB. Оркестрацией же этих процессов с сессиями занимается другой master процесс - main server, он хранит информацию о каждом процессе с сессией (её pid в системе) и спавнит новый процесс, если игрок просит новую сессию, или же даёт игроку выгрузку о всех сессиях с DynamoDB, если того интересует список текущих сессий. И вот эта вот связка - master процесс и процессы с сессиями работает на каждой машине в Auto Scaling группе.
Согласен, не часто встретишь такой коктейль для подобной задачи. Однако я объяснил все свои мотивы и, если не забуду, выложу в комментариях геймплей данной игры после анонса (как демонстрация работоспособности такого решения).
Хороший комментарий. Я, как объяснил в статье, не захотел заниматься излишней валидацией сообщений из-за UDP протокла, а также мне очень помогла гарантия порядка данных, которую предоставляет TCP. Однако в случае, который вы описали, действительно TCP уже может создавать ощутимые издержки, возможно я рассмотрю переход на UDP, если будут заметные проблемы с пингом у игроков с одного региона.
DynamoDB выходит бесплатно, AWS предоставляет 25 GB storage в рамках free tier (https://aws.amazon.com/dynamodb/). Действительно можно было самим поднять Redis, однако тогда я был бы ответственнен за его состояние и пришлось бы ему выделять отдельную машину, в то время как DynamoDB - это serverless сервис, его легко поднять (просто создать таблицу) и данные в нём легко можно посмотреть/изменить в интерфейсе AWS, это делает его очень удобным и быстрым решением в нашем случае.
Абсолютно верное замечание. Действительно через трюк с распаралелливанием самих процессов можно обойти GIL ограничение в Python и добиться более высокой производительности CPU. Я попытался в какой-то момент заменить использование multithreading на multiprocessing и получилось довольно успешно, однако всплыли другие моменты. Когда используешь multiprocessing, то для организации общения процессов через очереди нужен отдельный процесс, который эти очереди будет контроллировать - multiprocessing.Manager(), то есть в итоге мы получаем 5 процессов (игра, сокет для 1-го игрока, 2-го, 3-го и менеджер очередей, чтобы процессы могли обмениваться информацией). Однако каждый процесс - это целый интерпретатор питона, а это гораздо большее потребление как CPU, так и оперативной памяти, поэтому в целях экономии ресурсов (чтобы арендовать более слабые виртуальные машины) я остался на multithreading, поскольку оптимизации заметной глазу не получил, однако эксперимент сам удался, поэтому если бы сервер перестал справлятся, а с Python не хотелось бы уходить, то именно такой способ мог бы помочь, всё верно.
Без сарказма было бы более информативно, пока я так и не понял, в чём именно тут упрёк.
и мы попросим повара не использовать просроченные продукты
Это какой-то намёк на то, что AWS Shield уже стал deprecated и есть решения получше?
Я понимаю, что защита от DDoS тема довольно сложная и есть разные способы защитить себя от разных видов атак в AWS. По идее базовая защита уже должна быть включена благодаря AWS Shield Standard (https://docs.aws.amazon.com/waf/latest/developerguide/ddos-standard-summary.html) "All AWS customers benefit from the automatic protection of Shield Standard, at no additional charge. Shield Standard defends against the most common, frequently occurring network and transport layer DDoS attacks that target your website or applications". Вроде бы то, что нужно, так как мой игровой сервер как раз работает на TCP протоколе и должен покрываться данной защитой, однако неизвестно, когда эта защита работает, как она работает и когда именно она меня защитит. На странице AWS Shield Pricing (https://aws.amazon.com/shield/pricing/) можно и вовсе увидеть, что "AWS Shield Standard is automatically enabled when you use AWS services like Elastic Load Balancing (ELB), Application Load Balancer, Amazon CloudFront and Amazon Route 53.", то есть я даже не могу быть уверен, что данная защита покрывает мои EC2 машины, к которым у меня происходит прямое подключение, не через ELB. Поэтому для явной защиты от DDoS я все-таки пока могу полагаться только на AWS Shield Advanced, который и подключается по подписке за 3000 долларов в месяц, отправляет свою активность в CloudWatch и явно защищает EC2 машины (https://aws.amazon.com/shield/pricing/) "AWS Shield Advanced is a paid service that provides additional protections for internet-facing applications running on Amazon Elastic Compute Cloud (EC2), Elastic Load Balancing (ELB), Amazon CloudFront, AWS Global Accelerator, and Amazon Route 53.".
Godot действительно является неплохой альтернативой для нашего случая, но выбор пал на Unity ввиду уже имеющегося у меня опыта и давно сложившегося большого коммьюнити (легко гуглить возникшие проблемы). Также у Unity много готовых решений, например:
У нас на проекте есть саунд дизайнер и мы решили использовать Wwise как audio middleware, чтобы мне, как программисту, не писать логику затухания звуков, лупов и прочего, чтобы этим занимался сам саунд дизайнер. И у того же Wwise из коробки легкая интеграция с Unity (дополняется интерфейс в Unity, автоматически создаются необходимые файлы и сам Wwise проект).
Есть Unity Asset Store, где можно купить такие решения как лаунчер. Так как мы собираемся дистрибьютить игру через Game Jolt/Itch.io, то нужно как-то самим позаботиться об обновлении клиента (не скачивать же игрокам сотни мегабайт из-за каждого малого патча), а самим лаунчер изобретать тоже дело затратное по времени. В итоге просто приобрел готовое решение в Unity Asset Store и легко встроил его в свой проект. Благодаря большому коммьюнити выбор таких решений довольно велик.
Согласен, использование Python для параллельного программирования, а уж тем более для создания сервера real-time игры, где важна производительность - дело довольно сомнительное, так как Python в его дефолтной CPython реализации использует только одно ядро. Однако, в виду моего большого опыта работы с Python, мне изначально хотелось сделать все с его помощью, я начал изучать такие библиотеки как pygame и pymunk и пробовать сделать свои первые клиент и сервер. И действительно на Python я не смог далеко уехать. И проблема пришла не на сервере, клиент перестал справлятся со спрайтами, стал долго их загружать, а создавать эффекты, анимации с помощью сил Python стали огромным ударом по производительности, в итоге ради клиента перешлось перейти на Untiy C#, который облегчил разработку клиента и увеличил производительность в разы. А вот сервер со своими задачами всё ещё справлялся, но оно и понятно, наш проект - это 2D экшн с небольшой ареной до 3-х игроков, было бы что-то посложнее и тут бы Python не справился, пришлось бы также переходить на другой язык, но архитектура, описанная в статье, осталась бы той же. Я пробовал перейти на реализации, где нет GIL, такие как Jython, однако тут возникли проблемы с установкой нужных мне библиотек, поэтому остался на CPython.
Хорошая статья. Мне Duolingo понравился и нравится до сих пор тем, что с ним именно тренируешь грамматику и составление слов в предложения (какие слоги где использовать, что куда ставить), и да, как подметил автор, приложение старается сделать так, чтобы автор сам расшифровал подобное правила, хотя можно порой и мануал прочитать к уроку. Единственный минус для меня состоял в том, что Duolingo - это про короткие предложения, поэтому как-только начиналась непрерывная речь или большой кусок текста то все, мозг не мог удерживать контекст, так как он привык к коротким фразам, без контекста. Поэтому да, согласен с автором, что Duolingo хорош как дополненительный инструмент. Для больших текстов с контекстом можно искать упражнения по аудированию (где что-то рассказывают 2-3 минуты), а для действительно частых и полезных фраз (которым Дуо, как подмечено в статье, не учит) можно завести онлайн друга и слушать, как он соединяет предложения, какие слова паразиты использует и тд, чтобы самим брать это на вооружение.
Пример, описанный в начале статьи, имеет ряд допущений, т.к. главный смысл был в том, чтобы показать, что нам нужна та архитектура, где все будут подчинятся одному источнику правды, чтобы избежать любых конфликтов, т.к. любая случайная рассинхронизация выльется в разные вычисления физики движка на разных машинах.
Согласен, хорошая замена моему велосипеду в виде передачи строк, однако я об этом способе узнал только совсем недавно и лично его не проверял, может там есть свои моменты (буду рад услышать чей-то опыт).
Ну, это скорее потоки, нежели чем процессы (процессом тут является сама игровая сессия, в которой потоки игры и игроков, а вернее их сокетов крутятся), но сама мысль верная, да.
Вау, а вот это кстати интересеный момент, действительно можно было в каком-то плане упростить архитектуру, поскольку все корутины в одном потоке, а значит действительно им должно быть гораздо легче общаться. Хорошая идея.
Аа, ну да, интересный способ :) Но это то, что клиент передает серверу (нажатие клавиш), эта строка и так очень маленькая. А вот сервер передаёт клиенту уже большой массив данных (`
1.2.0.0.800.0.10.20.0.5;1.3.1200.0.2000.0.10.20.0.5;1.4.0.1200.2000.1200.10.20.0.5;...
), это координаты объектов, их радиус, наклон, айди раскраски и прочее, и я вот думал, как такую строку представить в виде int (binary) :DОно и видно :) Если решите написать свой игровой сервер с асинхронным подходом, желаю только успеха!
Спасибо огромное! Обязательно почитаю про Stackless Python и спасибо за наводку касаемо Go. По поводу UDP также согласен, стоило и его попробовать перед TCP.
По поводу asyncio, я пробовал писать сервера на асинхронных Python фреймворках (aiohttp и FastAPI) и, если честно, могу согласится, что это весело, но затрудняюсь сказать, что это упрощает вещи, писать везде async/await и думать над каждый раз над тем, где корутина должна вернуть управление в event loop как-раз таки делали процесс разработки тяжелее и менее очевидным (как по мне). Ну и плюс тут нужно будет все библиотеки на asyncio переделать, может с built-in сокетами такое и получится, а вот на счёт того же pygame/pymunk не очень уверен. Да и как кстати от очередей в этом случае можно отказаться для меня тоже пока не очень очевидно. Впрочем, об использовании asyncio в данном проекте я в-прицнипе даже не задумывался, но как эксперимент - звучит весело.
На счёт того, чтобы отправлять binary вместо строки, я так понимаю тут идёт речь о допустим передаче
b'110000'
вместо'110000'
? Как идея интересная, это получается не нужно будет декодить сообщение по получению, а можно сразу его использовать. Хотя вы уточнили, что речь идёт об int (binary) и я не очень понял, что тут имелось в виду, если передавать чисто цифры, то допустим в случае, когда серверу нужно передать состояние игры клиенту, там ведь много данных, там есть и float с плавающей точкой, и разделители между значениями и группой значений, тут непонятно, как такое обработать.Тут возможно небольшое недопонимание. Сейчас в-принципе так оно и есть, на каждую сессию создается свой отдельный процесс описанный в "Архитектура игровой сессии: общая картина" и в нём, всё верно, 4 потока (3 io-bound сокета и сама cpu-bound игра). Процессы с сессиями никак друг с другом не общаются, они просто кладут информацию о себе в DynamoDB. Оркестрацией же этих процессов с сессиями занимается другой master процесс - main server, он хранит информацию о каждом процессе с сессией (её pid в системе) и спавнит новый процесс, если игрок просит новую сессию, или же даёт игроку выгрузку о всех сессиях с DynamoDB, если того интересует список текущих сессий. И вот эта вот связка - master процесс и процессы с сессиями работает на каждой машине в Auto Scaling группе.
Согласен, не часто встретишь такой коктейль для подобной задачи.
Однако я объяснил все свои мотивы и, если не забуду, выложу в комментариях геймплей данной игры после анонса (как демонстрация работоспособности такого решения).
Хороший комментарий. Я, как объяснил в статье, не захотел заниматься излишней валидацией сообщений из-за UDP протокла, а также мне очень помогла гарантия порядка данных, которую предоставляет TCP. Однако в случае, который вы описали, действительно TCP уже может создавать ощутимые издержки, возможно я рассмотрю переход на UDP, если будут заметные проблемы с пингом у игроков с одного региона.
DynamoDB выходит бесплатно, AWS предоставляет 25 GB storage в рамках free tier (https://aws.amazon.com/dynamodb/).
Действительно можно было самим поднять Redis, однако тогда я был бы ответственнен за его состояние и пришлось бы ему выделять отдельную машину, в то время как DynamoDB - это serverless сервис, его легко поднять (просто создать таблицу) и данные в нём легко можно посмотреть/изменить в интерфейсе AWS, это делает его очень удобным и быстрым решением в нашем случае.
Абсолютно верное замечание. Действительно через трюк с распаралелливанием самих процессов можно обойти GIL ограничение в Python и добиться более высокой производительности CPU.
Я попытался в какой-то момент заменить использование multithreading на multiprocessing и получилось довольно успешно, однако всплыли другие моменты. Когда используешь multiprocessing, то для организации общения процессов через очереди нужен отдельный процесс, который эти очереди будет контроллировать -
multiprocessing.Manager()
, то есть в итоге мы получаем 5 процессов (игра, сокет для 1-го игрока, 2-го, 3-го и менеджер очередей, чтобы процессы могли обмениваться информацией). Однако каждый процесс - это целый интерпретатор питона, а это гораздо большее потребление как CPU, так и оперативной памяти, поэтому в целях экономии ресурсов (чтобы арендовать более слабые виртуальные машины) я остался на multithreading, поскольку оптимизации заметной глазу не получил, однако эксперимент сам удался, поэтому если бы сервер перестал справлятся, а с Python не хотелось бы уходить, то именно такой способ мог бы помочь, всё верно.Без сарказма было бы более информативно, пока я так и не понял, в чём именно тут упрёк.
Это какой-то намёк на то, что AWS Shield уже стал deprecated и есть решения получше?
Я понимаю, что защита от DDoS тема довольно сложная и есть разные способы защитить себя от разных видов атак в AWS. По идее базовая защита уже должна быть включена благодаря AWS Shield Standard (https://docs.aws.amazon.com/waf/latest/developerguide/ddos-standard-summary.html) "All AWS customers benefit from the automatic protection of Shield Standard, at no additional charge. Shield Standard defends against the most common, frequently occurring network and transport layer DDoS attacks that target your website or applications". Вроде бы то, что нужно, так как мой игровой сервер как раз работает на TCP протоколе и должен покрываться данной защитой, однако неизвестно, когда эта защита работает, как она работает и когда именно она меня защитит. На странице AWS Shield Pricing (https://aws.amazon.com/shield/pricing/) можно и вовсе увидеть, что "AWS Shield Standard is automatically enabled when you use AWS services like Elastic Load Balancing (ELB), Application Load Balancer, Amazon CloudFront and Amazon Route 53.", то есть я даже не могу быть уверен, что данная защита покрывает мои EC2 машины, к которым у меня происходит прямое подключение, не через ELB. Поэтому для явной защиты от DDoS я все-таки пока могу полагаться только на AWS Shield Advanced, который и подключается по подписке за 3000 долларов в месяц, отправляет свою активность в CloudWatch и явно защищает EC2 машины (https://aws.amazon.com/shield/pricing/) "AWS Shield Advanced is a paid service that provides additional protections for internet-facing applications running on Amazon Elastic Compute Cloud (EC2), Elastic Load Balancing (ELB), Amazon CloudFront, AWS Global Accelerator, and Amazon Route 53.".
Godot действительно является неплохой альтернативой для нашего случая, но выбор пал на Unity ввиду уже имеющегося у меня опыта и давно сложившегося большого коммьюнити (легко гуглить возникшие проблемы).
Также у Unity много готовых решений, например:
У нас на проекте есть саунд дизайнер и мы решили использовать Wwise как audio middleware, чтобы мне, как программисту, не писать логику затухания звуков, лупов и прочего, чтобы этим занимался сам саунд дизайнер. И у того же Wwise из коробки легкая интеграция с Unity (дополняется интерфейс в Unity, автоматически создаются необходимые файлы и сам Wwise проект).
Есть Unity Asset Store, где можно купить такие решения как лаунчер. Так как мы собираемся дистрибьютить игру через Game Jolt/Itch.io, то нужно как-то самим позаботиться об обновлении клиента (не скачивать же игрокам сотни мегабайт из-за каждого малого патча), а самим лаунчер изобретать тоже дело затратное по времени. В итоге просто приобрел готовое решение в Unity Asset Store и легко встроил его в свой проект. Благодаря большому коммьюнити выбор таких решений довольно велик.
Согласен, использование Python для параллельного программирования, а уж тем более для создания сервера real-time игры, где важна производительность - дело довольно сомнительное, так как Python в его дефолтной CPython реализации использует только одно ядро.
Однако, в виду моего большого опыта работы с Python, мне изначально хотелось сделать все с его помощью, я начал изучать такие библиотеки как pygame и pymunk и пробовать сделать свои первые клиент и сервер.
И действительно на Python я не смог далеко уехать. И проблема пришла не на сервере, клиент перестал справлятся со спрайтами, стал долго их загружать, а создавать эффекты, анимации с помощью сил Python стали огромным ударом по производительности, в итоге ради клиента перешлось перейти на Untiy C#, который облегчил разработку клиента и увеличил производительность в разы. А вот сервер со своими задачами всё ещё справлялся, но оно и понятно, наш проект - это 2D экшн с небольшой ареной до 3-х игроков, было бы что-то посложнее и тут бы Python не справился, пришлось бы также переходить на другой язык, но архитектура, описанная в статье, осталась бы той же.
Я пробовал перейти на реализации, где нет GIL, такие как Jython, однако тут возникли проблемы с установкой нужных мне библиотек, поэтому остался на CPython.