Pull to refresh

Comments 36

Очень хорошо описано, только одно замечание — в slab хранить пары token-socket все же лучше чем в HashMap.
Да, вы правы. В этой статье HashMap используется намеренно, для упрощения примеров и для того, чтобы оставить поле для дальнейшей оптимизации, которая будет описана в одной из следующих частей. :)
Полез в оригинал — там тоже есть замечание про slab с комментарием автора в духе «неосилил» :-)
Каюсь, неосиливший автор оригинала — это тоже я. :)
Блин, так глубоко я не залез! Тогда рассказываю: размер slab задается один раз и не меняется, т.е. этим размером и лимитируется количество подключений, но зато получение нового слота в slab очень быстрое. В данном случае нужен Slab::new_starting_at() со сдвигом 1, т.к. токен 0 уже занят для входящего сокета.

На линуксе по умолчанию: net.core.somaxconn = 128, насколько я помню, традиционно размер slab ставят эквивалентным беклогу.
Проблема в том, что Вебсокетный сервер может держать множество висящих подключений — см., например, проблему C10K. Но думаю, что это должно решаться аллокацией новых slab'ов при «исчерпании» старых — только это усложняет код, поэтому в примерах и используется HashMap.

Короче говоря, нужно все досконально тестировать и запускать микробенчмарки — не хотелось с самого начала смещать фокус статьи на такие суровые детали реализации.
да, был не прав, somaxconn это как раз размер беклога. Открытые сокеты упираются в лимит по fd.
Спасибо за статью! mio выглядит довольно интересно. С ней, кстати, работают такие библиотеки как mioco и coio-rs, которые дают чуть более высокоуровневый интерфейс. Может быть, их тоже возможно как-то применить в вашем случае?

Пара небольших замечаний: в функцию gen_key лучше передавать &str, а не &String — &String вообще никогда не имеет смысла использовать, а конструкцию типа "abcde".as_bytes() можно заменить на b"abcde".
Тогда уже AsRef<str> можно сразу.
Я сам точно не уверен, когда AsRef нужно применять, но точно знаю, что &str однозначно идиоматичнее &String. В данном случае AsRef, имхо, это перебор.
В std есть две реализации:

impl AsRef<str> for str impl AsRef<str> for String

Так что его стоит применять там где на входе может быть &str или String, а используется только &str.
Да, но в подавляющем большинстве случаев если вы используете только &str, то разрешать передавать String нет никакого смысла — в этом случае произойдёт передача права владения, которая совершенно бессмысленна, если используется только &str. Поэтому я и говорю, что я не вижу смысла в AsRef.
Смысл в том, что принимать format!() как аргумент становится проще
Спасибо! Насчет String, вы, конечно, правы — я еще не до конца освоился в Rust на момент написания статьи.

Может быть, их тоже возможно как-то применить в вашем случае?
Да, звучит интересно — но так или иначе, в дальнейшем предполагается выстраивать собственный высокоуровневый интерфейс для работы с Вебсокетами.
Есть еще проект Rotor, мне понравился его подход к композиции протоколов внутри mio — я подумаю над тем, как его можно будет использовать для следующих частей статьи.
Да, умеет — ничего особо в этом ему мешать не должно.

Рассмотрение многопоточных циклов событий в планах на следующие части. Следите за обновлениями. :)
Таки нет, сам не умеет:
The following are specifically omitted from MIO and are left to the user or higher level libraries.

File operations
Thread pools / multi-threaded event loop

Что означает, что это нужно делать самому, либо искать более высокоуровневую обертку, в которой это реализовано.
ничто не мешает форкнуться на N-процессов перед запуском mio event loop.
Ничто не мешает. Как ничто не мешает запустить несколько потоков, в каждом запустить event loop, и диспетчеризировать подключения между ними в рамках одного процесса. Но сам mio ни того, ни другого не делает, и в планы его развития это не входит.
запустить несколько потоков, в каждом запустить event loop, и диспетчеризировать подключения между ними в рамках одного процесса
Ну, на самом деле я именно так и планировал сделать и рассказать об этом в одной из следующих статей.
Полагаю, что вопрос cy-ernado касается самой теоретической возможности разброса event loop'ов на несколько ядер/процессоров — а с этой точки зрения, конечно, mio все умеет. :)
Буду с нетерпением ждать следующую статью. Я сам в скором (надеюсь) времени собираюсь приступить к «препарированию» mio, будет полезно почитать о Вашем опыте.
habrahabr Feature Request: Цикл статей. Хорошо бы иметь возможность подписаться каким-нибудь образом на цикл статей. Так же, можно сделать удобную навигацию по статьям.
можно сделать удобную навигацию по статьям.
Для этого, кстати, достаточно разрешить атрибут id у заголовков (ну или атрибут name у тегов «a»). Сначала хотел сделать с оглавлением, но все якоря беспощадно вырезаются HTML-парсером Хабра.
Точно, спасибо! Лучше переделаю на использование стандартного класса mio, TcpListener.
Код в статье и в репозитории на GitHub обновлен.
Еще раз спасибо, megaserg! :)
В начале главы 7, видимо, нужен register вместо register_opt ;) у меня компилятор ругается:
src/main.rs:25:16: 28:41 error: no method named `register_opt` found for type `mio::event_loop::EventLoop<_>` in the current scope
src/main.rs:25     event_loop.register_opt(&server_socket,
src/main.rs:26                         Token(0),
src/main.rs:27                         EventSet::readable(),
src/main.rs:28                         PollOpt::edge()).unwrap();
Ага, спасибо! Я уже раз в третий переписываю примеры в статье. :) API постоянно меняется.
UFO just landed and posted this here
Я думаю что поддержка Windows в mio пока еще очень сырая. Но вообще для подобных вопросов у них существует канал в IRC — irc.mozilla.org/#mio.

Ну и, честно говоря, Windows при написании статьи я вообще не учитывал. Наверное, это плохо, но из моей практики серверы под Win — исчезающе редкое явление. Я бы на вашем месте экспериментировал в виртуальной машине с Linux'ом.
UFO just landed and posted this here
"в современных серверных процессорах обычно есть от 8 до 16 ядер, и если мы создаем больше потоков, чем позволяет “железо”, то планировщик ОС перестает справляться с переключением задач с достаточной скоростью."

Я бы поспорил. В 2007-м на машине с 4 ядрами мы гоняли систему с 8000 (восемью тысячами) нитей. Именно в обслуживании http-запросов. Правда, потребовался тюнинг настроек ядра и стандартную jvm заменили на, ЕМНИП, jrockit.
Не спорю, конструкция фразы не совсем удачная — подразумевалось скорее значительно превышающее разумные пределы кол-во тредов.

В этом плане 8000 — относительно небольшое число, потому что известная "проблема C10K" уже в далеком прошлом. Как себя в тех же условиях покажут, скажем, 100 тысяч потоков? А миллион? А 10 миллионов? :) Это более актуальные на сегодняшний день цифры.
А реально ли актуальные? Миллион активных соединений вряд ли отработает сам сервер. Миллиону неактивных, казалось бы, неоткуда взяться… Конечно, по сути, нельзя ограничиваться просто числом и надо описывать распределение — скорость поступления запросов (в секунду), и распределение запросов по шкале стоимости — столько процентов висят без обслуживания, столько обслуживаются коротко (Out of memory), столько — обслуживаются дорого.
для тестов поднимал 2кк активных соединений на домашнем компе с 7ми летним процом… потребовалось гигов 12 оперативы, было интересно все это тестировать)
Поднять можно многое, но в реальности же за соединением — бизнес-логика. По опыту, упираемся не в соединения, не в треды, а в производительность бизнес-логики.
Sign up to leave a comment.

Articles