Пулы соединений, балансировка, реконнект

Рейтинг: 59.6% · 10 голосов
Исчерпывающий курс по Tarantool 3.x: модель данных, движки memtx и vinyl, Lua и файберы, транзакции и MVCC, SQL, конфигурация (box.cfg и декларативная 3.x), репликация и Raft, шардирование vshard, эксплуатация, безопасность. 47 уроков со схемами.
Ответить
Аватара пользователя
denis_tnt
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Пулы соединений, балансировка, реконнект

Сообщение denis_tnt »

Оглавление курса (47)
  1. Что такое Tarantool: in-memory СУБД и сервер приложений
  2. Архитектура изнутри: процесс, потоки, event-loop
  3. Установка и первый запуск: tt CLI, пакеты, Docker
  4. Интерактив: консоль, admin-консоль, первые команды
  5. Спейсы и кортежи: форматы, типы данных
  6. Типы индексов и их применимость
  7. Движки хранения: memtx vs vinyl
  8. DDL: схема, создание спейсов и индексов, миграции
  9. DML и выборки: insert/update/upsert, итераторы
  10. Персистентность: WAL, снапшоты, recovery
  11. Внутренности memtx: аллокаторы slab/arena, память
  12. Внутренности vinyl: LSM, компакция, тюнинг
  13. Lua и LuaJIT в Tarantool: box, модули, rocks
  14. Файберы: кооперативная многозадачность, каналы
  15. Транзакции: ACID, изоляция, MVCC
  16. Хранимые процедуры, модули, организация приложения
  17. net.box: удалённые вызовы, async
  18. Пулы соединений, балансировка, реконнект (вы здесь)
  19. Ошибки и диагностика: box.error, pcall
  20. Типы и сериализация: MsgPack, decimal, datetime, uuid
  21. SQL в Tarantool: возможности и связь с box
  22. SQL: таблицы, JOIN, подзапросы, представления
  23. SQL: подготовленные выражения, транзакции, Lua-интероп
  24. Классическая конфигурация box.cfg (legacy 1.x/2.x)
  25. Декларативная конфигурация 3.x: config.yaml, иерархия
  26. Роли и приложения в 3.x
  27. Централизованная конфигурация: etcd / config storage
  28. tt CLI глубоко: разработка, сборка, запуск
  29. Cartridge (официальный legacy) и миграция на 3.x
  30. Репликация: replicaset, топологии
  31. Механика репликации: WAL-стриминг, vclock
  32. Синхронная репликация и выборы лидера (Raft)
  33. Жизненный цикл узла: bootstrap, join, rejoin
  34. vshard: router/storage, виртуальные бакеты
  35. Решардинг и rebalancing бакетов
  36. Запросы поверх шардов: map-reduce, crud
  37. Мониторинг: метрики, Prometheus, Grafana
  38. Логирование и аудит
  39. Бэкапы и восстановление
  40. Безопасность: аутентификация, RBAC, TLS
  41. Производительность: профилирование, тюнинг
  42. Обновления: схема, rolling upgrade
  43. Деплой в продакшен: Docker, топология (официальные паттерны)
  44. Администрирование через официальный TCM (Tarantool Cluster Manager)
  45. Коннекторы: Python, Go, Java
  46. Ключевые модули (rocks): crud, metrics, queue, expirationd
  47. Capstone: шардированный отказоустойчивый кластер
Обзор

В реальном кластере приложение редко работает с одним инстансом. Узлов много (реплики, шарды, разные дата-центры), они то падают, то возвращаются, а запросы надо раскидывать так, чтобы чтение шло на реплики, запись - на мастер, и при этом обрыв сети не валил бизнес-логику. За это в Tarantool отвечают три тесно связанных механизма: net.box (одно сетевое соединение с авто-реконнектом), пул соединений (набор живых net.box-соединений ко всем инстансам конфигурации) и балансировка (правило, на какой из подходящих инстансов отправить конкретный запрос).

Важно сразу разделить уровни. net.box - это транспорт к ОДНОМУ инстансу. Пул - это уже знание о ВСЕХ инстансах кластера и умение выбрать нужный. В Tarantool 3.x официальный декларативный пул - это модуль experimental.connpool: он читает топологию прямо из конфигурации кластера (имена инстансов, replicaset-ы, группы, роли, метки) и поэтому не требует руками прописывать список адресов.
Ключевая идея урока: net.box даёт надёжный канал к узлу, а connpool поверх него превращает "набор узлов из конфига" в адресуемый по критериям пул, где выбор инстанса - это фильтрация по roles/labels/mode, а не хардкод адреса.
Механика: как устроено внутри

net.box: соединение как конечный автомат

net_box.connect() не открывает сокет немедленно. Он порождает worker-фибер, который запускает машину состояний: initial -> auth -> fetch_schema -> active. Само физическое соединение устанавливается лениво, при первом запросе. Все методам net.box - fiber-safe: десятки фиберов могут слать запросы через ОДНО соединение, и они пайплайнятся в один TCP-сокет, а каждый фибер получает именно свой ответ по sync-идентификатору запроса в протоколе IPROTO. Это снижает число сокетов и системных вызовов - поэтому "одно соединение на много фиберов" в Tarantool обычно лучше, чем пул из десятков сокетов к одному узлу.

Реконнект

Если сервер закрыл соединение, автомат уходит в состояние error. Если задан reconnect_after > 0, вместо error он попадает в error_reconnect и фоново пытается переподключиться через указанный интервал, бесконечно. Это делает кратковременные сетевые сбои прозрачными для приложения. Watcher-ы (conn:watch) переживают реконнект - после восстановления соединения подписки авто-восстанавливаются. Реконнект прекращается только при явном conn:close() или когда сборщик мусора удалил объект соединения.

Пул: connpool поверх net.box

connpool не хранит хитрых собственных сокетов - под капотом он создаёт обычные net.box-соединения к инстансам, но решает за вас, к КОМУ подключаться. connpool.connect(name, opts) даёт активное net.box-соединение к инстансу по имени. connpool.filter(opts) возвращает имена инстансов, подходящих под условия. connpool.call(func, args, opts) - выбирает кандидата по тем же условиям и выполняет на нём функцию.

Изображение
Пул connpool выбирает инстанс и шлёт запрос через net.box

Балансировка: как выбирается кандидат

Выбор инстанса в connpool.call - это последовательная фильтрация множества инстансов из конфига по опциям:
  • instances / replicasets / groups - сузить кандидатов по топологии;
  • roles - инстанс должен иметь указанную роль (например roles.crud-storage);
  • labels - совпадение пользовательских меток (например dc = east);
  • mode - фильтр по read-only статусу: ro, rw, prefer_ro, prefer_rw или nil (не проверять);
  • prefer_local - по умолчанию true, пробует локальный инстанс; при false идёт на случайного кандидата.
Именно так реализуется "чтение на реплику, запись на мастер": mode = "rw" отправит вызов только на read-write узел (мастер), mode = "prefer_ro" - сначала на реплики, а если их нет - на мастер. Заметьте: сам connpool делает простой выбор кандидата (с предпочтением локального), а сложную нагрузочную балансировку чтений round-robin по репликам с учётом весов даёт уже vshard.router (callro/callbro/callbre, опция prefer_replica/balance) - это отдельный слой поверх той же net.box-машинерии.

Команды и короткие примеры

Одно соединение с авто-реконнектом (классический net.box, работает и в 1.x/2.x/3.x):

Код: Выделить всё

local net_box = require('net.box')

-- ленивое соединение, реконнект каждые 5 с, ждать не дольше 1 с
local conn = net_box.connect('127.0.0.1:3301', {
    user = 'sampleuser', password = 'secret',
    reconnect_after = 5,        -- фоновый бесконечный реконнект
    connect_timeout = 1,        -- предел ожидания установки
    wait_connected  = false,    -- вернуть conn сразу, не блокируясь
    fetch_schema    = true,     -- нужно для conn.space.<name>
})

conn:ping({timeout = 0.5})              -- true/false, не кидает
print(conn:is_connected())              -- состояние сейчас
conn:wait_connected(2)                  -- подождать active до 2 с
conn.space.bands:select({1})            -- запрос идёт через сокет
conn:call('get_bands_older_than', {2000})
conn:close()                            -- останавливает реконнект
Декларативный пул 3.x (experimental.connpool) поверх кластерного конфига:

Код: Выделить всё

local connpool = require('experimental.connpool')

-- 1) соединение к конкретному инстансу по ИМЕНИ из конфига
local conn = connpool.connect('storage-b-002', { fetch_schema = true })

-- 2) кто подходит под условия (имена инстансов)
local names = connpool.filter({
    roles  = { 'roles.crud-storage' },
    labels = { dc = 'east' },
})

-- 3) выполнить функцию на подходящем read-only инстансе
local cnt = connpool.call('vshard.storage.buckets_count', nil, {
    roles  = { 'roles.crud-storage' },
    labels = { dc = 'east' },
    mode   = 'ro',           -- только реплики
    timeout = 1,
})
Таблица режимов выбора (mode) в моноширинном виде:

Код: Выделить всё

mode         кого рассматриваем
-----------  -----------------------------------------
nil          любой (ro-статус не проверяется)
ro           только read-only (реплики)
rw           только read-write (мастер)
prefer_ro    сначала реплики, иначе мастер
prefer_rw    сначала мастер, иначе реплики
Частые заблуждения и грабли
  • "net.box - это пул". Нет. Один net.box - это одно соединение к одному узлу. Пул (connpool) - слой выше, который знает топологию и выбирает узел.
  • "Открою по соединению на фибер для скорости". Обычно вредно: net.box fiber-safe и пайплайнит запросы в один сокет. Лишние соединения = лишние сокеты, дескрипторы и риск "too many open files". Несколько соединений к одному узлу оправданы лишь для приоритизации или разных auth-ID.
  • "reconnect_after сам всё ретраит". Он переустанавливает СОЕДИНЕНИЕ, но запрос, попавший в момент обрыва, всё равно вернёт ошибку - ретрай конкретного запроса делает приложение. Плюс при reconnect_after > 0 опция wait_connected игнорирует временные сбои и ждёт до явного active или close.
  • "connpool можно брать в продакшен как есть". Модуль experimental.connpool помечен как experimental (с 3.1.0) и API может меняться - закладывайте это в риски.
  • "connpool сам балансирует чтения round-robin". Нет, он лишь выбирает подходящего кандидата (с prefer_local). Полноценная нагрузочная балансировка чтений по репликам - это vshard.router, отдельный слой.
  • "fetch_schema не важен". Без него (false) недоступны conn.space.<name> и не работают триггеры on_schema_reload. Зато для чистых call/eval его выгодно выключить - меньше трафика.
  • "Долгий connect_timeout безопасен". connpool опрашивает инстансы по очереди, и его время работы зависит от числа узлов и времени коннекта к каждому - большой таймаут на недоступном узле затормозит весь вызов.
Мини-лаба

Запустите локальный инстанс на 127.0.0.1:3301. В консоли tt создайте соединение с reconnect_after = 3 и wait_connected = false. Проверьте conn:is_connected() сразу после connect (увидите, что ещё не active), затем conn:wait_connected(2). Сделайте conn:ping(). Теперь остановите инстанс (tt stop), повторите ping - получите false, но соединение не закроется. Запустите инстанс снова, через несколько секунд снова ping и убедитесь, что connpool/net.box сам переподключился без повторного connect. Зафиксируйте, сколько секунд занял реконнект и совпало ли это с reconnect_after.

Контрольные вопросы
  • Чем уровень net.box отличается от уровня connpool, и почему "одно соединение на много фиберов" обычно эффективнее пула сокетов к одному узлу?
  • Что произойдёт с запросом, который выполнялся в момент обрыва, если задан reconnect_after = 5? Кто отвечает за повтор именно этого запроса?
  • Как с помощью connpool.call отправить запись только на мастер, а чтение - предпочтительно на реплику? Какие значения mode для этого нужны?
  • Почему большое значение connect_timeout вместе с недоступным узлом может замедлить весь вызов connpool, и как это связано с тем, как пул перебирает кандидатов?
👍2 ❤️3 🔥 😄 🤔1
Ответить
← Предыдущая глава
net.box: удалённые вызовы, async
Следующая глава →
Ошибки и диагностика: box.error, pcall

Все главы курса «Tarantool: in-memory СУБД и сервер приложений с нуля до продакшена»

Поделиться темой: ✈ Telegram VK

Вернуться в «Tarantool: СУБД и сервер приложений»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость