Обновить
16K+
4
Александр@AlexB2012

Архитектор

15
Рейтинг
Отправить сообщение

Гигантская нагрузка. 500к на кластер это прилично. Мой кеш 80к + на ноду + обычно 1 коннекшн на приложение - мультиплексирование. Я думаю что Lettuce все таки использует больше соединений чем 1, но меньше чем Jedis. Event loop это да - просто редис сам по себе требует много коннекшнов - он так устроен. Хотел бы я попробовать свое решение на таких нагрузка. Удачи с редисом - это очень интересно.

Интрересный опыт. Выбор Lettuce vs Jedis понятнен. А какую нагрузку у вас держит Redis? Я просто его использовал для создания собственного сервера кеша. Интересно сравнить ваш кейс использования Redis и моего кеша.
Кстати сильно количество коннекшнов упало? Формально не должно было, так как и там и там есть управление пулом.

Как работает планировщик — тема глубокая. Главное понимать: поток потоку рознь.

Бывают потоки IO-bound (которые 99% времени спят в ожидании сокета или диска), а бывают CPU-bound например код

int i=0;
while (true){
 i++;
}

Почему система спокойно держит тысячи потоков на 8 ядрах? Потому что большинство из них спят и не потребляют кванты CPU. Но если запустить несколько CPU-bound потоков, начинается интересное:

  1. Вытеснение и честность. Если у вас 1 ядро и 2 потока с while(true), планировщик не даст одному из них работать монопольно. В Linux (CFS/EEVDF) планировщик ведет виртуальное время наработки (vruntime). Поток, молотящий цикл, быстро накручивает vruntime и отправляется в конец очереди, уступая место другим. Ресурсы ядра поделятся между ними ровно 50/50.

  2. Приоритет «спящих». Если проснется поток, ждавший сокет, его vruntime будет минимальным. Планировщик вытеснит while(true) мгновенно, отдаст квант времени сокету, и только когда тот снова уснет — вернется к вычислениям.

  3. Cache Affinity. Планировщик старается «пиннить» активный поток к конкретному ядру, чтобы не сбрасывать L1/L2 кэш (отсюда 100% загрузка конкретного ядра на графиках).

Формулу «1 поток = 1 ядро» используют для High-Load сервисов не потому, что ОС не справится с большим числом, а чтобы исключить Context Switch Overhead и выжать 100% из кэша процессора.

Обычно при росте количества потоков нужно думать о том что происходит на уровне ос. Переключение контекстов не бесплатное. Кроме того шина памяти не безграничная ну и надо между ядрами синхронизировать данные. Если количество потоков равно числу ядер то все более менее работает. Если больше то могут быть очень весёлые нюансы. Прирост далеко не линейный более того потом он может. Я бы рекомендовал глянуть perf top он покажет много интересного. Удачи в разработке

Понял спасибо. Значит я бы сказал что все шли в правильном направлении. Тюнинг мог бы выжать много интересного из решения. Еще раз спасибо за уточнение

А какая у вас версия java? Кроме того какой GC использовался? Ну и что с опциями JIT и как боросись с interpreter выполнением? Я просто несколько лет назад решал подобную задачу, но там удалось сделать все и без прогрева. Все решилось настройками код кеш + кое что с компиляцией кода JVM

Обновил статью с информацией о докере и клиентах

Пока не хочу публиковать серверный код. Клиентским кодом поделиться могу + могу собрать демо докер если заинтересовало

Приветствую.

При нагрузке 85К+ HurriCache потребляет около 20-30% CPU. Я не буду углубляться в архитектуру HurriCache. То что вы описали при помощи гугл ИИ не соответствует действительности и очень далеко от нее.

Сравнение абсолютно валидное, но здесь важно разграничить две разные проблемы: производительность самого in-memory движка и масштабирование сетевого транспорта на клиенте и сервере.

Зачем именно gRPC / HTTP/2?

Переход на gRPC был продиктован не попыткой обогнать RESP на одном сокете, а необходимостью иметь мультиплексирование. В микросервисных архитектурах при росте числа клиентов (когда тысячам подов/потоков нужно параллельно ходить в кэш) традиционная модель RESP заставляет создавать либо огромные пулы соединений, либо упираться в блокировки. Оверхед от поддержания и переключения десятков тысяч TCP-соединений в Redis/Dragonfly на практике может оказывается трагичным для общей latency системы. HTTP/2 закрывает эту проблему: мы гоняем тысячи параллельных RPC-стримов через один-два TCP-сокета без необходимости держать пулы.

Бенчмарки в реальных условиях (Non-pipelined workload)

Заявления в миллионы ops/sec у Dragonfly или KeyDB почти всегда строятся на идеальном pipelining (когда сотни команд пакуются в один TCP-пакет) и огромном количестве сокетов. Однако в реальных сервисах приложения часто делают одиночные неблокирующие запросы (GET/SET) из разных потоков.

Я ради интереса замерил Dragonfly в честном режиме без пайплайнинга на типичных операциях (32 потока, 100K пар ключей размером около 100 байт - стандартный тест производительности HurriCache). Результат — ~13K RPS. Он действительно ощутимо быстрее классического Redis, но на таком типе нагрузки оказался медленнее HurriCache.

Архитектура исполнения

Что касается внутрисерверного масштабирования (shared-nothing, lock-free/per-key locks), HurriCache использует подходы, во многом схожие с теми же KeyDB/Dragonfly (шардирование, локальность данных по ядрам) но как всегда есть свои нюансы.

gRPC действительно добавляет свою цену на Protobuf и HTTP/2 framing — но в данном случае она оказалась полностью оправданной.

Информация

В рейтинге
567-й
Откуда
Нижний Новгород, Нижегородская обл., Россия
Дата рождения
Зарегистрирован
Активность

Специализация

Архитектор программного обеспечения
Ведущий
Linux
C++
Java