![]() |
|
Модераторы: feodorv |
![]()
|
|
| drug007 |
|
|||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
Здравствуйте!
Стоит задача разработать сервер со следующими особенностями: а) малое число одновременных соединений (десятки) б) Соединения длительные - часы, а то и сутки в) Скорость передачи данных ограничена физическим каналом и небольшая до 1 КБ/с, а то и меньше (минимальная, максимальная неограниченая г) Большой пинг (от сотен мс до секунд) д) Сервер также выполняет тяжёлую обработку данных е) Объём передаваемых данных незначительный, но частый - много мелких сообщений. ж) доставка сообщений должна быть гарантированной Почитав тему про варианты архитектуры ASIO сервера, пришел к выводу что это должен быть: 1) tcp сервер (пункт ж) 2) с архитектурой типа SEDA (пункты д - разделяем сетевую часть и тяжелую бизнес-логику + пункт е - и там и там сообщения) 3) с архитектурой поток на сессию (пункт а - будет мало потоков, б -потоки создаются редко, т.е. нет оверхеда на их создание и пункт е -много мелких сообщений приведут к большему числу (системных?) вызовов при асинхронной архитектуре). 4) Сессии с большим таймаутом?(пункт г) Пункт В накладывает ограничение скорее на бизнес-логику, чем на сетевую часть. Может что-то лишнее в моих рассуждениях, а что-то нужно добавить? Прокомментируйте, пожалуйста. Это сообщение отредактировал(а) drug007 - 10.11.2011, 13:48 |
|||
|
||||
| boostcoder |
|
||||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 13 Всего: 110 |
эту?: http://forum.vingrad.ru/forum/topic-340251...0%BD%D0%BE.html
это в реализации сложнее. используй пул(хотя из требований, и одного потока будет достаточно). я, в одной из задач, применил такое необычное решение: 1. отдельный процесс-сервер. принимает соединения, читает пакеты, отправляет пакеты. 2. отдельный процесс-логика. общаются друг с другом посредством двух IPC очередей. в одну очередь сервер кладет данные, из второй очереди читает ответы от логики и отправляет клиентам. реализация получилась невероятно простой. это был эксперимент. сколько? приблизительно. в секунду.
опять же, вопрос выше. и это прочти: http://forum.vingrad.ru/forum/topic-341403/view-all.html Это сообщение отредактировал(а) boostcoder - 11.11.2011, 06:19 |
||||
|
|||||
| Lols |
|
|||
|
Шустрый ![]() Профиль Группа: Участник Сообщений: 144 Регистрация: 21.10.2011 Репутация: нет Всего: нет |
[QUOTE=boostcoder,11.11.2011, 00:15]
А ваш эксперимент реально будет исполнить на практике с такими требованиями? |
|||
|
||||
| boostcoder |
|
|||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 13 Всего: 110 |
у меня же он работает. хоть это и был эксперимент, но он работает в реальной задаче.
|
|||
|
||||
| drug007 |
|
||||||||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
Оно самое
Точных значений сказать не могу - если исходить из ширины канала, точнее из его узости, то в самом худшем варианте десятки сообщений в секунду на одно соединение, не больше. Требование по максимуму же пока можно сформировать только на уровне "чем больше, тем лучше" и "кашу маслом не испортишь", к сожалению. За ориентир возьму пока сотни сообщений в среднем размером 50-100 байт (это на уровне приложения), с возможность увеличения количества сообщений до тысяч с тем же размером - это в среднем, периодически (не часто) объем будет возрастать на порядок-два на непродолжительное время. Бизнес-логика пока смутно выглядит - собственно, для ее исследования и нужен сервер.
Почитал, полезно - я тоже разделял сервер и обработку данных, но, правда, в потоках. Но как то оценить не могу, т.к. опыта не очень много. На тот момент главным было, что работает. Про buffered_stream надо будет почитать, спасибо за ссылку - у меня как раз сначала читается хидер, затем тело - не нравилось уже когда код писал, но пока оставил так. А вот про большее количество системных вызовов в случае асинхронной архитектуры при небольшом количестве клиентов пока не могу сообразить - вроде согласен в целом, но в деталях в голове не раскладывается, буду думать. Тут еще нюанс в том, что по сути приложение предназначено для построения распределенной сети по сбору и обработке данных. И по возможности это приложение должно функционировать и на слабых машинах с небольшим объемом памяти. Сразу реализовывать такую возможность нет задачи, но не должно быть принципиальных ограничений на ее реализацию в дальнейшем. Если сервер сможет достойно крутится хотя бы на каком-нить двухядерном Cortex-A8 1.5GHz/512 Мb памяти, это будет заметным плюсом. Ясное дело, что вариант для х86 будет отличаться от варианта на ARM в решении некоторых вопросов, но желательно отличия свести к минимуму. Это я к тому, что закладывать требование в 4Гб на преаллокацию памяти (к примеру) крайне нежелательно. З.Ы. требования с учетом имеющихся ресурсов становятся похожи на наполеоновские, скорее всего как всегда из высокоадгезионного соединителя на межмолекулярных связях с использованием нанотехнологий получится пусть и качественный, но обыкновенный гвоздь, но будет полезно хотя бы в теории этот вопрос проработать |
||||||||
|
|||||||||
| boostcoder |
|
||||||||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 13 Всего: 110 |
я в одной из своих реализаций, добился ~470 000 тысяч в сек. а ты переживаешь за несколько тысяч. это будет весело, учитывая тот факт что доки к нему почти ноль. читать в этом случае нужно исходники. вот, специально для подобных задач создал: boost.asio.
т.е. ты говоришь про сервер? если да - то конфиг к нему прикрутить. или заранее создать набор предустановленных значений(кол-во потоков,сессий,размеры буферов, и т.д.).
запросто.
про конфиг написал несколькими строками выше. Это сообщение отредактировал(а) boostcoder - 6.8.2012, 11:50 |
||||||||
|
|||||||||
| drug007 |
|
||||||||||||||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
Да просто когда я первый раз столкнулся с такой задачей, канал связи был 50 бод (нужна была совместимость с аппаратурой лохматых годов) и сообщение в 20 байт передавалось пару секунд. Запустил как-то на 1200 бод - это был просто восторг Всегда упирался в минимальную ширину канала, загрузить цпу бизнес-логикой "удалось" только один раз из-за неправильного алгоритма, поэтому сложно оценить потенциальное количество сообщений. Но сотня тысяч это радует. Это имеется в виду скорость чисто сервера без бизнес-логики?
Исходники буста для меня еще довольно сложная вещь, но почитаю в любом случае.
Прекрасно.
Т.е. учесть достаточно большое на мой взгляд различие в производительности/области применения/другое вполне можно будет с помощью конфигурации? Это обнадеживает. Главное, чтобы для поддержки конфигурирования не пришлось лишний код писать - выбор между асинхронным сервером с io_service на цпу и синхронным сервером с блокирующими сокетами с потоком на каждого клиента, например, в конфигурации можно прописать, но фактически же будут запускаться разные куски кода, а не просто буфера с разным размером, а эти куски и поддерживать нужно будет по отдельности, чего не хочется. Я в связи с этим решил пока остановиться на асинхронном сервере, как более универсальном. То что при небольшом кол-ве клиентов он будет проигрывать серверу с потоком на клиент, будет с лихвой компенсироваться меньшей нагрузкой в целом. А при большом кол-ве клиентов асинхронный уже будет выигрывать. Да и реализован он уже у меня частично, в конце концов. |
||||||||||||||
|
|||||||||||||||
| boostcoder |
|
||||||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 13 Всего: 110 |
да.
да.
не-не-не! это что за зоопарк архитектур? для твоей задачи тебе достаточно одного io_service. а остальное пока не известно.
это еще почему? |
||||||
|
|||||||
| drug007 |
|
||||||||||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
Вот как раз зоопарка я и не хочу.
Дык вот. Процитирую, на всякий случай:
|
||||||||||
|
|||||||||||
| boostcoder |
|
|||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 13 Всего: 110 |
у тебя производительность затребована на столько низкая, что я думаю тебе для всего сервера хватит единственного потока. еще и много будет. |
|||
|
||||
| drug007 |
|
|||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
А я про это и говорил. Насчет одного потока - у меня сейчас как раз один и в нем и сервер, и клиент, и графика на Qt крутится. Но я его и не нагружал толком, нет возможности пока - бизнес-логику еще надо делать. В общем, я понял, что для решения моей задачи достаточно простого сервера - я взял за основу пример из asio samples и в принципе, на нем можно и остановится. Пока вопросов больше и нет. Спасибо за ответы. |
|||
|
||||
| mabrarov |
|
|||
|
Шустрый ![]() Профиль Группа: Участник Сообщений: 100 Регистрация: 12.1.2011 Где: Казань Репутация: нет Всего: 9 |
Рекомендую брать из trunk-а SVN-репозитория. Нашел недавно пару неприятных ошибок. P.S. Если Вы имели в виду asio samples а не asio examples Это сообщение отредактировал(а) mabrarov - 11.11.2011, 15:33 |
|||
|
||||
| drug007 |
|
|||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
Марат, Ваш qt_echo_server очень бы подошел к моему проекту, но как выяснилось, требования в моем случае настолько низкие, что я решил обойтись вариантом сервера на основе http::server3 из asio.samples (да, я тут описался), поскольку он заметно проще.
Когда я начал изучать qt_echo_server, я несколько по другому взглянул на C++ и мне стала более понятной точка зрения его противников и его сторонников - когда передо мной стоит задача, которую нужно решить и есть жесткий дедлайн, есть классный пример на boos.asio, который то, что надо, но надо его допилить, но для допиливания мне нужно потратить кучу времени чтобы поднять свой уровень владения языком, то конечно я выберу другой язык попроще и буду кричать на каждом углу, что c++ must die (или вроде того) и приводить аргументы. Если же передо мной стоит та же задача с тем же дедлайном и мой уровень языка достаточен, чтобы я наслаждался, как тонко я могу выразить термины предметной области в структурах языка c++ и просто восхищался, как богат внутренний мир языка и как другие этого могут не видеть, то я бы быстро допилил найденный пример (и еще сэкономил бы тем время) и на каждом углу кричал бы, что лучше c++ нет и приводил бы аргументы. И в каждом случае я был бы прав - отношение к языку определяется в первую очередь уровнем владения языком. Имхо, конечно, сорри за оффтоп =) Спасибо всем, кто выкладывает примеры по использованию boost'а вообще и asio в частности, способствуя их популяризации вы делаете мир лучше! =) |
|||
|
||||
| mabrarov |
|
|||
|
Шустрый ![]() Профиль Группа: Участник Сообщений: 100 Регистрация: 12.1.2011 Где: Казань Репутация: нет Всего: 9 |
В качестве оффтопа (продолжения не будет). Ну... там вся сложность (я уже писал где-то в комментариях в своем блоге) связана с:
Многопоточные алгоритмы сложны. На C/C++ особенно. С Asio все же по-легче, но все равно надо четко знать все ограничения и перестаривать логику/мышление/стиль программирования. Это я о причинах сложности. А так - с Вашим мнением я согласен. Где-то на rsdn попадался набор "слайдов" (или как там его), в котором между делом проскакивает, как Бьярн Страуструп оценивает свое владение языком C++ на 8 из 10. |
|||
|
||||
| Lazin |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 3820 Регистрация: 11.12.2006 Где: paranoid oil empi re Репутация: 5 Всего: 154 |
Я вот не вижу причин использовать асинхронный ввод вывод для решения этой задачи.
Event driven сервер будет иметь более высокую пропускную способность нежели threaded сервер только тогда, когда I/O сильно превалирует над обработкой, да и то, только до тех пор, пока программа не упрется в процессор. Event driven архитектура тупо сложнее в реализации, так как логика "размазывается" по куче callback вызовов. Threaded сервер сильно проще, там вся логика для отдельного соединения это просто последовательность действий, выполняемых в отдельном потоке. Помимо этого, event driven подход требует большего количества системных вызовов. Я экспериментировал с пайпами под windows, для IPC, у меня был простой RPC сервер и клиент (echo сервер). Threaded версия имела более чем вдвое низкую латентность, нежели event driven версия. Это сообщение отредактировал(а) Lazin - 15.11.2011, 08:54 |
|||
|
||||
| mabrarov |
|
|||
|
Шустрый ![]() Профиль Группа: Участник Сообщений: 100 Регистрация: 12.1.2011 Где: Казань Репутация: нет Всего: 9 |
Согласен. Только меня постоянно мучает вопрос: "ну все равно же эти потоки как-то должны "общаться" друг с другом?" Критические секции/мютексы очень похожи в этом случае на вырожденный вариант очереди с размерм в один элемент. Как сообщить thread-based объекту session, что пора кхм.. завершиться? Тупо закрыть сокет (на котором thread-based session м/б залочена в блокирующем вводе/выводе) в другом потоке - выглядит как-то криво (я знаю, что так и делают). Использовать Windows-события - форменное извращение. Использовать Windows Event для ожидания окончания ввода-вывода + дополнительный Event и WaitForMultipleObjects - натуральный костыль. Понятно, что есть разница м/у "красивая теория" и "суровая практика", но так хочется чего-то обобщенного. Это сообщение отредактировал(а) mabrarov - 15.11.2011, 09:17 |
|||
|
||||
| Lazin |
|
||||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 3820 Регистрация: 11.12.2006 Где: paranoid oil empi re Репутация: 5 Всего: 154 |
Можно не закрывать а сделать сокету shutdown, если религия не позволяет закрывать сокет, можно использовать pooling, читаем из сокета с таймаутом, при достижении таймаута проверяем не нужно ли завершиться, либо не превышено ли критическое время ожидания ответа и выполняем соответствующие действия, можно и закрыть(в чем проблема просто тупо закрыть сокет я не очень понимаю).
Мы не знаем какая там происходит обработка данных, может там вообще не потребуется синхронизация или потребуется очень простая синхронизация. Добавлено через 7 минут и 36 секунд
ты не поверишь! xD мьютекс (если это fair мьютекс) это и есть очередь, самая настоящая, first in first out |
||||||
|
|||||||
| drug007 |
|
|||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
Согласен, но к первому предложению добавлю, что также event driven сервер имеет смысл использовать, когда бизнес-логика тоже event driven, как в моем случае. Т.о. drawback от распределенной логики все равно будет присутствовать, зато архитектура приложения будет единой. Плюс это не конкретное задание на конкретный продукт с конкретными требованиями, это больше исследовательская модель для бизнес-логики, поэтому выбор event driven модели как более универсальной оправдан даже несмотря на больший оверкод и оверхед. А уже при допиливании вполне можно будет сделать конкретные версии. Хотя требования к латентности в моем случае высокие и это большой минус асинхронному серверу, но с другой стороны, т.к. логика тоже асинхронная, отказавшись от асинхронного сервера я просто перенесу латентность с ввода/вывода на логику, а латентность все равно останется. Ибо код в цепочке асинхронных вызовов всегда будет дольше обрабатываться, чем аналогичный код вызываемый последовательно из-за отсутствия лишних вызовов. Это сообщение отредактировал(а) drug007 - 15.11.2011, 10:49 |
|||
|
||||
| mabrarov |
|
||||||||
|
Шустрый ![]() Профиль Группа: Участник Сообщений: 100 Регистрация: 12.1.2011 Где: Казань Репутация: нет Всего: 9 |
Это уже выглядит как костыль. Нет, я понимаю что так делали и делают. Это такой "рабочий" вариант - но все равно костыль, как ни крути. Добавлено @ 10:44
Проблемы особой нет. За исключением логики. Наш класс session получается не простой. Его метод session::close() можно вызывать из любого потока в любое время. Но только, если underlying socket library гарантирует то же самое для socket::close() (shutdown). Плохо, когда классы так сильно связаны с потоками. В случае ma::echo::server::session - session связан с task scheduler (asio::io_service::strand и asio::io_service). А тот - с потоками. Добавлено @ 10:47
В том-то и дело, что синхронизация в каком-либо виде требуется всегда. Даже если это web-сервер. Хотя бы для того чтобы тот же session_manager мог дать команду "завершиться" всем session. Добавлено @ 10:49
Э, ну это уже нечестно Это сообщение отредактировал(а) mabrarov - 15.11.2011, 11:18 |
||||||||
|
|||||||||
| mabrarov |
|
|||
|
Шустрый ![]() Профиль Группа: Участник Сообщений: 100 Регистрация: 12.1.2011 Где: Казань Репутация: нет Всего: 9 |
На счет оверкода (что-то без регистрации перестали пускать).
Вкратце, я понимаю, что "не писать сложный код, потому что (именно) условия задачи позволяют обойтись более простым" в производстве нормально. "My personal bias is to try to avoid the complexity side of such tradeoffs, but sometimes its advantages are too compelling to ignore" (Andrew Koenig). Это сообщение отредактировал(а) mabrarov - 15.11.2011, 11:11 |
|||
|
||||
| mabrarov |
|
|||
|
Шустрый ![]() Профиль Группа: Участник Сообщений: 100 Регистрация: 12.1.2011 Где: Казань Репутация: нет Всего: 9 |
Я имел в виду shared data (например, флаги и состояния), защищенные критическими секциями/мютексами. Это сообщение отредактировал(а) mabrarov - 15.11.2011, 11:31 |
|||
|
||||
| Lazin |
|
||||||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 3820 Регистрация: 11.12.2006 Где: paranoid oil empi re Репутация: 5 Всего: 154 |
ето как? ничего не понял, где они высокие? высокие, это когда десятки микросекунд, а не сотни миллисекунд
латентность сервера = I/O time + CPU time, оно в любом случае никуда не денется, используя разные архитектуры сервера можно немного менять второе слагаемое, но не первое
Неа, не костыль. Close - это когда нужно остановить обмен данными через сокет на одной из сторон без ведома второй. Если нужно быстро остановить сервер, то это как раз тот случай. Не костыль, это когда у нас в протоколе есть возможность остановить сессию выполнив определенные действия. А если серверу действительно нужно просто взять и отвалиться, то нужно именно сделать close и все равно из какого потока, главное что-бы все отвалилось сразу.
И в event driven архитектуре в том числе. Мало того, в event driven сервере ее сделать сложнее, ибо нужно синхронизировать не только разные сессии но и внутри сессии. Скажем в рамках одного подключения пришли 2 сообщения, одно начало обрабатываться в одном потоке, второе в другом. Именно поэтому в asio есть strand-ы. Либо у нас будет один io_service на поток, в этом случае вам придется возиться с балансировкой нагрузки. В случае threaded сервера нужно просто синхронизировать доступ к общим данным. Это проще. Можно вообще построить threaded сервер так, что-бы каждый поток крутил что-то вроде цикла обработки сообщений, тобишь был event driven но в рамках одного соединения, в этом случае можно будет отказаться от общей памяти, организовав обмен сообщениями между потоками.
Это плохо только тогда, когда сессий может быть очень много. В остальных случаях это скорее хорошо, ибо не нужно об этом думать. |
||||||||
|
|||||||||
| mabrarov |
|
|||
|
Шустрый ![]() Профиль Группа: Участник Сообщений: 100 Регистрация: 12.1.2011 Где: Казань Репутация: нет Всего: 9 |
Согласен. |
|||
|
||||
| Lazin |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 3820 Регистрация: 11.12.2006 Где: paranoid oil empi re Репутация: 5 Всего: 154 |
вообще, автор писал что у него тяжелая обработка данных происходит, это автоматически означает низкую пропускную способность event driven сервера
max event dirven server throughput = number of CPUs / CPU time per query для многопоточного сервера: max threaded server throughput = number of threads / (IO time per query + CPU time per query) вторая формула справедлива до тех пор, пока мы еще не уперлись в возможности процессора, то есть он не загружен на 100% отсюда видно, что в том случае, если IO time per query сильно больше чем CPU time per query, то нужно использовать event driven архитектуру, в противном случае(интенсивные вычисления) - нужно использовать threaded архитектуру |
|||
|
||||
| mabrarov |
|
||||||||
|
Шустрый ![]() Профиль Группа: Участник Сообщений: 100 Регистрация: 12.1.2011 Где: Казань Репутация: нет Всего: 9 |
Все дело в том, что в идеале, хотелось бы иметь по-меньше классов (тот же сокет), которые имели бы thread-safe методы, так как это дается не бесплатно и предполагает, что класс заранее планировался для использования в многопоточной среде. Плохо by-design, но вполне используется, потому как никому не хочется лезть в дебри event driven. Добавлено @ 11:57
Ну это вроде бы уже "обсосали". Конечно, да. Классика же. Я просто о своем (о наболевшем) начал, как обычно. Добавлено @ 12:01
В том-то и дело, что в event driven архитектуре на базе Asio мы (архитектурно) разгружаем класс session от непосредственной синхронизации (и от знания того, что он будет использоваться в многопоточной среде), оставляя синхронизацию strand-ам. Добавлено @ 12:04
"что-то вроде цикла обработки сообщений" обеспечивает strand. "в этом случае можно будет отказаться от общей памяти, организовав обмен сообщениями между потоками" - так это и есть очередь - та же общая память, почти тот же strand. Пора заканчивать. Сплошной оффтоп пошел. Просто приятно обсудить наболевшие вопросы. Это сообщение отредактировал(а) mabrarov - 15.11.2011, 12:08 |
||||||||
|
|||||||||
| Lazin |
|
||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 3820 Регистрация: 11.12.2006 Где: paranoid oil empi re Репутация: 5 Всего: 154 |
с каких это пор ф-я WSAClose стала классом? шутка вам не кажется, что использовать event driven подход для того что-бы остановить event driven систему это как-то не ок? я лично люблю останавливать сервер так, что-бы это произошло за ограниченное время (1), каждая сессия смогла обработать разрыв соединения так, что-бы не испортить что-либо в базе или еще где нибудь (2). Вот close для этого оч. хорошо подходит. В коде потока сессии это все выражается в том, что один из send-ов или recv-ов бросает исключение а какой-нибудь try - catch его ловит. Причем все это происходит в предсказуемом порядке, сначала перестаем принимать новые подключения, потом закрываем первую сессию, первая сессия завершилась, закрываем вторую... В случае event driven тоже все просто, только вместо try catch у нас вызывается один из хэндлеров. Проблема этого подхода в том, что нужно явным образом посигналить(установить event или еще что нибудь) о том что сессия - все. Помимо этого, код обработки разрыва соединения приходится дублировать между всеми обработчиками. Помимо этого сокет может быть закрыт не только во время ожидания выпонения операции чтения-записи, но и во время обработки данных, в этом случае следующий вызов send_async/recv_async (или как это там называется) бросит исключение, вот вам еще одна точка обработки ситуации разрыва соединения. В общем, сложнее это. Добавлено через 9 минут и 14 секунд
нужно помнить что у тебя все должно идти через strand, вызовешь случайно метод класса - сессии не через strand и все хотя я согласен с тем что если мы пришли к тому что-бы городить цикл обработки сообщений внутри каждого потока, то проще просто использовать boost.asio у меня - все |
||||
|
|||||
| drug007 |
|
||||||||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
В общих чертах бизнес-логика представляет собой набор взаимосвязанных КА (и сама КА тоже). Переход одного КА в другое состояние может являться событием для других. И такая "волна" событий распространяется от источника первоначального события. И таких источников много, и они влияют друг на друга. Более того, КА появляются и удаляются динамически. Плюс есть задача репликации данных между серверами - проще делать репликацию, когда у тебя есть событие, которое говорит, что такие-то данные изменились, линейный алгоритм может означать только периодический обзор всех данных на предмет изменения. На данный момент я думаю, что реализовать такую модель линейным алгоритмом будет сложнее, чем с помощью событий. Хотя я могу заблуждаться. Немного оффтоп, но насколько верны мои предположения о простоте реализации такого способа репликации?
Пинг, что я привел, это качество каналов связи. Это не требование иметь такую латентность, а требование сохранять функциональность при таком пинге. Ибо сервер, работоспособный в LAN, при первом же запуске в WAN показал, что такие пинги не для него. Сам же сервер должен иметь латентность, стремящуюся к нулю.
Вот тут категорически не соглашусь. Вы же сами утверждали, что потоковая версия имела латентность вдвое меньше чем асинхронная? А это как раз первое слагаемое. |
||||||||
|
|||||||||
| Lazin |
|
||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 3820 Регистрация: 11.12.2006 Где: paranoid oil empi re Репутация: 5 Всего: 154 |
так то для IPC на одной машине, асинхронная версия делала в 2 раза больше системных вызовов чем синхронная
события != асинхронность, в данном случае все очень даже синхронно, изменилось состояние -> сгенерировалось событие -> изменилось состояние другого КА и тд вот если бы изменения в разных КА могли происходить с разной скоростью, то это была бы асинхронная модель, но я не вижу в этом смысла |
||||
|
|||||
| drug007 |
|
|||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
Похоже на то, что вы разговариваете о чем то до боли знакомом вам обоим. Настолько знакомом, что вы даже детали некоторые пропускаете, т.к. понимаете друг друга с полуслова. А вот я посмотрев данные формулы, пришел к выводу, что они не выражают общий случай, а могут быть применены только в определенном контексте, который не был озвучен. Мой опыт пока не позволяет понимать вас с полуслова без деталей. =) Поэтому я не буду комментировать формулы, а просто их проигнорирую, при все своем уважении. Или попрошу уточнить, если это не уведет в оффтоп. Это сообщение отредактировал(а) drug007 - 15.11.2011, 12:47 |
|||
|
||||
| mabrarov |
|
|||
|
Шустрый ![]() Профиль Группа: Участник Сообщений: 100 Регистрация: 12.1.2011 Где: Казань Репутация: нет Всего: 9 |
Ну почему же. Как раз в event driven системе останов (имеется в виду плановый, а не исключительная ситуация с обрывом всего и вся) - это просто event, который получает сессия. Дальше она уже сама правильно (!) завершит работу. Например, сначала дождется окончания обработки очередного запроса СУБД, отправит подготовленный ответ, а уже потом сделает shutdown + close. В случае же прямого вызова socket::close() из другого потока, если поток (thread) сессии был заблокирован на "отправке подготовленного ответа" (блокирующий socket::write()) - мы как раз получим невозможность реализации планового останова. Конечно, в реальности "прямой вызов socket::close() из другого потока" для планового останова threaded session никто не делает. Просто session в случае, если сокет закрывать нельзя ("отправка подготовленного ответа") через примитивы синхронизации лочит session shared data и устанавливает флаг "не вздумайте закрыть сокет". session::close() так же лочит session shared data, если "разрешено" - закрывает сокет и, наконец, обязательно устанавливает флаг "пора закругляться по такой-то причине" (а ниже основной поток сервера уже ждет а-ля session::join()). threaded session в этом случае должна так же периодически лочить session shared data и проверять флаг останова (ну да, atomic-варианты никто не отменял) - чувствуете, насколько логика такой session связана с потоками в отличие от использования asio::io_service::strand (который, по сути, обычная concurrent очередь функторов)?. Логика в обоих случаях одна и та же. Просто event driven + threads "разрывает мозг" необходимостью выносить участки кода (те что в threaded session идут м/у проверками флага останова) в отдельные event/task и добавляет накладных расходов (нехило так - как и любой task-based parallelism). В реальном мире - все есть КА. И работает как раз event driven и именно параллельно. Event driven сложнее именно из-за C/C++ - у этих языков все на более низком уровне (и хорошо). Взять тот же Erlang, и, думаю, будет уже не так "больно мозгу". На этом закончу мое участие в теме (особенно, с учетом того, что я согласен с Lazin и меня просто задевает то, что частенько стараются избавиться от сложности многопоточных асинхронных event-driven серверов лишь потому, что... нет готового решения по типу "просто написать модуль для nginx") и пойду обедать. Это сообщение отредактировал(а) mabrarov - 15.11.2011, 13:48 |
|||
|
||||
| drug007 |
|
||||||||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
В первую очередь хотел бы еще раз поблагодарить за ответы участников. Приятно пообщаться со знающими людьми. (Хотя вы больше между собой, правда
Вы меня все-таки тут путаете...
Я понял, что вы имеете в виду, вполне возможно, что я мешаю понятия асинхронного наступления события и асинхронного завершения обработки события. Возьму пока таймаут. Это сообщение отредактировал(а) drug007 - 15.11.2011, 13:36 |
||||||||
|
|||||||||
| Олег2005 |
|
|||
|
Опытный ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 421 Регистрация: 26.5.2005 Где: Рига Латвия Репутация: 6 Всего: 11 |
||||
|
||||
| drug007 |
|
||||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
Попробую уточнить свое понимание понятия асинхронности: события в event driven архитектуре всегда наступают асинхронно по определению — мы никогда не знаем, когда произойдет то или иное событие. Если получив событие, мы его тут же обрабатываем и пока не обработаем текущий поток не возвращает управление, то у нас синхронная обработка. Если получив событие, текущий поток тут же возвращает управление, а обработка выполняется ядром или в отдельном потоке/процессе, то у нас асинхронная обработка. т. е. имея асинхронные события, мы можем реагировать на них синхронно или асинхронно. Правильно? Возвращаясь к выбору архитектуры, я быстренько накидал простенькую модель асинхронной и синхронной на базе потоков. У меня получилось, что время работы у этих архитектур следующее: Ttotal async = N * 2 * Tsyscall + N * Tprocessing Ttotal thread = (N - 1) * Tcontextswitch + N * Tprocessing, где Ttotal async – время обработки данных в асинхронной архитектуре, Ttotal thread – время обработки данных в потоковой модели N – количество событий, подлежащих обработке (т. е. соединений), Tsyscall – время системного вызова (двойка т. к. вызовов два на одно событие), Tcontextswitch – время переключения контекста (N-1 т. к. между 2мя событиями 1 переключение, между 3мя — 2 и т.д.), Tprocessing — время обработки события, в обоих вариантах одинаковое. Первое слагаемое время работы системы ввода/вывода, второе слагаемое время работы бизнес-логики, считаем, что второе слагаемое одинаковое. Отсюда если Tsyscall меньше чем (примерно) 0.5* Tcontextswitch, выигрыш у асинхронной архитектуры, т. к. (Tcontextswitch – 2*Tsyscall) > 0. Однако при Tprocessing >> (Tcontextswitch - 2*Tsyscall), выигрыш потеряет значение на фоне общей вычислительной нагрузки, при этом модель thread pool проще в разработке и сопровождении, также формулы не учитывают скорость поступления событий или степень нагрузки цпу, что то же самое. Если же Tprocessing того же порядка или меньше чем (Tcontextswitch – 2*Tsyscall), то выбор за асинхронной архитектурой. Мои выводы верны? Если да, то продолжу =) |
||||
|
|||||
| boostcoder |
|
||||||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 13 Всего: 110 |
да.
да.
да. на мой взгляд - да. |
||||||
|
|||||||
| drug007 |
|
|||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
Получается, что даже если у меня предметная область решаемой задачи имеет асинхронную природу, я могу использовать и синхронную обработку и асинхронную. При этом использование асинхронной обработки не только не даст никакого выигрыша, но и увеличит время реакции приложения и его сложность?
Это сообщение отредактировал(а) drug007 - 17.11.2011, 14:23 |
|||
|
||||
| boostcoder |
|
|||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 13 Всего: 110 |
получается так.
но мое ИМХО - мне как-то логически привычней асинхронные реализации. Добавлено @ 14:10 я пытаюсь отделить ввод/вывод от логики используя disruptor. это позволит абстрагировать логику от ввода/вывода и даст более гибкую и расширяемую структуру. Это сообщение отредактировал(а) boostcoder - 17.11.2011, 14:22 |
|||
|
||||
| drug007 |
|
|||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
Ок, буду считать, что с понятием асинхронности я определился.
Тогда опять по выбору архитектуры. Немного повторю предпоследний свой пост, правда. Получается, что в общем случае мы имеем на входе N событий (это может быть как получение/передача данных, так и соединение/завершение соединения). На обработку каждого события ЦПУ тратит Tprocessing времени. Плюс время на обработку события системой ввода/вывода. Общие затраты времени на обработку N событий равны равны сумме затрат на обработку событий системой ввода/вывода и бизнес-логикой, которые для асинхронной архитектуры складываются из: Ttotal async = N *2 * Tsyscall + N*Tprocessing, т. е. затраты будут складываться из стоимости двух системных вызовов (начало и завершение асинхронной операции) на каждое событие плюс время на обработку события бизнес-логикой. для синхронной: Ttotal sync = (N-1)*P*Tcontext switch + N*Tprocessing где P - вероятность переключения контекста, зависит от скорости поступления событий М, времени обработки Tprocessing и числа потоков в пуле k: P = 0; при M < 1/Tprocessing - все события обрабатываются одним потоком, нет переключений контекста P = M*Tprocessing/k; при M > 1/Tprocessing и M < k/Tprocessing - задействована часть потоков пула, периодически происходит переключение контекста P = 1; при M > k/Tprocessing - задействованы все потоки из пула, переключение контекста происходит каждое событие здесь затраты складываются из времени на переключение контекста плюс время обработки бизнес-логикой. При этом количество переключений контекста меньше чем количество событий минимум на единицу, т. к. необходимость переключения контекста может возникнуть не раньше второго события. Если события поступают со скоростью, позволяющей все события обрабатывать в одном потоке, то количество переключений контекста будет равно нулю. Т.е. если в 1 секунду мы получаем M событий, такое что M <= 1/Tprocessing, то Tcontext switch = 0 (мы понимаем, что цена вызова функции recv и send не зависит от архитектуры, поэтому здесь их не учитываем). Если мы получаем события со скоростью, не позволяющей их обрабатывать одним потоком, то возникают переключения контекста и если мы получаем за 1 секунду М событий, такое, что M > k/Tprocessing, где k – количество потоков в пуле синхронной архитектуры, то контекст будет переключатся N-1 раз на каждые N событий (т.е. начиная со второго события). При M на интервале от 1/Tprocessing до k/Tprocessing вероятность переключения контекста будет соответственно от 0 до 1 (в формуле я взял линейное распределение вероятности для простоты, все равно грубая модель, отражает только мгновенное значение, по идее надо интегрировать, но понятия не имею как Отсюда у меня получилось, что: 1) чем больше событий, тем выгоднее асинхронная архитектура, при малом количестве выигрыш синхронной значителен. 2) время обработки в асинхронной не зависит от скорости поступления событий, как в синхронной, поэтому чем выше скорость поступления событий, тем выгоднее асинхронная архитектура (либо увеличивать число потоков в синхронной) 3) чем больше время обработки, тем больше асинхронная архитектура теряет свои позиции 4) и т. д. Тут бы графики показать, нагляднее было бы... Как это лучше сделать? Мне кажется, получилось бы хорошее дополнение к вопросу о производительности различных архитектур сервера.
Я тоже склоняюсь к асинхронной в своем проекте, но производительность иногда важна Про disruptor обязательно почитаю, сегодня уже некогда, но вещь интересная, спасибо за ссылку. Это сообщение отредактировал(а) drug007 - 17.11.2011, 14:31 |
|||
|
||||
| boostcoder |
|
|||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 13 Всего: 110 |
по этому я и решил отделить ввод/вывод от логики при помощи disruptor. Добавлено через 4 минуты и 6 секунд
в следствии этой темы, я сейчас работаю над эталонными тестами. но тут самое сложное(как и с любыми тестами) - эти тесты должны быть действительно эталоном. ну или хотя бы большинство из тестеров должны получить приблизительно одинаковые результаты этих тестов. |
|||
|
||||
| Lazin |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 3820 Регистрация: 11.12.2006 Где: paranoid oil empi re Репутация: 5 Всего: 154 |
вот - http://evgeny-lazin.blogspot.com/2011/11/t...ent-driven.html
навеяно в том числе и этим топиком Добавлено @ 21:57 бесстыдный само-пиар! Это сообщение отредактировал(а) Lazin - 17.11.2011, 21:57 |
|||
|
||||
| drug007 |
|
||||||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
Когда я вчера писал свой пост, у меня уже было штук пять познавательных (для меня по крайней мере) графиков. Уже засыпая, я пришел к выводу, что графиков может быть больше намного, потому что вариантов реализации сервера получается много. И расстроился, потому что понял, что это будет большая работа провести сравнительный анализ и привести его в вид, достойный всеобщего обсуждения. Не уверен, что смогу себе это позволить. Но если у кого-то это получится буду рад и оценю его труд. Добавлено через 9 минут и 2 секунды
Согласен с комментариями Марата - не совсем безупречны ваши выкладки, нужны уточнения. Хотя выводы большей частью верные и я с ними согласен, но обоснование не всегда прозрачно и мне показалось, что вы под свое уже сформировавшееся мнение подогнали матаппарат, вместо того, чтобы сформировать мнение на основе матаппарата. |
||||||
|
|||||||
| Lazin |
|
||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 3820 Регистрация: 11.12.2006 Где: paranoid oil empi re Репутация: 5 Всего: 154 |
нужны конечно, просто пост итак большим получился, к тому же, большинству эти уточнения читать было-бы не интересно, они довольно очевидны
Этому матаппарату в обед сто лет, в смысле ничего я не подгонял и все там достаточно прозрачно. Если не понятно почему именно так и откуда такие выводы, могу объяснить Выкладки, учитывающие время переключения контекста и длительность системных вызовов, на мой взгляд, вообще бесполезны, так как не учитывают главного. Допустим мы пишем под windows и используем IOCP. Вызов GetQueuedCompletionStatus действительно не будет приводить к переключению контекста, но, когда мы вытащим из него очередной completion packet, нам нужно будет восстановить контекст выполнения. Это можно сделать, например прочитав значение указателя на функцию обработчик из OVERLAPPED структуры и вызвав ее с нужными параметрами. Так вот этот overhead тоже нужно учитывать. В случае многопоточного сервера это делать не нужно, так как поток это и есть контекст выполнения и им не нужно явно управлять, адрес обработчика уже находится в IP регистре, а все нужные переменные уже находятся в стеке Помимо этого, event based сервер может быть многопоточным, в этом случае completion packet может быть обработан на любом ядре, что приведет к кэш промаху, а потоки обычно бывают привязаны к одному ядру. Вообще, на мой взгляд, event based подход к созданию сереверов это костыль. Если подумать, то используя IOCP, epool или kqueue программист создает свои потоки, сам таскает контекст, сам управляет переключением этих контекстов. Все из-за того что потоки плохо масштабируются, в своей текущей реализации. |
||||
|
|||||
| drug007 |
|
||||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
Объясните.
Не то, чтобы главного, но многого они не учитывают, это да. Об этом я вчера вечером и подумал, отчего и огорчился немного. З.Ы. ваша модель этого тоже не учитывает, кстати |
||||
|
|||||
| Lazin |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 3820 Регистрация: 11.12.2006 Где: paranoid oil empi re Репутация: 5 Всего: 154 |
там есть оговорка по поводу того, что первая формула верна только до тех пор, пока процессор не будет загружен на 100% для одного потока справедливо соотношение - время выполнения запроса = время выполнения синхронных операций ввода вывода + время обработки, это очевидно, ибо во время выполнения синхронного ввода/вывода поток простаивает выглядит это как-то так: ![]() красный график - I/O, зеленый - CPU важное допущение, мы считаем что I/O подсистема у нас - идеальна, обладает бесконечной пропускной способностью и concurrency, а CPU - нет. Теперь, если мы запустим 2 потока, то I/O у нас сможет происходить параллельно, а обработка - нет (считаем что CPU - один) то бишь, пропускная способность одного потока: 1/(I + C), I - время выполнения I/O операций запроса, С - время обработки, если запустить K потоков, то пропускная способность будет выше в K раз, но только если процессор не полностью загружен. если он загружен полностью, то добавление еще одного потока не приведет к увеличению пропускной способности. При таком K, при котором процессор загружен полностью, пропускная способность сервера максимальная, но при этом, пропускная способность сервера ограничена сверху. В результате зависимость T от K должна быть примерно такой (в реальности она такой будет только при сравнительно небольшом К - десятки, сотни потоков): ![]() в случае асинхронного сервера, пропускная способность максимальна тогда, когда процессор загружен полностью, полностью он загружен тогда, когда непрерывно обрабатывает completion handler-ы от завершившихся операций ввода вывода, отсюда очевидна формула: T = 1/C (для одного ядра) можно сказать что С - время выполнения всех обработчиков, которые вызываются при обработке запроса. накладные расходы, связанные с работой ОС мы игнорируем, считаем что процессор целиком принадлежит нашему приложению (на самом деле это даст не такую уж и большую погрешность) если теперь мы приравняем две эти пропускные способности, это можно сделать, ибо макс. пропускная способность сервера в случае идеальной I/O подсистемы с бесконечными ресурсами ограничивается только процессором, то получим: 1/C = K/(I + C), откуда можно получить K - число потоков при котором достигается пропускная способность, эквивалентная event driven серверу если K - десятки/сотни, то возиться с событиями нет никакого смысла естественно здесь есть погрешности, допущения и неточности, вот только это не мешает оценить порядок K, а именно порядок K нас и интересует Добавлено через 6 минут и 4 секунды я и не пытаюсь считать такие подробности, меня как правило порядок цифры интересует, а не конкретное значение Это сообщение отредактировал(а) Lazin - 18.11.2011, 11:54 |
|||
|
||||
| mabrarov |
|
|||
|
Шустрый ![]() Профиль Группа: Участник Сообщений: 100 Регистрация: 12.1.2011 Где: Казань Репутация: нет Всего: 9 |
Ну это как сказать. Надо все же отделить event driven, где каждый event loop (active object, actor) крутится в отдельном потоке ("thread-per-object-loop"), от event driven на базе пула потоков ("event driven with thread-pool"). В данном обсуждении под "event driven" понимается как раз "event driven with thread-pool", что, действительно, похоже на костыль. Хотя... все ресурсы ограничены. В любом случае где-то все равно должно быть сужение N (кол-во обслуживаемых клиентов) -> M (кол-во ядер процессора). "thread-per-object-loop", кажется, исповедует Erlang. С теоретической точки зрения этот вариант лучше (естественней) описывает процесс "обслуживания" клиента. Ну и, конечно, природа event driven позволяет логике "thread-per-object-loop" плавно перетекать в "event driven with thread-pool" (простота переноса кода сильно зависит от языка/инструментария). Мой предыдущий пост был как раз про то, что "thread-per-session" тоже "не сахар". Приходится аккуратно работать с shared data. Много логики остается в проверках флагов и синхронизации. Вообще, класс session архитектурно получается сильно связан с понятием thread. Приходится явно выделять shared data и обеспечивать отдельный доступ к этой части session. Так что, чем больше shared data, тем ближе оверкод "thread-per-session" к оверкоду "event-driven" ("thread-per-object-loop" или "event driven with thread-pool"). P.S. Автор темы все еще не определился с архитектурой? Это сообщение отредактировал(а) mabrarov - 18.11.2011, 16:29 |
|||
|
||||
| drug007 |
|
|||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 196 Регистрация: 3.11.2011 Репутация: 1 Всего: 1 |
Определился - асинхронная.
Благодаря обсуждению, я пришел к выводу, что для текущих требований оптимальна будет архитектура thread-per-connection и по сложности и по быстродействию, но она плохо масштабируема, а требования могут поменяться и я решил остановится на асинхронной архитектуре, как более универсальной. У нее ИМХО один недостаток - сложность, но в данном случае он приемлем. |
|||
|
||||
![]()
|
| 0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
| 0 Пользователей: | |
| « Предыдущая тема | C/C++: Сети | Следующая тема » |
|
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |