User Tools

Site Tools


kg:dic_disdic_server_common

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

kg:dic_disdic_server_common [2011/11/26 09:09] (current)
Line 1: Line 1:
 +====== About the Server ======
 +For the sake of the you ppl this section written by plain old russian)
 +Ага, сейчас я попытаюсь объяснить главные вопросы по дизайну сервера,​ связанные с этим технические проблемы и решения. Многие моменты тут нетривиальны,​ как и в целом дизайн сетевых многопользовательских систем,​ но, надеюсь,​ кое что таки станет понятнее.
 +
 +===== Функционал =====
 +Что делает сервер?​
 +
 +  - Т1 - Хранит состояние мира и базу пользователей
 +  - Т2 - Обеспечивает возможность сетевого соеднинения с клиентом (инициируемое клиентом же)
 +  - Т3 - Принимает и обрабатывает команды от клиентов
 +  - Т4 - Отсылает клиенту изменения в мире
 +  - Т5 - Обрабатывает периодические события в мире
 +  - Т6 - Обеспечивает общение клиентов друг с другом посредством чата
 +
 +Итак, основополагающие характеристики решения:​
 +  - Р1 - Соответствие технического решения условиям запланированной нагрузки
 +  - Р2 - Масштабируемость решения (вертикальная,​ горизонтальная,​ динамическая),​ предсказуемость
 +  - Р3 - Надежность решения (failover, availability)
 +  - Р4 - Управляемость (deployment,​ maintainability)
 +  - Р5 - Доступность (минимизация простоя)
 +  - Р6 - Мониторинг (отслеживание процессов)
 +  - Р7 - Простота разработки
 +
 +Схема основных компонент сервера:​
 +(картинко)
 +
 +===== Общий сценарий взаимодействия клиента и сервера =====
 +Один из важных моментов дизайна - сервер является авторитетом по отношению к клиенту и принимает решения о правомерности всех команд поступающих от него. Клиент хранит некоторое,​ минимальное представление мира, но оно не является авторитетным. Поскольку представление клиента всегда запаздывает от актуального,​ желательно минимизировать этот объем, храня только то, что связанно с отображением. С другой стороны стоит задача минимизации нагрузки на сервер,​ т.е. частота запросов от клиента. ​
 +
 +Очевидный конфликт - либо усложнять сервер,​ хранить для каждого клиента его состояние и отсылать дельту изменений (черевато рассинхронизацией,​ нецелевыми расходами per client на сервере ) либо отсылать все состояние (актуальное для клиента) с подвариантом - сделать клиент ведущим в этом процессе.
 +
 +  * Старт
 +      * Загрузка конфигурации,​ automatic-discovery
 +      * Валидация данных,​ самодиагностика
 +      * Загрузка резидентных данных
 +  * Клиент устанавливает соединение с сервером
 +  * -> Сервер создает сессию для клиента,​ отсылает базовую информацию клиенту о аккаунте
 +  * Клиент запрашивает обновление видимой части мира
 +  * -> Сервер (проверив правомернось) отсылает видимый срез статической и динамической карты
 +  * Клиент отсылает команды-действия
 +  * -> Сервер проверяет,​ подтверждает и сохраняет действия в очередь команд
 +  * Клиент отсылает информационные запросы,​ не изменяющие состояние
 +  * -> Сервер отсылает актуальные данные мира
 +  * Сервер обрабатывает очередь команд от всех клиентов,​ отсылает клиенту обновления
 +  * -> Клиент принимает и отображает результаты выполнения команд
 +  * Сервер обновляет мир с течением времени,​ отсылает нотификации о изменениях
 +  * -> Клиент принимает к сведению,​ запрашивает обновления
 +
 +===== Наброски прикладного протокола =====
 +  - Клиент:​ Authenticate (credentials) ​
 +  - Сервер:​ Auth-result (client_id)
 +
 +  - Клиент:​ GetPlayerData(client_id)
 +  - Сервер:​ PlayerData(...)
 +
 +  .. и остальное
 +  ​
 +===== Обсуждение технических решений =====
 +А сейчас перейдем к обсуждению конкретных технических решений,​ их плюсов и минусов. К сожалению картина меняется каждый раз и все из нижеприведенных факторов так или иначе имеют значение.
 +
 +Пространство принятия решения в целом - [питон / jvm]. Питон все упрощает,​ но, в силу производительности тяготеет в сторону распределенного stateless решения (о чем будет речь позже) когда мы легко можем под нагрузкой добавить еще дополнительные instances серверов. Ява решения,​ наоборот,​ тяготеют к одному большому statefull решению,​ в силу производительности и хорошей многозадачности.
 +
 +==== Платформа для разработки ====
 +В этом забеге принимают участие следующие контестанты:​ python и несколько гостей из мира jvm:
 +
 +**Питон**
 +
 +Несмотря на свою динамическую натуру и неторопливость имеет следующие преимущества:​
 +  - +существуют библиотеки практически для всего
 +  - +простая установка и кроссплатформенность
 +  - +простота разработки и прототипирования
 +  - +сила и простота языка )
 +  - -+все же он интерпретируем,​ хотя и есть pypy который tracing jit
 +  - -+непривычность для некоторых слабо-типизируемых языков,​ необходимость покрытия тестами функционала
 +  - -GIL и сложности с многопоточностью (обычно решается в рамках отдельных процессов,​ что увеличивает нагрузку на систему)
 +  - -относительная неэффективность работы с большим объемом (бинарных) данных
 +
 +**Ява и ее друзья**
 +
 +Собственно на jvm как платформе существует уже много интересных решений. Для начала рассмотрим старую,​ добрую яву.
 +
 +  - +отличная поддержка в виде сторонних библиотек для всего
 +  - +простота для типичного разработчика из мира C++
 +  - +eclipse и другие ide c морем функционала
 +  - +производительность - где то не слишком далеко от c/c++
 +  - +кроссплатформенность - работает практически везде. даже в SIM-карте телефона
 +  - -verbosity - это один из самых страшно многословных языков. На каждый чих - класс, отдельный файл. Без IDE работать с таким объемом кода невозможно.
 +  - -энтерпрайзнутость - сама среда существования языка тяготеет к энтерпрайз решениям,​ over-engineering в чистом виде, мегабайтам кода, фреймворкам размером с динозавра
 +  - -требует компиляции и дружбы с такими вещами как ant и maven
 +
 +К нашему счастью на ее основе все больше появляется решений,​ вбирающих плюсы платформы и в тоже время компенсирущих недостатки -
 +
 +**Groovy/​Groovy++**
 +
 +Гость из JVM мира, разработка codehaus. Динамический язык, делающий мир явы куда более дружелюбным. ​
 +Ознакомиться можно тут: [http://​goo.gl/​LMc2K]
 +  - +простой и внятный язык с хорошим отношением количества текста к несомому функционалу
 +  - +reuse всего того что есть для явы
 +  - +элементарная разработка своих DSL
 +  - -+медленный как питон, но дело исправляет разработка нашего соотечественника Groovy++ (http://​code.google.com/​p/​groovypptest/​)
 +  - -+нетипизированный,​ но опять же groovy++
 +  - -+поддерживается в eclipse, но не так хорошо как скажем родная ява
 +  - +замечательные свои библиотеки для многих аспектов серверной разработки
 +  - +упрощенный deployment
 +
 +Грубо говоря Groovy++ это надстройка (точнее плагин к компилятору) для оригинального Groovy, которая вводит строгую типизацию,​ существенно улучшает производительность и вводит несколько полезных фич, оставляя при этом возможность использовать оригинальный язык.
 +
 +**Scala**
 +
 +То же JVM родственник но другого толка. [http://​www.scala-lang.org/​node/​104]
 +
 +  - +По verbosity ближе к groovy, точно не ява, хотя и выглядит местами похоже
 +  - -+Желание авторов языка сделать комбайн
 +  - +опять же работает со всем что есть для явы
 +  - +поддерживается эклипсом,​ т.е. де-факто IDE
 +  - +строгая,​ очень строгая типизация и упор на функциональщину
 +  - -строгая,​ строгая типизация. Иной раз за этими типами прячется программа
 +  - +производительность ~ яве
 +  - +опять же свои DSL из коробки
 +  - -+доступен для обычного C++ разработчика,​ практически шаблоны,​ классы,​ перегружаемые операторы,​ типы. После некоторого начального этапа с++ программист покрывается испариной и просится домой.
 +
 +**C++**
 +
 +Добавим для полноты картины.
 +
 +  - +Знаком с детства)
 +  - +Фактически наилучшее решение по железной производительности
 +  - +Привычно типизирован
 +  - +Половину насущных задач решает boost
 +  - -Verbose и полон управления памятью
 +  - -Рай для развития и прогрессирования синдрома Non Invented Here
 +  - -Из за бедноты стандартной библиотеки и отсутствия систематики спотыкается на месте и требует индивидуального подхода к каждому стороннему модулю
 +  - -Переносимость требует медитации над платформенно-зависимым кодом
 +  - -Отсутствует нормальная кросс-платформенная система сборки. Make, CMake - не предлагать. Waf - еще куда как, но это прощай VStudio.
 +  - -Нет ничего из нормального языкового инструментария. Последний стандарт пока еще толком не поддерживается а без него язык - динозавр соревнующийся по древности с коболом и фортраном.
 +  - -Коллекция Undefined Behaviors и неудачной,​ отданной на откуп железу модели памяти. ​
 +  - -Low level API уровня оси везде оставляют желать лучшего и скорейшей кончины их разработчикам
 +==== Вопросы архитектуры ====
 +Собственно не архитектуры-архитектуры,​ а архитектуры-композициию. Концептуальные моменты. Первый вопрос - это stateless vs stateful. ​
 +
 +**Stateless vs stateful**
 +
 +Итак, даем определение - сервер без состояния работает целиком с представлением в базе. Не имеет состояния,​ хотя и может что то кэшировать r/o. Буквально - если его прибить и рестартануть,​ то ничего плохого не произойдет. Какие плюсы / минусы?​
 +
 +  - +Горизонтальная масштабируемость инстансами - если capacity перевыполняется,​ просто спавним еще один сервер,​ а load-balancer перенаправляет часть нагрузки на него
 +  - +Надежность - все сложности с потерей данных,​ крешами итп уходят целиком на базу. А они, сюрприз-сюрприз,​ с этой мыслью и создаются. ​
 +  - +Дружелюбность к облачному хостингу - создав образ диска с настроенным сервером их можно поднимать и убивать в паравиртуализации как душе угодно. Более того, в том же Amazon EC2 есть возможность делать это автоматически в зависимости от нагрузки или по расписанию. И платить только за потребляемые ресурсы.
 +  - +Состояние локализовано,​ через стандартный интерфейс базы доступно для интроспекции
 +  - +Легкая миграция без остановки:​ изменился код - последовательно перезапускаем сервера (часть клиентов дисконнектится,​ но клиент сам восстанавливает соединение),​ поменялся формат базы(!) - оок, в новый сервер добавляем код миграции,​ если в новой базе нет - читаем со старой,​ пишем в новую. Заменяем последовательно сервера на новые, они же, в процессе работы перелопачивают базу в новый актуальный формат. Потом лишний код можно выпилить.
 +  - -Нетрадиционность разработки - stateless модель это больше из мира веба и энтерпрайзнутой опердени. База становится главной осью вокруг которой все вращается. Изменить объект - достать из базы, покрутить,​ покласть обратно. Ест-но количество операций которое можно себе позволить - ограниченно.
 +  - -База попадает под перекрестный обстрел - нужно знать/​уметь как ее масштабировать,​ оптимизировать запросы,​ делать репликацию,​ шардирование.
 +  - -Считается,​ что плохо сочетается с игровой логикой какую привыкли писать в десктоп сингле. Нельзя взять вот всеее эти объекты и сделать с ними что то осмысленное силами БД (да если и можно средствами хранимых процедур,​ не всегда разумно) - т.е. скажем пошаговые стратегии хорошо,​ а все интерактивное уже никак.
 +  - -Латентность - время реакции системы велико из за протяженности маршрута обработки. Но все что рейал-тайм дизайнится совсем по другим принципам.
 +
 +На другом поясе находится целиком stateful модель,​ которая похожа на типичный сингловый прообраз игры, с той лишь разницей что игроков там больше. Все в одном месте, все пользователи лезут туда же.
 +
 +  - +Относительная простота для заблудших в онлайн программистов
 +  - +Данные локальны и неважно насколько часто их требуется "​трогать"​ - ставь мьютекс и делай что хочешь
 +  - +Все в одном, база не нужна - можно прочесть состояние с диска при запуске и записать при выходе. До какого то порога проще предсказать боттлнеки
 +  - +Все шустро,​ маленькая латентность по отношению к клиенту
 +  - -В минусы можно записать практически все плюсы stateless архитектуры
 +  - -Нельзя перезапустить или мигрировать без полного останова
 +  - -Масштабирование упирается в один большой сервер и дальше - остается только надеяться найти забытый где то в коде sleep
 +  - -Надежность - может упасть со всем актуальным содержимым целиком. И привет.
 +
 +Кроме того возможны гибриды. Например в чисто stateless сервере запросто можно ввести shared cache который обеспечивает быстрый доступ к горячим данным быстрее базы. Или в stateful - присобачить базу как хранилище,​ хранить все у себя но сбрасывать изменения в базу по факту.
 +==== Соединение с клиентом (коммуникационный протокол) ====
 +В выборе коммуникационного протокола принимает участие много переменных,​ в тоже время выбор не особо и велик. Имеет смысл отталкиватся от уже работающих решений из мира web 2.0 - http как основа позволяет построить решение из существующих проверенных компонент. Собственно флеш, к примеру,​ поддерживает свой собственный RTMP протокол для RPC но он не особенно популярен,​ заточен под коммерческий сервер и существенно ограничивает гибкость. Прямое raw-socket соединение,​ хотя и возможно,​ но так же сильно ограничивает reuse существующих ингредиентов.
 +
 +HTTP же, по умолчанию,​ хорошо работает с текущей инфраструктурой сети, дружит с различными прокси и файрволами,​ имеет огромную базу готовых решений.
 +
 +Далее, выбор между устойчивым соединением и моделью когда клиент принудительно опрашивает сервер (и плеядой промежуточных решений).
 +
 +Pooling на основе http просто как две копейки. Грубо говоря клиент с некоторой периодичностью или по требованию каждый раз устанавливает новое http соединение с сервером,​ отсылая в http-транзакции свои команды и запросы и получая накопленные обновления от сервера. Ест-но сервер полностью пассивен в этом случае и не имеет возможности отослать клиенту данные сам по себе. ​
 +
 +**Poll / принудительный опрос**
 +
 +  - +Еще лучше интегрируется с интернетом,​ собственно ничем не отличается от того что делает браузер.
 +  - +Технически прост (по крайней мере со стороны клиента)
 +  - -Сервер вынужден сам имитировать транспортную сессию (т.к. мы не имеем постоянного соединения),​ ее expiration в случае внезапного отсоединения клиента
 +  - -Сервер должен заново обрабатывать установку соединения,​ все эти http-заголовки итп каждый раз когда клиент отсылает команду ​
 +  - +При низкой активности клиентов (количестве действий в секунду) мы экономим на сервере число активных подсоединений
 +
 +**Persistent**
 +
 +  - +Нормальный двунаправленный протокол,​ каждый может отослать peer'​у что он хочет.
 +  - -Сервер должен уметь держать соединения по количеству *всех* пользователей,​ даже если они не выполняют активных действий.
 +  - +Сервер имеет обычную транспортную сессию для клиента благодаря устойчивому соединению,​ не нужно ее проверять на каждый запрос от клиента.
 +  - +Экономим траффик
 +
 +Очевидно что второй случай более предпочтителен,​ хотя может быть и более затратным. Основной для него я решил выбрать WebSockets как молодой,​ но подходящий для нас транспортный протокол. В настоящее время его поддержка бурно развивается в клиентских и серверных решениях,​ он удачно маскируется под http и в тоже время является нормальным двусторонним устойчивым протоколом. ​
 +
 +Фактически websockets представляет собой http-handshake и последующий апгрейд соединения до персистентного. Сам протокол достаточно прост, имеет родной lightweight framing и имеет уже довольно неплохую поддержку со стороны языков и платформ.
 +
 +
 +==== DB backend ====
 +Несоменно,​ это тоже один из краеугольных технических аспектов. Надежность и производительность решения напрямую соотносятся с его выбором. Необходимо отметить,​ что в силу структуры проекта выбор не одномерный. Фактически требуется два вида БД - основная,​ в которой живет мир целиком и некое NoSQL решение для кэша. Словом,​ это не взаимоисключающие варианты.
 +
 +**MySQL**
 +
 +Классическая база, обеспечивающая приемлимую производиетельность с InnoDB хранилищем + XtraDB патчи. Транзакционность,​ SQL - все как везде. Местами кривоватый или скажем слишком простой оптимизатор запросов,​ но скорее всего это не касается нашей области применения. Можно даже заменить на Postgres, от этого сильно ничего не изменится. В принципе гарантированная производительность,​ поддерживает репликацию. Собственно и сказать нечего. Есть байндинги для python (правда не асинхронные либо эмулирующие асинхрононность пулом соединений),​ ест-но есть и для явы.
 +
 +**MongoDB**
 +
 +NoSQL document-based storage. Фактически да, ноу-скьюль,​ но с дружественными фичами. База живет в памяти (memory-mapped files) из за чего ограниченна по размеру адресным пространством и физической памятью (во взрослых базах disk-based базах память так же имеет не последнюю роль, стоит перейти определенный порог и все, база вместо полезной работы будет свопить данные с диска). Нет схемы, т.е. хранит "​документы",​ а попросту - некоторые json-структуры. Но поддерживает индексацию,​ быстрый поиск и подобие SQL посредством встроенного java-script'​а. Весьма шустра,​ как минимум раза в два от скорости mysql, даже по записи. Поддерживает fail-over и репликацию в slave-базы. Если нет нужды связываться с реляционными запросами - хороший кандидат на основное хранилище. Из минусов - в связи с аморфностью (отстуствием схемы) для каждой записи хранит все целиком в BSON формате. Иначе говоря структура { name: "​bubu",​ level: 123 } кроме bubu и 123 хранятся так же "​name"​ и "​level"​ для каждой записи,​ мгм. Опять же есть поддержка для любой платформы.
 +
 +**Redis**
 +
 +Кандидат на эдакий общий сетевой кеш. В целом идея - тот же memcached, но с ценными фичами. Фактически хеш-таблица,​ которую можно писать и читать очень быстро по сети. Поддерживает так же очереди,​ ассоциативные списки,​ pub/sub. Производительность на уровне сотни тысяч запросов в секунду. Удобна для хранения данных сессии игрока,​ transient данных,​ организации внешних performance-счетчиков или скажем чата.
 +  ​
 +
 +==== Асинхронность ====
 +Работать с сетью и вводом-выводом можно двумя способами - синхронно,​ создавая по нити на каждое соединение и спя ожидая окончания операций и асинхронно ​ - демультиплексируя весь ввод-вывод в одну нить или ограниченный (bounded) пул нитей-обработчиков. ​
 +На самом деле, как постулирует C10K, этот вопрос даже не стоит - из за накладных расходов и нецелевой траты ресурсов обработать много одновременных соединений в синхронной модели невозможно.
 +
 +С другой стороны,​ асинхронная модель сложнее в программировании. Фактически,​ без поддержки со стороны языка (coroutines,​ call/cc, yield, generators, привет С++) это выливается в сплошные коллбэки на каждый чих. Это похоже на закат солнца вручную или организацию кооперативной многозадачности. Простой пример - читаем с файла. Обычно мы зовем fread(...) и нить блокируется осью до окончания операции. В асинхронной модели блокировки недопустимы и вместо этого мы передаем callback-функцию - fread( ..., callback ). Вызов инициирует операцию и после вызова мы продолжим исполнение до тех пор, покуда не вернемся вниз по стеку вызовов до реактора ( демультиплексора ) и тогда он, подобрав результаты операции позовет callback с результатами. В ископаемых языках за, некоторым исключением,​ нет возможности сохранить контекст и вывалиться вниз до нужной точки, с тем, чтоб продолжить исполнение с сохраненного контекста,​ без вмешательства низкоуровневой магии. ​
 +
 +Картину дополняют вложенные,​ последовательные и другими способами замиксованные асинхронные вызовы,​ что, разумеется,​ доставляет. Мешанина из коллбеков это еще так радость. По счастью в питонах и других вышеобсужденных языках так или иначе наличиствуют средства для борьбы с этим произволом. (Привет лиспу и схеме с call/cc)
 +
 +Но это еще не все) Дело чуть упрощается пока на jvm языках мы можем отдать отдельную нить для демультиплексора и увести все обработчики в worker-нити,​ в то время как на питоне нить одна и там происходит все вместе. Блокировка нити на значительное время означает задержку всего остального мира. Впрочем,​ там это решается отдельными instance сервера,​ но все же. 
 +
 +==== Хостинг и maintenance ====
 +Сугубо прагматический вопрос,​ но, какбэ, напрямую связан со всем остальным. Отбрасывив вариант "​соберем машину дома и повесим в сеть"​ для хостинга существует несколько доступных решений. ​
 +
 +**Dedicated**
 +
 +Выделенный сервер - аренда железа на площадке хостера. Цена вопроса,​ без учета траффика - где то в районе 1к-2к в год. Предоставляется полный доступ к железу и, в разумных рамках,​ с ним можно делать что угодно. Сам ставишь ось, сам организовываешь бэкапы итд. Если сервер поломается,​ ответственность за свои данные - на тебе. Достаточно дешево и сердито. ​
 +
 +**VPS**
 +
 +Практически ​ тоже самое, только вместо железного сервера дается паравиртуализированный - иначе говоря кусок железного с определенными ресурсами. С некоторыми ограничениями делать с ним можно тоже самое - устанавливать свою ось, перегружать итп. Дешевле выделенного,​ но все определяется выделяемыми ресурсами на слайс.
 +
 +**Облачный хостинг**
 +
 +Эта расширенный вариант VPS паравиртуализации. Фактически добавляются развитые способы управления - можно склонировать готовый образ оси, установить необходимое ПО, сохранить в свой образ и потом запустить его на множестве виртуалок. Все это организуется достаточно просто,​ посредством веб-консоли либо предоставляемого API. Может быть дороже простого VPS и даже выделенного сервера,​ но это SaaS за что и цена. В целом же получается профит из за того что нет необходимости арендовать железку на год (без возможности отменить или поменять план) а наращивать ресурсы по необходимости.
 +
 +==== Сетевая топология ====
 +Кластер серверов живет за load-balancer'​ом/​reverse proxy и не так важно, stateless or stateful. Дело в том, что наличие еще одного звена в цепочке до пользователя позволяет более гибоко манипулировать траффиком. В случае poll-протокола это дополнительная буферизация,​ уменьшение нагрузки на сервер приложения. А в целом - защита,​ например,​ от простого DDOS'​а. Присылает школьнег письмо - мол, заддосю вас всем классом если сейчас не пришлете мне $200. И ведь может - правильно подобрав запрос,​ сервер можно нагрузить его обработкой и с довольно узким каналом. А тут мы можем запросто отсечь часть трафика совершенно бесплатно для серверов приложения.
 +
 +   ​Продолжение следует
  
kg/dic_disdic_server_common.txt · Last modified: 2011/11/26 09:09 (external edit)