Как стать автором
Обновить

Комментарии 29

>А вот производительность и надежность Tomcat, проверенного годами и тысячами разработчиков, не вызывает вопросов.

Т.е. netty, которому тоже не мало лет и на котором построено очень много продуктов, не вызывает у вас доверия?

И еще, посмотрите на protobuf в качестве протокола. Очень удобная штука.

P.S. Ваша цифра 0.09 мсек на запрос. Что именно вы измеряли? Т.е. время между чем и чем?
Спасибо, что прочитали:)

Нет, что вы, производительность Netty никаких вопросов не вызывает абсолютно. Я думаю, что ее писали хорошие программисты и она очень хорошо оптимизирована. Имелось ввиду, что мое решение несет «некий» оверхед + многопоточная работа, которую нужно довести до ума и хорошенько отладить. Все это сыграло не в пользу моего «велосипеда».

>И еще, посмотрите на protobuf в качестве протокола. Очень удобная штука.
Смотрел, интересная вещь. Изначально планировал взять JSON для реализации буфера, но он мне показался слишком «жирным» для тестов. Мб, решись я доводить систему до ума, я бы реализовал JSON или protobuf. Когда понадобится использовать ее для чата или других «узких» для http мест, я интегрирую туда какой-нибудь готовый протокол.

>P.S. Ваша цифра 0.09 мсек на запрос. Что именно вы измеряли? Т.е. время между чем и чем?
Банально мерялся пинг. Т.е. измерения проводились на клиенте с момента отправки данных и до получения первой порции.
Просто тема интересна.
У меня, тут же, есть статья, пересекающаяся с вашей по теме и содержанию.
Ну и думаю тут нет оверхеда, ибо время на его реализацию очень мало и сравнимо с реализацией без него. Просто правильные технологии и хорошая архитектура дают многие преимущества. А так же позволяют не наступить на многие грабли.
Пинг я не измерял. Привык измерять производительность запросами/сек.
На Core i5, сервер на netty из моей статьи переваривает порядка 300000 запросов/сек. И это с сериализацией тасков protobuf.

P.S. Но после того как я увлекся Scala и переписал ядро на ней, получил >800000 запросов/сек, кода раза в 3 меньше и другие полезные и нужные ништяки.
Я читал вашу статью, но уже после того, как разобрался с Netty. В частности, у вас увидел организацию Decoder через чек-поинты и сделал также, ибо удобнее.

>Ну и думаю тут нет оверхеда, ибо время на его реализацию очень мало и сравнимо с реализацией без него. Просто правильные технологии и >хорошая архитектура дают многие преимущества. А так же позволяют не наступить на многие грабли.
Да, можно согласится, но многопоточность- штука сложная и требует идеальной реализации, и было бы не очень приятно, если, внезапно, в проект пойдут пользователи и на нагрузке начнут лезть баги, которых не было на синтетических тестах.

Насчет архитектуры согласен, грамотно проектировать- всегда важно. Но я пока что только учусь.

>На Core i5, сервер на netty из моей статьи переваривает порядка 300000 запросов/сек. И это с сериализацией тасков protobuf.
protobuf, кстати, оказывается есть для AS3.0, я этого не знал.
Это очень серьезная цифра:) Запросы легковесные?
Я так понимаю, что парсинг что protobuf, что JSON работает через reflection, поэтому это не очень быстро.

Ваш тест, скорее, показывает пределы нагрузки. В идеале, нужно мерять пинг именно на больших нагрузках, когда загружены все логические ядра процессора и все серьезно.

А как вы тестируете? Мб мне тоже попробовать написать подобный тест. Я читал пару статей про jMetter. Или вы просто стандартными средствами тестили?

>P.S. Но после того как я увлекся Scala и переписал ядро на ней, получил >800000 запросов/сек, кода раза в 3 меньше и другие полезные и >нужные ништяки.
Scala- это интересно и круто, но я пока не готов к таким «преждевременным» оптимизациям. Ибо сейчас у меня нет проекта, в котором огромный поток пользователей и требуется срочно решать проблему с нагрузками на сервер.
Да и я пилю «типичную» браузерную игру, поэтому, у меня на таких запросах быстрее ляжет база данных, чем сервер с логикой.
Да, я написал для себя стандартный тест, чтобы измерять одинаковую величину на разных реализациях и таким образом не сравнивать ящики с бутылками.
Он простой, какое-то количество потоков которые непрерывно отсылают стандартный запрос логина в игру, состоящий из двух полей, имя и пароль.

Если загружены все логические ядра, то это уже ахтунг полный, такого никогда не должно быть.
И да, БД одно из самых узких мест в системе, но ведь игровой запрос!=запрос в базу, иначе даже для небольшого количества запросов в сек надо будет серьезное железо ставить.

Ну и Scala это не оптимизация, это очень удобный язык для реализации своих идей. Просто ее система акторов позволила сделать очень простой и быстрый многопоточный код.
>Он простой, какое-то количество потоков которые непрерывно отсылают стандартный запрос логина в игру, состоящий из двух? полей, имя и пароль.
Попробую написать на досуге

>Если загружены все логические ядра, то это уже ахтунг полный, такого никогда не должно быть.
Подразумевалось, что ядра используются максимально эффективно и ничего не простаивает. Глупо было бы на 6-ядерном i7 работать в 4 потока, когда доступно 12.

>И да, БД одно из самых узких мест в системе, но ведь игровой запрос!=запрос в базу, иначе даже для небольшого
>количества запросов в сек надо будет серьезное железо ставить.

Для всевозможных ферм- увы. Для работы с БД сейчас используется Hibernate. У него есть кеш, конечно, но все равно вся логика завязана на внешней базе. В идеале, хотелось бы перейти на inMemory базу, но пока большого смысла не вижу.

>Ну и Scala это не оптимизация, это очень удобный язык для реализации своих идей.
Я знаю, что это язык :) Я имел ввиду, что изучать ее в данный момент для меня только для того, чтобы система работала еще быстрее- нет смысла, потому что сейчас нет никакой нагрузки. Логичнее сначала сделать прототип и посмотреть, реально ли с моей идеей набрать нагрузку в принципе. Может, никто не оценит)

Для всевозможных ферм- увы. Для работы с БД сейчас используется Hibernate. У него есть кеш, конечно, но все равно вся логика завязана на внешней базе. В идеале, хотелось бы перейти на inMemory базу, но пока большого смысла не вижу.
Когда занимался фермочками и другими социально ориентированными играми, почему-то работа с базой заключалась лишь в двух событиях:
1. достать все при логине
2. сохранить все, после логаута.

И хибернейт… брр.
Игра изначально не социалка, как фермочка- сравнение не совсем корректное, я имел ввиду механику игры(кликер по сути, но со своими элементами). Хотя клиент легко портировать под соц сети, если понадобиться.

А вы использовали готовое решение для хранение данных или писали свое?

А гибернейт- он работает, вроде бы даже быстро и стабильно:)

P.S. Меня давно не покидает идея inMemory хранилища, но, я думаю, мой скилл недостаточно высок для самостоятельной реализации таких вещей.
Мы использовали и хибернейт, и ручкамки писали голый sql через jdbc, и даже монгу. Последняя для всего этого, мне кажется, подходит идеально.
Монга- эт интересно, да. Но и хибернейта, вроде бы, пока хватает. А вот писать SQL ручками- ну не знаю, пока нет конкретных узких мест, не вижу смысла. Да и гибернейт поддерживает запросы на чистом SQL, насколько я знаю…
У хибернейта есть одна проблема: вышел из скоупа сессии — получи перед апдейтом полную выборку сохраняемого графа объектов.
Вот одного не пойму, зачем тут SocketSession . Этож совершенно бредовая вещь по сути своей.
Чтобы можно было написать примерно такое:
public class Chat extends Servlet
{
     @Override
     public void doRequest(InputBuffer input, OutputBuffer output, SocketSession session)
     {
           String message= input.getPar("message");
           OutputBuffer out= new OutputBuffer();
           out.setPar("message", message);
           for (SocketSession ses: SocketServletContainer.getInstance().getListSession())
           {
                 ses.send(out, "Chat");
           }

           output.setPar("state", "succsess");
     }
}


Вот вам и простой чатик. В терминологии Tomcat для WebSocket мои SocketSession- это Connection, кажется.
Я немного про другое, я про то, что внутри этого класса: а именно это невероятно странное ограничение, которое сделано невероятно странным образом.
Вы про выдачу id-ниов? Я ж там написал, что константа в 20000, в идеале, должна подбираться под конкретное железо. Лучше ее вообще вынести в конфиг, просто я этого не сделал.

Просто все id должны быть уникальными, чтобы, например, при логине можно было привязать id сессии к игроку и послать сообщение конкретному игроку(при реализации private-чатов, например).

Более простой системы выдачи уникальных id как-то не пришло в голову. Можно, конечно, складывать все, что уже выдано, в list, но тогда при выдаче придется делать поиск по List для проверки уникальности, в общем, мое решение мне показалось более изящным. Мб я что-то упускаю.
У нас, в глуби кода есть такое
public class LocalIdentifierSequencer implements IdentifierSequencer {

    private final AtomicInteger integer;

    public LocalIdentifierSequencer(int start) {
        this.integer = new AtomicInteger(start);
    }

    public LocalIdentifierSequencer() {
        this(1);
    }

    @Override
    public int identifier() {
        return integer.getAndIncrement();
    }
}
Изначально так и было, но потом посетила мысль, что возможно банальное переполнение при активном подключении\отключении клиентов. Допустим, клиент с id=1 подключен и висит достаточно долго(реальный игрок). Тут злоумышленник начинается подключаться\отключаться от сервера, в итоге происходит переполнение int-а и мы снова имеем значение 1.

Мб проблема притянута за уши, но все же. Я именно поэтому выбрал способ проверки ключей через массив.
Если волнует такая проблема… то думаю тогда стоит обратить внимание на AtomicLong.
Ниже предложили более простое решение, которое я как-то просмотрел)
>> Более простой системы выдачи уникальных id как-то не пришло в голову.
Channel.getId() — готовый уникальный идентификатор.

>>synchronized(channel)
зачем ??

Нафига для bossExecutor использовать OrderedMemoryAwareThreadPoolExecutor?
>Channel.getId() — готовый уникальный идентификатор.
А ларчик просто открывался, действительно)

>зачем ??
Дабы чего не вышло. Я как-то упустил момент, является ли Channel thread-safe реализацией или нет. Если нет, то возможно одновременное чтение и запись с нескольких потоков.

>Нафига для bossExecutor использовать OrderedMemoryAwareThreadPoolExecutor?
Пишут, что они самые эффективные по тестам. Своих не проводил, поверил так.
Советую вам посмотреть как выглядитOrderedMemoryAwareThreadPoolExecutor изнутри.
Каналы — полностью thread safe.

OrderedMemoryAware — нужен не для эффективности, а для того чтобы соблюдался порядок выполнения событий в канале + защита от ООМ.
Для boss, нафиг не нужен, да и для worker тоже. Лучше создать отдельный executionHandler и запихать его в конец pipeline, если вас заботит очередность обработки событий в канале и есть какая-то блокирующая работа + можно выкинуть ваш QueueHandler.

connectTimeoutMillis — не имеет смысла для серверных каналов.
Тогда соглашусь, что synchronized(){} там не нужен.

QueueHandler сделал для того, чтобы не заботиться о логике, реализованной в сервлете и чтобы разделить работу с сетью и обработку логики. Чтобы это были разные threadPool. Концептуально, так сказать.

По названию догадывался о назначении OrderedMemoryAwareThreadPoolExecutor, но изнутри устройство не изучал. Его выбрал, собственно, потому что пару раз мелькал в приведенных по Netty примерах.
Один из лучших способов отладки логики любого приложения — это сохранение всех внешних сообщений и порядка в котором они пришли. ожно либо писать асинхронно по типу node.js-скрипта, либо записывать лог отдельно для каждого потока, добавив в него ещё и данные запрашиваемые из общей памяти.
Годная статья.
Просто напомню что если использовать Jetty с вебсокетами то код будет гораздо легче.

new String(bytes)

Тут осторожней, либо подавайте utf8 в new и getBytes, либо джаву запускайте с -Dfile.encoding=UTF8.
Ну дык в преамбуле написано, что это- велосипед. Иногда хочется поисследовать и написать что-нибудь свое.
Вебсокеты прекрасно себя чувствуют и в Tomcat, насколько я знаю.

Правда у вебсокетов, насколько я понимаю, тройное рукопожатие сделано по http-протоколу. Как реализовать это, например, на флеш- непонятно. Видимо, придется вручную составлять заголовок и слать http-запрос. Или я не прав?

>Тут осторожней, либо подавайте utf8 в new и getBytes, либо джаву запускайте с -Dfile.encoding=UTF8.
Там все UTF-8, разумеется.
> Как реализовать это, например, на флеш- непонятно
Гугл сразу выдал github.com/y8/websocket-as

> Там все UTF-8, разумеется.
Так где конкретно он прописан? А то ведь попадётся OS с недефолтным UTF-8.
>Гугл сразу выдал github.com/y8/websocket-as
Посмотрел, внутри примерно так и работает. Но интересно.

>Так где конкретно он прописан? А то ведь попадётся OS с недефолтным UTF-8.
Я бы запускал с ключом encoding=UTF8
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории