Статьи

Упрощение услуг с низкой задержкой

обзор

Java Chronicle  
— это постоянная межпроцессная система обмена сообщениями, которая очень быстро работает при низком уровне. Однако, если вам не нужна эта предельная скорость, есть несколько простых способов использовать эту библиотеку с открытым исходным кодом. Один из них использовать распределенные коллекции Chronicle. Это очень просто в использовании, но довольно медленно. 

Этот пост исследует промежуточное решение. Это быстрый (менее 10 микросекунд, 99,9% времени), сверхнизкий ГХ и работает хорошо, даже если у вас есть пакет данных больше, чем объем основной памяти.

Этот пост продолжается от сервисов с
малой задержкой,  и демонстрация представляет собой реализацию шлюзов и механизма обработки на диаграмме.

Сервис по контракту


Чтобы смоделировать службу, нужно иметь интерфейс для методов / запросов / событий, которые вы хотите поддерживать, и другой интерфейс для событий вне механизма обработки.
Демонстрация была добавлена, чтобы продемонстрировать этот подход.

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

public interface Gw2PeEvents {
    public void small(MetaData metaData, SmallCommand command);
}
public class SmallCommand implements ExcerptMarshallable {
    public StringBuilder clientOrderId = new StringBuilder();
    public String instrument;
    public double price;
    public int quantity;
    public Side side;
Простой ответ на запрос выглядит следующим образом

public interface Pe2GwEvents {
    public void report(MetaData metaData, SmallReport smallReport);
}
public class SmallReport implements ExcerptMarshallable {
    public CharSequence clientOrderId = new StringBuilder();
    public ReportStatus status;
    public CharSequence rejectedReason = new StringBuilder();

Класс MetaData оборачивает временные метки для сквозного процесса.
Он записывает десятую часть микросекунды времени, когда
  • запрос написан
  • запрос прочитан
  • ответ написан
  • ответ прочитан.
  • Включает в себя sourceId и eventId, запускающий ответ, необходимый для перезапуска

Как это работает?


Пропускная способность на i8 3,8 ГГц с двумя шлюзами, генерирующими по 10 миллионов входящих и 10 миллионов исходящих сообщений, занимает 12,2 секунды для возврата к шлюзам, которые их отправляют, или 1,6 миллиона запросов / ответов в секунду.
Для 200 миллионов сообщений скорость SSD начинает иметь значение, поскольку размер дискового кэша превышен, а производительность упала до 200 миллионов за 176 секунд или 1,1 миллиона в секунду.

Критические задержки, на которые нужно смотреть, — это не средние или типичные задержки, а более высокие процентили.
В этом тесте 90-й, 99-й и 99,9-й процентили (наихудшие 10%, 1% и 0,1%) составляли 3,3 
мкс, 4,9  
мкс, 9,8  
мкс. При более высокой нагрузке с 200 миллионами запросов и 200 миллионами ответов, при превышении объема основной памяти задержки увеличились до 4,2  мкс, 6,8 мкс и 28,8 мкс

Очень мало систем, которые могут обрабатывать всплески активности, которые превышают объем основной памяти, не слишком снижая производительность.
(В 1,5–3 раза хуже)
Если вы запустите этот тест с -verbosegc, вы можете увидеть небольшой GC при запуске с небольшим размером кучи, равным 16 МБ, однако демонстрационная версия предназначена для создания менее одного объекта на запрос / ответ, и вы не получите дополнительные GC.
Примечание: этот тест не включал разминку, чтобы сделать числа более репрезентативными для того, что вы можете увидеть в реальном приложении. то есть хуже, чем обычно показывает микробенчмарк.

Как выглядит профиль памяти?

Профиль памяти в основном плоский. Если вы посмотрите на процессы с помощью VisualVM, то увидите, что память используется для опроса процесса (то есть, когда вы ничего не делаете)
Если вы используете -XX: -UseTLAB для более точного использования памяти и jstat -gccapacity, он не показывает использования памяти для 200 миллионов запросов и ответов. Это не правильно, поскольку Chronicle действительно использует некоторую кучу, но jstat показывает только использование страницы (кратно 4 КБ), то есть использование менее 4 КБ.

Что такое ExcerptMarshallable?

Этот интерфейс в основном такой же, как Externalizable, однако он поддерживает все функциональные возможности Excerpts для повышения производительности и повышения плотности потока. При наличии больших объемов данных данные службы могут быть ограничены пропускной способностью памяти дисковой подсистемы, это может иметь значение.
По сравнению с Externalizable в реальных программах можно ожидать, что половина данных будет сериализована и удвоит пропускную способность.
ExcerptMarshallable предназначен для поддержки утилизации объектов. Большой процент затрат на десериализацию приходится создавать новые объекты, которые заполняют кэш вашего ЦП мусором, замедляя всю программу, не только код десериализации, но и весь код, работающий на том же сокете (включая другие программы на это гнездо)

@Override
public void readMarshallable(Excerpt in) throws IllegalStateException {
    // changes often.
    clientOrderId.setLength(0);
    in.readUTF(clientOrderId);
    // cachable.
    instrument = in.readEnum(String.class);
    price = in.readDouble();
    quantity = in.readInt();
    side = in.readEnum(Side.class);
}
@Override
public void writeMarshallable(Excerpt out) {
    out.writeUTF(clientOrderId);
    out.writeEnum(instrument);
    out.writeDouble(price);
    out.writeInt(quantity);
    out.writeEnum(side);
}

Другие преимущества


Если вы считаете, что выполняется запись каждого отправленного сообщения, включая подробные отметки времени 0,1 мкс, вы получаете большую поддержку для очень точного отслеживания времени с минимальными затратами для приложения.

Что еще более важно, вы получаете детерминированный сервис, который можно воспроизвести, чтобы обеспечить воспроизводимость поведения и производительности.

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

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

Вывод


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

Резюме производительности
  • 20 миллионов: 1,6 миллиона в секунду, задержки 90/99 / 99,9%: 3,3  мкс, 4,9  мкс, 9,8  мкс. 
  • 200 миллионов: 1,1 миллиона в секунду, задержки 90/99 / 99,9%:   4,2  мкс, 6,8 мкс, 28,8 мкс

20 миллионов запросов и ответов помещаются в дисковый кеш и не оказали существенного влияния на производительность дисковой подсистемы.

200 миллионов запросов и ответов превысили дисковый кеш.