Коннектор (драйвер) - это библиотека для конкретного языка, которая позволяет приложению общаться с Tarantool по сети, не зная деталей бинарного протокола. Снаружи это выглядит как обычный клиент БД: открыл соединение, вызвал insert/select/call, получил данные в виде нативных объектов языка. Внутри коннектор делает всю грязную работу - сериализует запрос в MsgPack, кладёт его в TCP-сокет, сопоставляет ответ с запросом и десериализует результат обратно.
Все коннекторы Tarantool делятся на две группы: те, что поддерживает команда Tarantool, и общественные. Официально командой ведутся: Go (go-tarantool), Python (tarantool-python), Java (Tarantool Java SDK), а также C (high-level C API) и C++. Остальные - community, и поддержка новых фич там может запаздывать.
Как это устроено внутри: протокол iprotoВажно понимать: коннектор - это НЕ часть ядра Tarantool. Он ставится отдельно (pip, go get, Maven) и развивается своим репозиторием со своим версионированием. Сервер не знает, какой клиент к нему подключился - он видит только байты протокола iproto.
Все коннекторы говорят с сервером на одном языке - бинарном протоколе iproto поверх TCP (порт по умолчанию 3301). Понимание этого протокола - ключ к пониманию любого коннектора.
Структура запроса. Каждый запрос начинается с заголовка переменной длины. В заголовке лежат: тип запроса (код операции - insert=02, select, call, eval и т.д.), идентификатор запроса (sync id), идентификатор инстанса, LSN. Перед заголовком всегда идёт его длина - это упрощает чтение для клиента и для прокси. Тело запроса (space id, ключ, кортеж) кодируется в MsgPack - компактный бинарный аналог JSON.
Асинхронность и мультиплексирование. Главная архитектурная черта iproto: сервер отвечает на запрос, как только ответ готов, а в заголовке ответа повторяет тот же sync id, что был в запросе. Поэтому ответы могут приходить В ЛЮБОМ ПОРЯДКЕ, а клиент сопоставляет их по id. Это значит, что по ОДНОМУ соединению можно одновременно держать в полёте десятки запросов, не дожидаясь предыдущего. Именно на этом строится производительность всех хороших коннекторов: один сокет - много параллельных операций.
Что делает коннектор поверх протокола. Помимо сериализации, зрелый драйвер берёт на себя:
- подгрузку схемы (fetch schema) - чтобы можно было обращаться к спейсам и индексам по именам, а не по числовым id;
- переподключение (reconnect) при обрыве и проверку живости соединения;
- пул соединений и обнаружение мастера в кластере;
- поддержку MsgPack-расширений: decimal, uuid, datetime, interval, error;
- вотчеры (watchers) и box.session.push() для серверных событий;
- IPROTO_ID - обмен списком поддерживаемых фич при коннекте.

Коннекторы говорят с Tarantool по iproto через MsgPack
Python: tarantool-python
Официальный синхронный коннектор. Ставится через pip:
Код: Выделить всё
pip install tarantoolКод: Выделить всё
import tarantool
conn = tarantool.Connection('127.0.0.1', 3301,
user='sampleuser', password='secret')
conn.insert('bands', (1, 'Roxette', 1986))
conn.select('bands', 1) # по первичному ключу
conn.select('bands', 'The Beatles', index='name') # по вторичному
conn.update('bands', 2, [('=', 2, 'Pink Floyd')])
conn.replace('bands', (1, 'Queen', 1970))
conn.delete('bands', 5)
conn.call('get_bands_older_than', (2000,)) # хранимая процедура
conn.close()Go: go-tarantool v2
Официальный коннектор, для продакшена берём именно ветку v2 (стабильный API). MsgPack под капотом - vmihailenco/msgpack.
Код: Выделить всё
go get github.com/tarantool/go-tarantool/v2Код: Выделить всё
dialer := tarantool.NetDialer{
Address: "127.0.0.1:3301",
User: "sampleuser", Password: "secret",
}
ctx := context.Background()
conn, _ := tarantool.Connect(ctx, dialer, tarantool.Opts{})
// асинхронно: получаем Future, ждём позже
fut := conn.Do(tarantool.NewInsertRequest("bands").
Tuple([]interface{}{1, "Roxette", 1986}))
resp, _ := fut.Get()
// синхронно
data, _ := conn.Do(tarantool.NewSelectRequest("bands").
Index("primary").Iterator(tarantool.IterEq).
Key([]interface{}{uint(1)})).Get()
conn.CloseGraceful()Java: Tarantool Java SDK
Сейчас основной и активно развиваемый Java-коннектор - Tarantool Java SDK. Он поддерживает актуальные версии и фичи Tarantool, а интеграции со Spring Data и Testcontainers идут модулями того же проекта.
Частые заблуждения и граблиГрабли версий Java: старый tarantool-java (с JDBC) НЕ поддерживается и не умеет фичи 2.x и кластеры. cartridge-java тоже помечен как deprecated/планируется к выводу. Для новых проектов - только Tarantool Java SDK.
- Связь версий клиента и сервера. Коннектор версионируется отдельно от сервера. v1 go-tarantool ещё встречается в старом коде, но для продакшена нужен v2. Старый tarantool-java живёт своей жизнью и устарел.
- guest без пароля. Примеры часто коннектятся под guest без прав. В реальности нужен пользователь с grant на read/write/execute по конкретным спейсам и функциям, иначе - access denied.
- select по вторичному индексу. По умолчанию select идёт по первичному ключу. Чтобы искать по другому полю, нужно явно указать индекс (index= в Python, .Index() в Go) и заранее иметь этот индекс в схеме.
- Шифрование - только Enterprise. Шифрование трафика iproto доступно лишь в Tarantool Enterprise Edition. В Community-версии трафик идёт открытым - выносите его в защищённую сеть или туннель.
- eval против call. При вызове функции через call коннектор делает доп. преобразования возвращаемых значений (скаляры заворачиваются в кортежи). При прямом eval-слое преобразований нет - результат приходит как голый MsgPack. Это объясняет, почему одна и та же Lua-функция даёт разную форму ответа.
- Кортеж - всегда массив. Tarantool всегда возвращает результат как массив кортежей, даже если совпал один. Не забывайте брать нулевой элемент.
- CRUD для кластера. Для одного инстанса хватает обычных insert/select. Для шардированного кластера используйте модуль CRUD (tarantool.crud в Python, пакет crud в Go) - он сам маршрутизирует запрос по бакетам.
Подготовьте сервер (один инстанс) и через любой коннектор повторите полный цикл. Сначала на сервере:
Код: Выделить всё
box.cfg{listen = 3301}
box.schema.space.create('bands')
box.space.bands:format({{name='id', type='unsigned'},
{name='name', type='string'},
{name='year', type='unsigned'}})
box.space.bands:create_index('primary', {parts = {'id'}})
box.space.bands:create_index('name', {parts = {'name'}, unique = true})
box.schema.user.create('sampleuser', {password = 'secret'})
box.schema.user.grant('sampleuser', 'read,write', 'space', 'bands')Контрольные вопросы
- За счёт какого поля заголовка iproto клиент может держать много запросов в полёте по одному соединению и сопоставлять ответы, пришедшие не по порядку?
- Почему доступ к спейсам и индексам по именам возможен только после fetch schema, и что произойдёт при изменении схемы на сервере?
- Чем отличается форма ответа при вызове функции через call и через eval, и почему?
- Какой Go-коннектор и какая его ветка рекомендованы для продакшена, и зачем перед закрытием соединения вызывать CloseGraceful()?