# Общее apt install gnupg2 curl lsb-release # Экспортируем ключи curl https://download.tarantool.org/tarantool/release/2.8/gpgkey | sudo apt-key add - # Добавляем репозиторий echo "deb https://download.tarantool.org/tarantool/release/2.8/debian/ `lsb-release -c -s` main" | sudo tee /etc/apt/sources.list.d/tarantool_2_8.list echo "deb-src https://download.tarantool.org/tarantool/release/2.8/debian/ `lsb-release -c -s` main" | sudo tee -a /etc/apt/sources.list.d/tarantool_2_8.list # Ставим apt update && apt install tarantool
Все строится вокруг инстанса (приложения). Пользователи/роли/объекты заводятся внутри него и относятся к нему. В инстансе задается порт прослушивания, системные конфиги и т.д.
Для хранения конфигурации есть две папки «instances.available» и «instances.enabled», из второй приложения автозапускаются при старте службы. (по логике файлы создаются в первой, и при активации ссылка создается в enabled, но так чето не работает, даже если создать ссылку вручную, пока что работает если файл сразу поместить в enabled.)
Для управления служит утилита «tarantoolctl», либо «systemctl tarantool@<name_app>».
Параметры приложению можно заливать при создании, в основном файле «.lua», в интерактивном режиме, либо скриптами в процессе работы, утилитой «tarantoolctl eval app_name».
Примененные впоследствии скрипты не сохраняют своего действия при перезагрузке приложения + перезагрузка основной службы не влияет на работу приложений (инстансов).
Система может выполняться:
За основу можно взять файл «example.lua», в папке «available»
box.cfg { -- Здесь параметры остались по умолчанию } local function bootstrap() -- Создание пользователя box.schema.user.create('good', { password = 'secret' }) box.schema.user.grant('good', 'read,write,execute', 'universe') ----------------------------------- -- Пространство стикеров local stickers = box.schema.create_space('stickers') -- Индекс для файла stickers:create_index('primary', {type = 'TREE', parts = {1, 'unsigned'}}) -- Индекс для рейтинга stickers:create_index('secondary', {type = 'TREE', unique = false, parts = {2, 'integer'}}) -- Индекс для названий стикеров stickers:create_index('ternary', {type ='HASH', parts = {3, 'string', 4, 'string'}}) ----------------------------------- -- Пространство стикер-пака local packs = box.schema.create_space('packs') -- Индекс для стикер-пака packs:create_index('primary', {type = 'HASH', parts = {1, 'string'}}) -- Индекс для рейтинга пака packs:create_index('secondary', {type = 'TREE', unique = false, parts = {2, 'integer'}}) end -- При первом запуске создаем пространство и назначаем привилегии box.once('example-2.0', bootstrap)
Записи хранятся в пространствах (space), аналог таблицы в реляционной базе.
Внутри пространства находятся кортежи (tuples), похоже на строку в таблице и на JSON-массив данных.
Необходимо создавать индексы (index), бывают как минимум два типа:
Есть два движка хранения данных: «memtx» и «vinyl», первый хранит данные в ОЗУ, асинхронно записывая на диск для сохранности, второй классический вариант хранения на диске, оптимизирован для большого кол-ва операций.
После запуска инсталляции, подключится в режиме консоли можно:
# tarantoolctl enter <name>
Перечень запущенных инстансов можно просмотреть в списке процессов ОС:
# ps -ef | grep tarantoolctl
Запуск в докере «картриджа»
# docker run -p 3301:3301 -p 8081:8081 tarantool/getting-started
Подключение к консоли по сети (из другой консоли)
# Пока под вопросом. вроде работает но какая то специфика тут есть tarantool> net_box = require('net.box') tarantool> conn = net_box.connect(3301) # Работает но непонятно с кластером в связке с Докером $ tarantoolctl connect user:passwd@host:port # Внутри консоли так работает: tarantool> console = require('console') tarantool> console.connect('user:passwd@host:port')
#
Максимальное количество пользователей - 32
Пользователи содержатся в пространстве _user
> box.space._user:select() > box.space._user:select{id}
Работа происходит через схему schema.user
> box.schema.user.create('user-name', {password= '..'}) > box.schema.user.passwd('user-name', 'new-passwd') > box.schema.user.drop('user-name') > box.schema.user.exists('user-name') > box.schema.user.info('user-name') # Возвращает хэш пароля > box.schema.user.password('user-name')
Если у пользователя нет пароля, то подключится с его помощью не получится. Он рассматривается только как внутренний. Такие могут быть полезны с процедурами с SETUID, в которых привилегии предоставляются пользователям с внешним подключением.
В системе одна основная база данных- «box.schema» (universe), содержит все объекты. Владелец- «admin»
Владельцы автоматом получают привилегии создаваемых объектов, можно делится с пользователями или ролями командой- «box.schema.user.grant()» (revoke() для удаления)
Для создания объектов, нужна привилегия create и как минимум read, write в системном пространстве.
Пару примеров:
> box.schema.user.grant('User','read,write,execute,create,drop','universe') > box.schema.user.grant('User','write', 'space', '_schema') > box.schema.user.grant('User','create,read','space','T') > box.schema.user.grant('User','drop','sequence') > box.schema.user.grant('User','execute','function','F') > box.schema.user.grant('User','create','role')
Функция будет выполняться с привилегиями внутреннего пользователя, вызываясь при этом другим пользователем.
box.schema.space.create('u') box.schema.space.create('i') box.space.u:create_index('pk') box.space.i:create_index('pk') box.schema.user.create('inner-user') box.schema.user.grant('inner-user', 'read,write', 'space', 'u') box.schema.user.grant('inner-user', 'read,write', 'space', 'i') box.schema.user.grant('inner-user', 'create', 'universe') box.schema.user.grant('inner-user', 'read,write', 'space', '_func') function read_and_modify(key) local u= box.space.u local i= box.space.i local fiber= require('fiber') local t= u:get{key} if t ~= nil then u:put{key, box.session.uid()} i:put{key, fiber.time()} end end box.session.su('inner-user') box.schema.func.create('read_and_modify', {setuid= true}) box.session.su('admin') box.schema.user.create('public-user', {password= 'secret'}) box.schema.user.grant('public-user', 'execute', 'function', 'read_and_modify')
Роли являются контейнерами привилегий, которые могут применяться к пользователям. Информация о ролях хранится в пространстве «_user»- третье поле является ролью.
«usage» и «session» - не могут быть назначены ролями.
Роли могут быть вложенными, т.е. `роль2` может содержать в себе `роль1`, тогда к пользователю будет применена `роль2` вместе с `роль1` неявно.
Есть два способа управлять ролями:
> box.schema.user.grant-or-revoke(user-name-or-role-name,'execute', 'role',role-name...) > box.schema.user.grant-or-revoke(user-name-or-role-name,role-name...)
Пару примеров:
> box.schema.role.create('Role1') > box.schema.role.grant('Role1', 'Role2') > box.schema.user.grant('User1', 'Role1') -- Даже после связки ролей, изменения применятся > box.schema.role.grant('Role2', 'read,write', 'space', 'T')
В консольном режиме «fiber= require('fiber')», и далее через эту переменную можно получить информацию о работающих потоках, есть целый ряд методов.
Интерпретатор скриптов: «/usr/bin/evn tarantool», с помощью его можно иcполнить любой скрипт на lua.
Можно просто дать скрипт аргументом процессу он его выполнить и завершится: «tarantool my-script.lua»
Вызов функции внутри pcall()- обеспечивает безопасное поведение в случае ошибки.
Оф документация
Ставим Tarantool, Cartridge-Cli, после запуска UI доступен в веб странице по порту 8081.
В кластере есть две служебные роли:
Масштабирование кластера собсна и происходит добавлением доп хранилищ (шард), в настройках параметр «Replica set weight» регулирует приоритет использования, по всей видимости, можно вкл/выкл и данные «перетекают» из одного шарда в другой.
Остались вопросы:
Серверное приложение, создание одного спейса, пара функций, генерация рандомной символьной строки, вставка таких строк в цикле, + показан пример замера времени работы скрипта
Файл конфига сервер-аппа:
box.cfg { listen = '*:3311'; --slab_alloc_arena= 0.2; io_collect_interval = nil; readahead = 16320; memtx_memory = 128 * 1024 * 1024; memtx_min_tuple_size = 16; memtx_max_tuple_size = 128 * 1024 * 1024; vinyl_memory = 128 * 1024 * 1024; vinyl_cache = 128 * 1024 * 1024; vinyl_max_tuple_size = 128 * 1024 * 1024; vinyl_write_threads = 2; wal_mode = "none"; wal_max_size = 256 * 1024 * 1024; checkpoint_interval = 60 * 60; checkpoint_count = 6; force_recovery = true; log_level = 5; log_nonblock = false; too_long_threshold = 0.5; } local function startbuild() --local space = box.schema.create_space('example') --space:create_index('primary') -- Создание пользователя box.schema.user.create('user2', {password = 'user2'}) box.schema.user.grant('user2', 'read,write,execute,create,session', 'universe') --------------------------- ----- Пространство стикеров local tester = box.schema.create_space('tester') -- Индекс для файла tester:create_index('primary', {type= 'TREE', parts= {1, 'unsigned'}}) end function string_function() local random_number local random_string random_string = "" for x = 1,10,1 do random_number = math.random(65, 90) random_string = random_string .. string.char(random_number) end return random_string end function main_function() local string_value, t for i = 1,1000000,1 do string_value = string_function() t = box.tuple.new({i,string_value}) box.space.tester:replace(t) end end -- Создание пространства и привилегий box.once('wtf', startbuild)
Использование:
tarantool> start_time = os.clock() tarantool> main_function() tarantool> end_time = os.clock() tarantool> 'insert done in ' .. end_time - start_time .. ' seconds' tarantool> box.space.tester:select{1}
Подсчет происходит в этой функции
«Pairs()» типа массив всех строк, по которым и делаем цикл
json= require('json') function sum_json_field(field_name) local v, t, sum, field_value, is_valid_json, lua_table sum= 0 for v, t in box.space.tester:pairs() do is_valid_json, lua_table= pcall(json.decode, t[2]) if is_valid_json then field_value= lua_table[field_name] if type(field_value)== "number" then sum= sum + field_value end end end return sum end
В двух записях намерено допущены ошибки, эти строки пропускаются
-- Создание/Заполнение пространства box.space.tester:drop() box.schema.space.create('tester') box.space.tester:create_index('primary', {parts = {1, 'unsigned'}}) box.space.tester:insert{445, '{"Item": "widget", "Quantity": 7}'} box.space.tester:insert{446, '{"Item": "golf club", "Quantity": "sunshine"}'} box.space.tester:insert{447, '{"Item": "waffle iron", "Quantit": 3}'} -- Вызов ф-ции sum_json_field("Quantity")
Проверьте наличие пакета «tarantool-dev»
Создайте и скомпилируйте следующий модуль (easy.c):
#include "module.h" int easy(box_function_ctx_t *ctx, const char *args, const char *args_end) { printf("hello world\n"); return 0; } int easy2(box_function_ctx_t *ctx, const char *args, const char * args_end) { printf("hello world -- easy2\n"); return 0; }
Скомпилировать можно командой:
$ gcc -shared -o easy.so -fPIC easy.c
Далее откройте клиент tarantool в интерактивном режиме:
box.cfg{listen=3306} box.schema.space.create('capi_test') box.space.capi_test:create_index('primary') net_box = require('net.box') capi_connection = net_box:new(3306) box.schema.func.create('easy', {language = 'C'}) box.schema.user.grant('guest', 'execute', 'function', 'easy') capi_connection:call('easy')
Находясь в той же директории где скомпилирован модуль С, тарантул находит указанные файлы.
Если название функции отличается он файла, то нужно указывать его:
box.schema.func.create('easy.easy2', {language = 'C'}) box.schema.user.grant('guest', 'execute', 'function', 'easy.easy2') capi_connection:call('easy.easy2')
В основном используются API функции модуля msgpuck
«hardest.c»
#include "/usr/include/tarantool/module.h" #include "/var/msgpuck/msgpuck.h" int hardest(box_function_ctx_t *ctx, const char *args, const char *args_end) { uint32_t space_id= box_space_id_by_name("capi_test", strlen("capi_test")); char tuple[1024]; char *tuple_pointer= tuple; tuple_pointer= mp_encode_array(tuple_pointer, 2); tuple_pointer= mp_encode_uint(tuple_pointer, 10000); tuple_pointer= mp_encode_str(tuple_pointer, "String 2", 8); int n= box_insert(space_id, tuple, tuple_pointer, NULL); return n; }
После компиляции модуля выполните в терминале тарантула:
box.schema.func.create('hardest', {language = "C"}) box.schema.user.grant('guest', 'execute', 'function', 'hardest') box.schema.user.grant('guest', 'read,write', 'space', 'capi_test') capi_connection:call('hardest') -- Выполнение box.space.capi_test:select()
Подключаемся к консоли, двумя командами задаем язык и разделитель команд:
tarantool> \set language sql tarantool> \set delimiter ;
Далее выполняем команды в формате SQL
tarantool> CREATE TABLE table1 (column1 INTEGER PRIMARY KEY, column2 VARCHAR(100)); tarantool> INSERT INTO table1 VALUES (1, 'A'); INSERT INTO table1 VALUES (2, 'B'); tarantool> UPDATE table1 SET column2='AB' WHERE column1='2'; tarantool> SELECT * FROM table1;
Результат аналогичен использованию команд типа «box.space…«
Здесь создается пространство «table1», и вставка пары кортежей (записей).