Russian Qt Forum
Май 03, 2024, 01:36 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2 3 ... 5   Вниз
  Печать  
Автор Тема: Нагрузка на поток  (Прочитано 34242 раз)
kramer3d
Гость
« : Февраль 24, 2010, 09:18 »

Всем привет!

Есть многопоточный сервер, в главном потоке сидит наследник QTcpServer'а и слушает порт, а принятые соединения раздает рабочим потокам, которые сам и создает при необходимости. Один рабочий поток может обслуживать несколько соединений. Характер вычислений, производимых в потоке для какого-либо соединения совершенно непредсказуем. Это может быть простой обмен сообщениями, может быть и запрос в базу, может быть обращение к ядру сервера за какими-то system-wide данными, могут быть вычисления, передача файлов, соединения могут быть защищенными SSL, короче, характер нагрузки на поток количественному анализу не поддается, т.е. нет прямой зависимости от количества подключений.

Вопрос в следующем: есть ли способ оценить нагрузку на поток (т.е. эффективность его Event-Loop'a) средствами Qt, и, если эта нагрузка превысит критический уровень, создать новый?

Держать каждое соединение в отдельном потоке и предоставить операционной системе разбираться самостоятельно может оказаться неэффективным, так как возможна ситуация с большим количеством неактивных/слабо активных соединений, и threading overhead превысит полезную нагрузку по обслуживанию клиентов.
Записан
BRE
Гость
« Ответ #1 : Февраль 24, 2010, 10:49 »

А для чего было идти по пути Apache и сейчас решать его проблемы, о которых еще Владимир Ильич Ленин кажется писал?  Улыбающийся
Почему, пропустили мимо глаз, то что в последнее время частенько используют связку nginx + Apache? Почему не разделить сервер на фронтэнд и бакэнд? Где фронтэнд будет заниматься только отправкой/получением данных для всех клиентов, а готовить эти данные будет бакэнд.
Не обязательно это все разделять физически, это могут быть два объект общающиеся через сигналы.
Думаю этот подход решит многие проблемы.
Записан
ритт
Гость
« Ответ #2 : Февраль 24, 2010, 11:35 »

Вопрос в следующем: есть ли способ оценить нагрузку на поток (т.е. эффективность его Event-Loop'a) средствами Qt, и, если эта нагрузка превысит критический уровень, создать новый?
такого способа нет, насколько я знаю.
можно, конечно, извратиться и сделать примерно так: задать функторам "вес" в относительных единицах (например, обмен сообщениями - 1, соединение с бд - 2, выборка данных - 5, запись в бд - 7, ...) и суммировать значения веса по ходу выполнения...даже звучит корявенько...

Держать каждое соединение в отдельном потоке <snip>
это вообще неэффективно...
Записан
ритт
Гость
« Ответ #3 : Февраль 24, 2010, 11:37 »

А для чего было идти по пути Apache и сейчас решать его проблемы, о которых еще Владимир Ильич Ленин кажется писал?  Улыбающийся
Почему, пропустили мимо глаз, то что в последнее время частенько используют связку nginx + Apache? Почему не разделить сервер на фронтэнд и бакэнд? Где фронтэнд будет заниматься только отправкой/получением данных для всех клиентов, а готовить эти данные будет бакэнд.
Не обязательно это все разделять физически, это могут быть два объект общающиеся через сигналы.
Думаю этот подход решит многие проблемы.

будет такая же проблема с распределением нагрузки на бэкэнд, нет?
Записан
BRE
Гость
« Ответ #4 : Февраль 24, 2010, 12:04 »

будет такая же проблема с распределением нагрузки на бэкэнд, нет?
Случай, где на каждое соединение используется отдельная нить не рассматриваем совсем из-за непомерных расходов ресурсов.
Случай, где несколько клиентов обслуживаются в одной ните. При формировании ответа для одного клиента, останавливаются обслуживание всех остальные клиентов в данной ните. Даже если для большинства клиентов данные уже сформированы и их нужно просто отправлять.
В случае с разделением, получаем следующее. Фронтэнд получает данные от клиента в буфер, если пакет пришел полностью, он передается на обслуживание бэкэнду и фронтэнд о нем забывает (или продолжает принимать следующие данные от клиента). Бекенд принимает решение, запустить другую нить для формирования ответа или при "легком" ответе сформировать его сразу и отдать его фронтэнду. Дальше уже фронтэнд имея данные для клиента небольшими частями начинает отправлять их клиенту.
Бэкэнд может подготовить данные для нескольких клиентов и фронтэнд непрерывно будет раздавать их с учетом возможности клиента (одни могут забирать данные медленно, другие быстро).
Потоки будут создаваться (браться из пула простаивающих) только для конкретных заданий. Передача не будет останавливаться при формировании ответов.
Записан
kramer3d
Гость
« Ответ #5 : Февраль 24, 2010, 12:36 »

Цитировать
В случае с разделением, получаем следующее. Фронтэнд получает данные от клиента в буфер, если пакет пришел полностью, он передается на обслуживание бэкэнду и фронтэнд о нем забывает (или продолжает принимать следующие данные от клиента). Бекенд принимает решение, запустить другую нить для формирования ответа или при "легком" ответе сформировать его сразу и отдать его фронтэнду.
У меня как раз вот этот вариант, разница только в том, что решение о легковесности ответа принимает фронтэнд, а не бэкэнд, а точнее, обработчик данных внутри потока фронтэнда, чтобы не забивать очередь бэкэнда, которому и так есть чем заняться. При этом я хочу сделать фронтэнд многопоточным с однородными потоками. Так что мне нужен критерий создания нового потока фронтэнда. Пока я придумал вот что - в EventLoop'е потока фронтэнда создаю таймер, который постит кастомные события с таймстампом самому себе через равные промежутки времени. Обработчик эти события отлавливает, замеряет время, которое событие простояло в очереди, усредняет/сглаживает его и пишет в член объекта потока. Это получается такая усредненная занятость. Пул менеджер потоки сортирует по этому значению, и по запросу выдает ссылку на поток с наименьшим значением, upd: ну, да, и если потока со значением меньше критического нету, создает новый и выдает ссылку на него. По-моему, довольно кривенько, и оверхед присутствует (вроде небольшой, хотя кто знает), но альтернативы пока не вижу. Пожалуйста, советуйте. Улыбающийся
« Последнее редактирование: Февраль 24, 2010, 12:44 от kramer3d » Записан
BRE
Гость
« Ответ #6 : Февраль 24, 2010, 12:49 »

Т.е. ты успеваешь формировать данные для клиентов, но не успеваешь их отдавать?
Как происходит отдача? Ты же не пытаешься пропихнуть все данные одному клиенту разом?
Описывай?  Подмигивающий
Записан
kramer3d
Гость
« Ответ #7 : Февраль 24, 2010, 13:27 »

Внутри потока фронтенда живет список клиентов (объектов, инкапсулирующих сокеты, по сути). Один клиент, при желании, может иметь несколько подключений (тянуть файл по одному и работать с базой по другому). Клиент отвечает за свои подключения, отчитывается перед бэк-эндом (ядром сервака), пишет в логи и мало ли чего еще. Полученные данные он десериализует и вызывает для них зависящую от их типа функцию-обработчик, которая решает, что дальше с ними делать. Естественно, у него есть и очередь данных на отправку и обработчик событий, который в эту очередь ставит данные, пришедшие от бэкэнда или от других клиентов. По отправке данных (или их верификации, опционально) клиент сообщает их отправителю, что все дошло и все в порядке, и отправитель их прибивает (пока не берем в расчет кэширование, там отдельная песня). Сам по себе клиент - наследник QObject, живет в потоке и участвует в очереди. Кроме того, у него есть куча членов, которые тоже наследники QObject (сокеты, интерфейс к логгеру и т.п.). Так вот я и думаю, не до хрена ли это будет? В том смысле, что если вертеть всю эту банду в одном потоке, при тысяче клиентов уже возможны ощутимые тормоза из-за обработки очереди событий. Тысяча клиентов для этого проекта - это очень много, но я сейчас пока на этапе проектирования и программирования прототипов, и хочу предусмотреть возможность масштабирования в будущем.
Записан
BRE
Гость
« Ответ #8 : Февраль 24, 2010, 14:00 »

Достоинство описанного метода как раз в том, что он может отдавать данные практически одновременно всем клиентам в одном потоке. При необходимости, есть возможность масштабирования, например по количеству ядер процессора.
Главное это отдавать данные частями, а не упираться в одного клиента и пихать ему весь блок целиком. С каждым клиентом ассоциируется выходной буфер (это абстракция, может быть буфер в памяти, а может быть файлом на диске), если в нем есть данные они должны быть отправлены. В цикле проверяем для каждого клиента, если данные есть, отправляем кусок (chunk) и переходим к следующему клиенту. Размер "куска" можно задать фиксированным или проверять количество свободных байт в буфере отправки tcp-стека.
Этот метод легко ложиться на архитектуру Qt-сокетов, т.к. они работают по принципу не блокируемых сокетов.
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #9 : Февраль 24, 2010, 14:35 »

2 BRE,
это сродни "Теории конечных автоматов" ? Улыбающийся
т.е. предлагаете нечто подобное автоматному программированиию?
Записан

ArchLinux x86_64 / Win10 64 bit
kramer3d
Гость
« Ответ #10 : Февраль 24, 2010, 14:44 »

Хммм. Поправь меня если я ошибаюсь. Насколько я знаю, буферизацией в данном случае занимается QIODevice, от которого отнаследован QTcpSocket. Т.е. данные из очереди на отправку сериализуются в сокет с помощью write() или QDataStream, сокет их буферизует, и уже сам решает, когда и какими кусками их отправлять. Отправив кусок, эмитит bytesWritten, который воткнут в слот моего объекта-клиента, где я проверяю, есть ли у сокета еще данные в буфере, и если нет, то сериализую туда следующий объект из очереди. Сокет, как я понимаю, работает асинхронно, в цикле событий потока. Так что я совсем не забочусь об отправке данных по частям параллельно всем клиентам - это должен за меня делать эвент-луп, который обходит все объекты, созданные в потоке, и говорит им, что делать. Сокет в момент вызова write() ничего никуда не пишет, а просто копирует данные в свой буфер. Когда метод, вызвавший write(), вернет управление циклу событий, и когда этот цикл дойдет до объекта сокета, вот тут-то кусок данных будет отправлен непосредственно на интерфейс, и проэмичен сигнал bytesWritten.
Цитировать
При необходимости, есть возможность масштабирования, например по количеству ядер процессора.
Это подразумевает выполнение в несколько потоков, так ведь? Пусть даже их количество фиксировано и равно числу ядер, но мне в любом случае необходимо будет при новом входящем подключении выяснить, какой их них наименее загружен, чтобы отдать это подключение именно ему. А как я раньше писал, в моем случае прямой корреляции между загрузкой потока и количеством обслуживаемых подключений нет. Хотя бы потому, что подключения могут быть шифрованными, что съедает немало процессорного времени, а могут не быть. Вот к тому и вопрос, как выяснить загрузку потока?
Какие есть мнения по поводу вот этого:
Цитировать
Пока я придумал вот что - в EventLoop'е потока фронтэнда создаю таймер, который постит кастомные события с таймстампом самому себе через равные промежутки времени. Обработчик эти события отлавливает, замеряет время, которое событие простояло в очереди, усредняет/сглаживает его и пишет в член объекта потока. Это получается такая усредненная занятость. Пул менеджер потоки сортирует по этому значению, и по запросу выдает ссылку на поток с наименьшим значением, upd: ну, да, и если потока со значением меньше критического нету, создает новый и выдает ссылку на него. По-моему, довольно кривенько, и оверхед присутствует (вроде небольшой, хотя кто знает), но альтернативы пока не вижу.
Какие тут могут быть подводные камни?
Записан
BRE
Гость
« Ответ #11 : Февраль 24, 2010, 15:13 »

Хммм. Поправь меня если я ошибаюсь. Насколько я знаю, буферизацией в данном случае занимается QIODevice, от которого отнаследован QTcpSocket....
Да Qt-сокет все кеширует и отправляет частями, только что делать если основной массе клиентов отдаются огромные файлы (образы DVD). Куда это будет кешироваться, что будет с памятью и свопом? Как будет распределяться нагрузка на канал связи? Первым клиентам уйдут огромные куски, остальные будут курить в стороне. А можно принудительно отдавать каждому клиенту по очереди блоками по 20-40 Кб.

Это подразумевает выполнение в несколько потоков, так ведь? Пусть даже их количество фиксировано и равно числу ядер, но мне в любом случае необходимо будет при новом входящем подключении выяснить, какой их них наименее загружен, чтобы отдать это подключение именно ему. А как я раньше писал, в моем случае прямой корреляции между загрузкой потока и количеством обслуживаемых подключений нет. Хотя бы потому, что подключения могут быть шифрованными, что съедает немало процессорного времени, а могут не быть. Вот к тому и вопрос, как выяснить загрузку потока?
Если отдавать равными блоками, то нагрузка на поток будет делиться на всех клиентов, для которых есть готовые данные.
Например, есть два потока фронтэнда, в одном подготовлены (ждут отправки) данные для 100 клиентов, во втором для 20 клиентов. Значит мы можем из первого потока перенести во второй 40 клиентов. Время отправки данных фиксировано, т.к. размер куска одинаковый.

Какие есть мнения по поводу вот этого:
...
Сейчас подумаю...  Улыбающийся
Записан
BRE
Гость
« Ответ #12 : Февраль 24, 2010, 15:17 »

Плюс, можно назначать разные приоритеты на отдачу, одним клиентам отдавать по 100 кб за раз, а другим урезать до 20 кб.
Записан
kramer3d
Гость
« Ответ #13 : Февраль 24, 2010, 15:44 »

Да Qt-сокет все кеширует и отправляет частями, только что делать если основной массе клиентов отдаются огромные файлы (образы DVD). Куда это будет кешироваться, что будет с памятью и свопом? Как будет распределяться нагрузка на канал связи? Первым клиентам уйдут огромные куски, остальные будут курить в стороне. А можно принудительно отдавать каждому клиенту по очереди блоками по 20-40 Кб.
Ааа. Теперь понял. Это предусмотрено. Есть специальный тип данных, отвечающий передачу файлов, у него специальная сериализация/десериализация, которая подсасывает файл кусками с диска, или также кусками пишет принятые данные на диск, попутно считая контрольные суммы. Улыбающийся В бэкэнде есть диспетчетр файлов, который отправляет клиентам файлы и за этим всем следит.
Если отдавать равными блоками, то нагрузка на поток будет делиться на всех клиентов, для которых есть готовые данные.
Например, есть два потока фронтэнда, в одном подготовлены (ждут отправки) данные для 100 клиентов, во втором для 20 клиентов. Значит мы можем из первого потока перенести во второй 40 клиентов. Время отправки данных фиксировано, т.к. размер куска одинаковый.
Короче, я идею понял. Я в клиент/серверном программировании пока новичок, это первый большой проект. Буду думать, как это адаптировать под свои реалии. Получается, нужен еще один уровень абстракции над сокетом - блок данных. И тогда нагрузка на поток будет характеризоваться количеством блоков.
Сейчас подумаю...  Улыбающийся
Спасибо! Вопрос безотносительно серверной архитектуры пока так и остается открытым. Т.е. как быть, если есть поток, производящий вычисления исходя из поступающих извне данных? Как этому потоку понять, что он загружен по самое не могу и больше данных принимать не может?
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #14 : Февраль 24, 2010, 18:46 »

Пока я придумал вот что - в EventLoop'е потока фронтэнда создаю таймер, который постит кастомные события с таймстампом самому себе через равные промежутки времени. Обработчик эти события отлавливает, замеряет время, которое событие простояло в очереди, усредняет/сглаживает его и пишет в член объекта потока. Это получается такая усредненная занятость. Пул менеджер потоки сортирует по этому значению, и по запросу выдает ссылку на поток с наименьшим значением, upd: ну, да, и если потока со значением меньше критического нету, создает новый и выдает ссылку на него. По-моему, довольно кривенько, и оверхед присутствует (вроде небольшой, хотя кто знает), но альтернативы пока не вижу. Пожалуйста, советуйте. Улыбающийся
Красиво но сложно  Улыбающийся Почему не простенько: задачи принимаются и складываются в одну общую очередь. Каждая нитка берет следующую задачу из этой очереди как только закончит предыдущую. Число ниток просто фиксировано. Вероятно задачи могут быть зависимыми, напр задача "B" может требовать результатов задачи "A". Мне кажется не стоит решать это созданием новых ниток (напр. нитка не может принять "C" потому что она ждет "B", давайте создадим еще нитку). Лучше сохранять результаты/контекст а потом восстанавливать.
« Последнее редактирование: Февраль 24, 2010, 18:48 от Igors » Записан
Страниц: [1] 2 3 ... 5   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.056 секунд. Запросов: 22.