├── README.md ├── practice-1.md ├── practice-2.md ├── practice-3.md └── practice-4.md /README.md: -------------------------------------------------------------------------------- 1 | # Тренинг по Тарантулу 2 | 3 | - [Архитектура](./practice-1.md) 4 | - [Масштабирование](./practice-2.md) 5 | - [Обслуживание и диагностика](./practice-3.md) 6 | - [Создание приложений](./practice-4.md) 7 | -------------------------------------------------------------------------------- /practice-1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tarantool Arch 3 | tags: tarantool, trainings 4 | description: 5 | --- 6 | 7 | # Практика: Сервис сокращения ссылок 8 | 9 | ## ТЗ 10 | 11 | - Пользователь хотел бы сократить ссылку: 12 | - Чтобы вместить её в смс или в твиттер 13 | - Сохранить в QR код 14 | - Или даже расположить в печатной продукции 15 | - Необходимо хранение соответствий 16 | 17 | ## Общий план 18 | 19 | - Настроить хранилище Tarantool 20 | - Настроить репликацию 21 | - Настроить масштабирование 22 | - Запустить http-сервер 23 | 24 | ## Часть 1 — Хранилище 25 | 26 | ### План 27 | 28 | - Конфигурация базы данных 29 | - Схема хранения 30 | - Запросы 31 | 32 | ### Конфигурация базы данных 33 | 34 | - `init.lua` 35 | ```lua 36 | box.cfg{} 37 | ``` 38 | - Инициализация файлов 39 | 40 | ### Создание спейса для хранения 41 | 42 | Для решение задачи сделаем спейс со следующими полями: 43 | - оригинальная ссылка 44 | - сокращённая ссылка 45 | 46 | - создание спейса 47 | ```lua 48 | box.schema.space.create('redirector', { if_not_exists=true }) 49 | ``` 50 | 51 | Флаг if_not_exists подавляет ошибку в случае существования спейса. 52 | 53 | Укажем поля и типы полей для спейса: 54 | ```lua 55 | box.space.redirector:format({ 56 | { name='source', type='string' }, -- исходная сслылка 57 | { name='short', type='string' }, -- сокращенная ссылка 58 | }) 59 | ``` 60 | 61 | ### Создание индексов 62 | 63 | Для просто хранения данных нам нужен первичный индекс. 64 | 65 | Сделаем первичный ключ по полю с оригинальной ссылкой. 66 | 67 | ```lua 68 | box.space.redirector:create_index( 69 | 'primary', 70 | { 71 | parts = { 'source' }, 72 | if_not_exists = true, 73 | } 74 | ) 75 | ``` 76 | 77 | В случае, когда пользователь переходит по сокращенной ссылке, нам надо найти оригинальную. 78 | 79 | ```lua 80 | box.space.redirector:create_index( 81 | 'short', 82 | { 83 | parts = { 'short' }, 84 | if_not_exists = true, 85 | } 86 | ) 87 | ``` 88 | 89 | ### Создание сокращенной ссылки 90 | 91 | Для вставки новой ссылки сделаем сокращённый хеш ссылки: 92 | 93 | ```bash 94 | $ echo -n "https://go.mail.ru/search\?q\=tarantool" | shasum | head -c 10 95 | ``` 96 | 97 | Cоздадим запрос на вставку 98 | 99 | ```lua 100 | box.space.redirector:insert({"https://go.mail.ru/search?q=tarantool", "0f74d9fd75"}) 101 | ``` 102 | 103 | Если теперь сделать повторную вставку даже с другим url — получим ошибку по индексу по полю с сокращенной ссылкой. 104 | 105 | ```lua 106 | box.space.redirector:insert({"https://habr.com", "0f74d9fd75"}) 107 | ``` 108 | 109 | ``` 110 | Duplicate key exists in unique index 111 | ``` 112 | 113 | Вставим некоторое множество ссылок: 114 | 115 | ```lua 116 | box.space.redirector:insert({"http://google.com/?q=tarantool", "9da443d847"}) 117 | box.space.redirector:insert({"https://www.tarantool.io/en/doc/latest/", "e5b1f259a8"}) 118 | box.space.redirector:insert({"https://db-engines.com/en/system/Tarantool", "ad0e253828"}) 119 | box.space.redirector:insert({"https://mcs.mail.ru", "47bfb2302e"}) 120 | box.space.redirector:insert({"https://github.com/tarantool/tarantool/", "c1ceeaaf95"}) 121 | ``` 122 | 123 | ### Запрос ссылки по сокращенной части 124 | 125 | Для поиска оригинальной ссылки по сокращенной воспользуемся итератором по индексу: 126 | 127 | ```lua 128 | box.space.redirector.index.short:get("0f74d9fd75") 129 | ``` 130 | 131 | ### Для перечисления всех ссылок, что есть в нашей базе 132 | 133 | Такой запрос можно сделать несколькими способами: 134 | ```lua 135 | for _, tuple in box.space.redirector.index.short:pairs() do 136 | print(tuple) 137 | end 138 | ``` 139 | -------------------------------------------------------------------------------- /practice-2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tarantool Topology 3 | tags: tarantool, trainings 4 | description: 5 | --- 6 | 7 | # Практика: Сервис сокращения ссылок 8 | 9 | ## ТЗ 10 | 11 | - Пользователь хотел бы сократить ссылку: 12 | - Чтобы вместить её в смс или в твиттер 13 | - Сохранить в QR код 14 | - Или даже расположить в печатной продукции 15 | - Необходимо хранение соответствий 16 | 17 | ## Общий план 18 | 19 | - Настроить хранилище Tarantool 20 | - Настроить репликацию 21 | - Настроить масштабирование 22 | - Запустить http-сервер 23 | 24 | ## Часть 2 — Масштабирование 25 | 26 | ### Создание репликасета 27 | 28 | - Создадим директорию `cluster`. 29 | - `schema.lua` 30 | ```lua 31 | if box.info.ro then 32 | return 33 | end 34 | 35 | box.schema.user.grant('guest', 'super', nil, nil, {if_not_exists=true}) -- grant replication role 36 | 37 | box.schema.space.create('redirector', { if_not_exists=true }) 38 | box.space.redirector:format({ 39 | { name='source', type='string' }, -- исходная сслылка 40 | { name='short', type='string' }, -- сокращенная ссылка 41 | }) 42 | 43 | box.space.redirector:create_index( 44 | 'primary', 45 | { 46 | parts = { 'source' }, 47 | if_not_exists = true, 48 | } 49 | ) 50 | box.space.redirector:create_index( 51 | 'short', 52 | { 53 | parts = { 'short' }, 54 | if_not_exists = true, 55 | } 56 | ) 57 | ``` 58 | - `moscow-storage.lua` 59 | ```lua 60 | box.cfg { 61 | listen = '127.0.0.1:3301', 62 | replication = { 63 | '127.0.0.1:3301', 64 | '127.0.0.1:3302', 65 | }, 66 | memtx_dir = "moscow-storage", 67 | wal_dir = "moscow-storage", 68 | replication_connect_quorum = 1, 69 | replicaset_uuid = 'aaaaaaaa-0000-4000-a000-000000000000', 70 | instance_uuid = 'aaaaaaaa-0000-4000-a000-000000000011' 71 | } 72 | 73 | require('schema') 74 | 75 | require('console').start() os.exit() 76 | ``` 77 | - `moscow-replica.lua` 78 | ```lua 79 | box.cfg { 80 | read_only=true, 81 | listen = '127.0.0.1:3302', 82 | replication = { 83 | '127.0.0.1:3301', 84 | '127.0.0.1:3302', 85 | }, 86 | memtx_dir = "moscow-replica", 87 | wal_dir = "moscow-replica", 88 | replication_connect_quorum = 1, 89 | replicaset_uuid = 'aaaaaaaa-0000-4000-a000-000000000000', 90 | instance_uuid = 'aaaaaaaa-0000-4000-a000-000000000012' 91 | } 92 | require('console').start() os.exit() 93 | ``` 94 | - Запустим инстансы в разных терминалах 95 | - `$` 96 | ``` 97 | mkdir moscow-storage 98 | tarantool moscow-storage.lua 99 | ``` 100 | - `$` 101 | ``` 102 | mkdir moscow-replica 103 | tarantool moscow-replica.lua 104 | ``` 105 | 106 | - Проверим на обоих узлах, что репликация работает. 107 | - `tarantool>` 108 | ```lua 109 | box.info.vclock 110 | box.info.replication 111 | ``` 112 | 113 | - Выполним модифицирущую операцию на лидер узле и убедимся, что данные реплицируются 114 | ```lua 115 | box.space.redirector:insert({"http://google.com/?q=tarantool", "9da443d847"}) 116 | box.space.redirector:insert({"https://www.tarantool.io/en/doc/latest/", "e5b1f259a8"}) 117 | box.space.redirector:insert({"https://db-engines.com/en/system/Tarantool", "ad0e253828"}) 118 | box.space.redirector:insert({"https://mcs.mail.ru", "47bfb2302e"}) 119 | box.space.redirector:insert({"https://github.com/tarantool/tarantool/", "c1ceeaaf95"}) 120 | ``` 121 | - На другом узле проверим наличие данных 122 | ```lua 123 | for _, tuple in box.space.redirector.index.short:pairs() do 124 | print(tuple) 125 | end 126 | ``` 127 | 128 | ## Часть 3 — Шардирование 129 | 130 | - Погасим кластер 131 | - Удалим полностью данные на узлах 132 | - `$` 133 | ``` 134 | cd moscow-storage 135 | rm -rf *.snap *.xlog 136 | cd .. 137 | cd moscow-replica 138 | rm -rf *.snap *.xlog 139 | cd .. 140 | ``` 141 | - Установим модуль vshard 142 | ``` 143 | tarantoolctl rocks install vshard 144 | ``` 145 | - Добавить поле для хранения ключа шардирования 146 | - `schema.lua` 147 | ```lua 148 | if box.info.ro then 149 | return 150 | end 151 | 152 | box.schema.user.grant('guest', 'super', nil, nil, {if_not_exists=true}) -- grant replication role 153 | 154 | box.schema.space.create('redirector', { if_not_exists=true }) 155 | box.space.redirector:format({ 156 | { name='source', type='string' }, -- исходная сслылка 157 | { name='short', type='string' }, -- сокращенная ссылка 158 | { name='bucket_id', type='unsigned' }, -- сокращенная ссылка 159 | }) 160 | 161 | box.space.redirector:create_index( 162 | 'primary', 163 | { 164 | parts = { 'source' }, 165 | if_not_exists = true, 166 | } 167 | ) 168 | box.space.redirector:create_index( 169 | 'short', 170 | { 171 | parts = { 'short' }, 172 | if_not_exists = true, 173 | } 174 | ) 175 | 176 | box.space.redirector:create_index( 177 | 'bucket_id', 178 | { 179 | parts={ "bucket_id" }, 180 | unique=false, if_not_exists=true, 181 | } 182 | ) 183 | ``` 184 | 185 | - Создадим файл с описание топологии кластера 186 | - Топология `topology.lua` 187 | ```lua 188 | return { 189 | bucket_count = 16, 190 | sharding = { 191 | ['aaaaaaaa-0000-4000-a000-000000000000'] = { 192 | replicas = { 193 | ['aaaaaaaa-0000-4000-a000-000000000011'] = { 194 | name = 'moscow-storage', 195 | master=true, 196 | uri="guest@127.0.0.1:30011" 197 | }, 198 | ['aaaaaaaa-0000-4000-a000-000000000012'] = { 199 | name = 'moscow-replica', 200 | master=false, 201 | uri="guest@127.0.0.1:30012" 202 | }, 203 | } 204 | }, 205 | ['bbbbbbbb-0000-4000-a000-000000000000'] = { 206 | replicas = { 207 | ['bbbbbbbb-0000-4000-a000-000000000021'] = { 208 | name='spb-storage', 209 | master=true, 210 | uri="guest@127.0.0.1:30021" 211 | }, 212 | ['bbbbbbbb-0000-4000-a000-000000000022'] = { 213 | name='spb-replica', 214 | master=false, 215 | uri="guest@127.0.0.1:30022" 216 | }, 217 | }, 218 | }, 219 | } 220 | } 221 | 222 | ``` 223 | - `router.lua` 224 | ```lua 225 | vshard = require('vshard') 226 | topology = require('topology') 227 | vshard.router.cfg(topology) 228 | require('console').start() os.exit(0) 229 | ``` 230 | - `moscow-storage.lua` 231 | ```lua 232 | vshard = require('vshard') 233 | 234 | local topology = require('topology') 235 | 236 | vshard.storage.cfg({ 237 | bucket_count = topology.bucket_count, 238 | sharding = topology.sharding, 239 | 240 | memtx_dir = "moscow-storage", 241 | wal_dir = "moscow-storage", 242 | replication_connect_quorum = 1, 243 | }, 244 | 'aaaaaaaa-0000-4000-a000-000000000011') 245 | 246 | local schema = require('schema') 247 | 248 | require('console').start() os.exit(0) 249 | ``` 250 | 251 | - `moscow-replica.lua` 252 | ```lua 253 | vshard = require('vshard') 254 | 255 | local topology = require('topology') 256 | 257 | vshard.storage.cfg({ 258 | bucket_count = topology.bucket_count, 259 | sharding = topology.sharding, 260 | 261 | memtx_dir = "moscow-replica", 262 | wal_dir = "moscow-replica", 263 | }, 264 | 'aaaaaaaa-0000-4000-a000-000000000012') 265 | 266 | local schema = require('schema') 267 | 268 | require('console').start() os.exit(0) 269 | ``` 270 | - `spb-storage.lua` 271 | 272 | ```lua 273 | vshard = require('vshard') 274 | 275 | local topology = require('topology') 276 | 277 | vshard.storage.cfg({ 278 | bucket_count = topology.bucket_count, 279 | sharding = topology.sharding, 280 | 281 | memtx_dir = "spb-storage", 282 | wal_dir = "spb-storage", 283 | }, 284 | 'bbbbbbbb-0000-4000-a000-000000000021') 285 | 286 | local schema = require('schema') 287 | 288 | require('console').start() os.exit(0) 289 | ``` 290 | 291 | - `spb-replica.lua` 292 | ```lua 293 | vshard = require('vshard') 294 | 295 | local topology = require('topology') 296 | 297 | vshard.storage.cfg({ 298 | bucket_count = topology.bucket_count, 299 | sharding = topology.sharding, 300 | 301 | memtx_dir = "spb-replica", 302 | wal_dir = "spb-replica", 303 | }, 304 | 'bbbbbbbb-0000-4000-a000-000000000022') 305 | 306 | local schema = require('schema') 307 | 308 | require('console').start() os.exit(0) 309 | ``` 310 | 311 | - Запустим кластер: 312 | 313 | ``` 314 | mkdir moscow-storage 315 | tarantool moscow-storage.lua 316 | ``` 317 | 318 | ``` 319 | mkdir moscow-replica 320 | tarantool moscow-replica.lua 321 | ``` 322 | 323 | ``` 324 | mkdir spb-storage 325 | tarantool spb-storage.lua 326 | ``` 327 | 328 | ``` 329 | mkdir spb-replica 330 | tarantool spb-replica.lua 331 | ``` 332 | 333 | - Запустим роутер: 334 | 335 | ``` 336 | tarantool router.lua 337 | ``` 338 | 339 | - Забутстрапим шардинг 340 | - На роутере `tarantool>` 341 | ``` 342 | vshard.router.bootstrap() 343 | ``` 344 | 345 | ### Работа с шардированными данными 346 | 347 | - Воспользуемся списком урлов, который мы готовили ранее 348 | - Теперь все операции с данными должны проходить через роутер, с помощью функций vshard.router.callrw, vshard.router.callro 349 | ```lua 350 | bucket_id = 1 351 | vshard.router.callrw(bucket_id, 'box.space.redirector:insert', {{"http://google.com/?q=tarantool", "9da443d847", bucket_id}}) 352 | bucket_id = 4 353 | vshard.router.callrw(bucket_id, 'box.space.redirector:insert', {{"https://www.tarantool.io/en/doc/latest/", "e5b1f259a8", bucket_id}}) 354 | bucket_id = 8 355 | vshard.router.callrw(bucket_id, 'box.space.redirector:insert', {{"https://db-engines.com/en/system/Tarantool", "ad0e253828", bucket_id}}) 356 | bucket_id = 12 357 | vshard.router.callrw(bucket_id, 'box.space.redirector:insert', {{"https://mcs.mail.ru", "47bfb2302e", bucket_id}}) 358 | bucket_id = 16 359 | vshard.router.callrw(bucket_id, 'box.space.redirector:insert', {{"https://github.com/tarantool/tarantool/", "c1ceeaaf95", bucket_id}}) 360 | ``` 361 | - Запрос данных — по условиям задачи по короткому урлу должны получить оригинальный 362 | - Мы шардировали данные вне зависимости от короткого урла, таким образом мы не знаем на каких шардах как распределены короткие урлы 363 | - Значит нам нужно пройтись по всем шардам — это называется map/reduce 364 | 365 | ### Запрос данных `map/reduce` 366 | 367 | - `vshard.router.routeall` возвращает все репликасеты 368 | - Функции роутера работают только на роутере 369 | - `replica:call*` вызывает функцию на подходящей реплике 370 | 371 | - Поищем короткую ссылку 'e5b1f259a8' 372 | ```lua 373 | do 374 | local resultset = {} 375 | shards, err = vshard.router.routeall() 376 | if err ~= nil then 377 | print(err) 378 | return 379 | end 380 | for uid, replica in pairs(shards) do 381 | local set = replica:callro('box.space.redirector.index.short:select', {{'e5b1f259a8'}}) 382 | for _, redirect in ipairs(set) do 383 | resultset[redirect[2]] = redirect[1] 384 | end 385 | end 386 | return resultset 387 | end 388 | ``` 389 | 390 | ## Troubleshooting 391 | 392 | - `can't chdir to : No such file or directory` 393 | - Создать нужную <DIRECTORY> для хранения файлов 394 | - `ER_ALREADY_RUNNING: Failed to lock WAL directory` 395 | - Тарантул уже запущен, найти и убить 396 | - `pkill -9 tarantool` 397 | - Каждый тарантул должен иметь свою директорию для хранения данных 398 | - `ER_EXACT_MATCH: Invalid key part count in an exact match (expected 1, got 0)` 399 | - Не задан пользователь в `uri` ноды 400 | - `Incorrect password supplied for user ` 401 | - Не задан пароль в `uri` репликасета 402 | - `Procedure 'vshard.storage.buckets_discovery' is not defined` 403 | - Сделать `vshard` модуль глобальным 404 | - `Bucket 1 cannot be found. Is rebalancing in progress?` 405 | - Проверить что все шарды подняты 406 | - Если подняты cделать `vshard.router.bootstrap()` 407 | - `Please call box.cfg{} first` 408 | - Попытка использовать базу до конфигурации 409 | - Возможно применение схемы до конфигурации 410 | - `Error during discovery` 411 | - Не все узлы подняты 412 | - `Local replica aaaaaaaa-0000-4000-a000-000000000011 wasn't found in config` 413 | - Проверить что в топологии нод корректная информация и в скрипте запуска используется имеющийся uuid 414 | - `Only one master is allowed per replicaset` 415 | - Внутри одного репликасета задано два мастера, должен быть только один 416 | - `Duplicate uri sharding:pass@127.0.0.1:30011` 417 | - У разных нод назначен одинаковый `uri` 418 | - `ER_READONLY: Can't modify data because this instance is in read-only mode.` 419 | - В топологии не указан `master` для нужной ноды 420 | - `Duplicate uuid` 421 | - У двух разных нод в топологии задан одинаковый `uuid` 422 | - `can't initialize storage: Incorrect value for option 'replication': failed to connect to one or more replicas` 423 | - Проверить что все ноды внутри репликасета запущены 424 | - `Some buckets are not active, retry rebalancing later` 425 | - Проверить что все шарды подняты 426 | - Если да сделать `vshard.router.bootstrap()` 427 | - `Cluster is already bootstrapped` 428 | - Попытка вызывать `vshard.router.bootstrap()` на уже забутстрапленом кластере 429 | - `applier.cc:264 E> error applying row: {type: 'INSERT', 430 | can't read row 431 | ER_TUPLE_FOUND: Duplicate key exists in unique index 'primary' in space '_cluster' 432 | ` 433 | - Неконсистетно поднят кластер 434 | - Следить за конфликтами и перезапускать репликацию 435 | ```lua 436 | replication = box.cfg.replication 437 | box.cfg{replication={}} 438 | box.cfg{replication=replication, replication_skip_conflict=true} 439 | ``` 440 | -------------------------------------------------------------------------------- /practice-3.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tarantool Maentenance 3 | tags: tarantool, trainings 4 | description: 5 | --- 6 | 7 | # Практика: Сервис сокращения ссылок 8 | 9 | ## ТЗ 10 | 11 | - Пользователь хотел бы сократить ссылку: 12 | - Чтобы вместить её в смс или в твиттер 13 | - Сохранить в QR код 14 | - Или даже расположить в печатной продукции 15 | - Необходимо хранение соответствий 16 | 17 | ## Общий план 18 | 19 | - Настроить хранилище Tarantool 20 | - Настроить репликацию 21 | - Настроить масштабирование 22 | - Обслуживание и диагностика 23 | - Запустить http-сервер 24 | 25 | ## Часть 4 — обслуживание и диагностика 26 | 27 | ### Потоки `tarantool` 28 | 29 | - `iproto` 30 | - Прием, передача сетевых сообщений 31 | - Используется в бинарных коннекторах к `Tarantool` 32 | - `tx` (в том числе `luajit`) 33 | - Обработка данных lua скриптами 34 | - `wal` 35 | - Запись транзакций в файл журнал 36 | 37 | Запрос от клиента попадает в `iproto` поток. Затем передаётся в `tx`. В `tx` потоке потоке 38 | обработка может создать транзакцию, которая при `box.commit()` передаётся в `wal` поток. 39 | `wal` поток записывает транзакцию в файл и возвращает управление в `tx`. Затем 40 | `tx` возвращает результат в `iproto` поток, который в свою очередь передаёт его клиенту. 41 | 42 | Таким образом, строго говоря, обработка данных в Tarantool трехпоточна. 43 | 44 | - Посмотреть потоки можно, например, утилитой `htop`. 45 | ``` bash 46 | tarantool 47 | 48 | tarantool> box.cfg{} 49 | ``` 50 | 51 | ``` bash 52 | htop --tree 53 | # f2 (ctrl+f2, click "Setup") 54 | # Display options -> Show Custom Thread Names 55 | # f3 (ctrl+f3, click "Search") tarantool 56 | ``` 57 | - Примерный вывод 58 | ``` bash 59 | └─ tarantool 60 | ├─ coio 61 | ├─ coio 62 | ├─ wal 63 | └─ iproto 64 | ``` 65 | 66 | ### Настройка сетевого потока 67 | 68 | - readahead (байт) (min 128) 69 | - net_msg_max (количество) (min 2) 70 | 71 | #### readahead 72 | 73 | ``` bash 74 | mkdir mnt1 && cd mnt1 75 | ``` 76 | 77 | - Сервер 78 | ``` bash 79 | tarantool 80 | ``` 81 | - `tarantool>` 82 | ``` lua 83 | -- запускаем сервер 84 | box.cfg{listen=3301} 85 | -- для тестов даем права гостю 86 | box.schema.user.grant('guest', 'super', nil, nil, { if_not_exists = true }) 87 | 88 | -- конфигурируем размер буфера чтения 89 | box.cfg{readahead=128} 90 | -- проверка 91 | box.cfg.readahead 92 | ``` 93 | - Генератор нагрузки 94 | ``` bash 95 | tarantool 96 | ``` 97 | - `tarantool>` 98 | ``` lua 99 | netbox = require('net.box') 100 | c = netbox.connect('127.0.0.1:3301') 101 | fiber = require('fiber') 102 | 103 | -- создаем некоторое количество файберов для одновременных запросов 104 | for i=1,120 do 105 | fiber.new(c.eval, c, [[ a = ... ]], {('x'):rep(128)}) 106 | end 107 | ``` 108 | - `stopping input on connection fd 17, aka 127.0.0.1:3301, peer of 127.0.0.1:48916, readahead limit is reached` 109 | 110 | 111 | #### net_msg_max 112 | 113 | - Почистим директорию от предыдущего запуска 114 | ``` bash 115 | rm -rf *.snap *.xlog 116 | ``` 117 | 118 | - Сервер 119 | ``` lua 120 | -- запускаем сервер 121 | box.cfg{listen=3301} 122 | -- Для тестов даем права гостю 123 | box.schema.user.grant('guest', 'super', nil, nil, { if_not_exists = true }) 124 | 125 | -- конфигурируем максимальное количество одновременных сообщений 126 | box.cfg{net_msg_max=2} 127 | -- проверка 128 | box.cfg.net_msg_max 129 | ``` 130 | - Генератор нагрузки из другого инстанса 131 | ``` bash 132 | tarantool 133 | ``` 134 | - `tarantool>` 135 | ``` lua 136 | netbox = require('net.box') 137 | c = netbox.connect('127.0.0.1:3301') 138 | 139 | fiber = require('fiber') 140 | -- создаем три одновременных запроса через файберы 141 | for i=1,3 do 142 | fiber.new(c.eval, c, [[a=...]], {('x'):rep(128)}) 143 | end 144 | ``` 145 | - `stopping input on connection fd 17, aka 127.0.0.1:3301, peer of 127.0.0.1:48798, net_msg_max limit is reached` 146 | 147 | ### Настройка памяти 148 | 149 | #### memtx_memory 150 | 151 | - Почистим директорию от предыдущего запуска 152 | ``` bash 153 | rm -rf *.snap *.xlog 154 | ``` 155 | 156 | - Предельно минимальное значение для хранение данных в `memtx` 32mb 157 | ``` bash 158 | tarantool 159 | ``` 160 | 161 | - `tarantool>` 162 | 163 | ``` lua 164 | -- выделим 32 мегабайта памяти 165 | box.cfg{memtx_memory = 32*1024*1024} 166 | 167 | -- создадим тестовый спейс 168 | box.schema.space.create('test', { if_not_exists=true }) 169 | -- первичный ключ на нем 170 | box.space.test:create_index('primary', { if_not_exists=true }) 171 | 172 | -- попытаемся вставить примерно 1 мегабайт информации 173 | box.space.test:put({1, ('x'):rep(1024*1024 - 30)}) 174 | ``` 175 | - `error: Failed to allocate 1048567 bytes in slab allocator for memtx_tuple` 176 | 177 | - Интроспекция слабов 178 | ```lua 179 | local function MAP(t) 180 | return setmetatable(t, require "json".map_mt) 181 | end 182 | local t = require "fun".iter(box.slab.stats()) 183 | :map(function(s) 184 | return MAP { 185 | -- kind: Какой максимальный размер тапла можно хранить 186 | kind = s.item_size, 187 | -- cnt: Сколько слабов под них выделено 188 | cnt = s.slab_count, 189 | -- items: Сколько уже таплов поселилось в слабах 190 | items = s.item_count, 191 | -- use: % занятости слаба 192 | use = math.floor(s.mem_used / (s.slab_size * s.slab_count) * 1000) / 10, 193 | -- size: Размер слаба в байтах (отношение slab_size/kind дает примерное количество таплов, которые поместятся в слаб) 194 | size = s.slab_size, 195 | } 196 | end) 197 | :totable() 198 | table.sort(t, function(a, b) return a.kind < b.kind end) 199 | return t 200 | ``` 201 | 202 | - То же самое в виде однострочника (`Ctrl+C -> Ctrl+V`) 203 | ``` lua 204 | local function MAP(t) return setmetatable(t,require'json'.map_mt) end local t = require'fun'.iter(box.slab.stats()):map(function(s) return MAP{kind=s.item_size, cnt=s.slab_count, items=s.item_count, use=math.floor(s.mem_used/(s.slab_size * s.slab_count)*1000)/10, size=s.slab_size} end):totable() table.sort(t,function(a,b) return a.kind < b.kind end) return t 205 | ``` 206 | 207 | #### malloc 208 | 209 | - Выделение памяти для таплов без спейса происходит в rss памяти отдельно от спейса 210 | - Посмотрим на пределы 211 | ``` bash 212 | tarantool 213 | ``` 214 | 215 | - `tarantool>` 216 | 217 | ``` lua 218 | _ = box.tuple.new{('x'):rep(512*1024*1000)} 219 | ``` 220 | 221 | - `error: Failed to allocate 32719 bytes in mpstream for reserve` 222 | 223 | #### lua objects 224 | 225 | ``` bash 226 | tarantool 227 | ``` 228 | 229 | - `tarantool>` 230 | ``` lua 231 | _ = ('x'):rep(1024*1024*1000) 232 | ``` 233 | 234 | - `error: not enough memory` 235 | 236 | ### Поток записи транзакций 237 | 238 | - Почистим директорию от предыдущего запуска 239 | ``` bash 240 | rm -rf *.snap *.xlog 241 | ``` 242 | 243 | - Если коммит транзакции происходи слишком долго, необходима диагностика 244 | - Например, если какой-то файбер надолго задержал управление 245 | ``` bash 246 | tarantool 247 | ``` 248 | - `tarantool>` 249 | ``` lua 250 | box.cfg{} 251 | -- создадим тестовый спейс 252 | box.schema.space.create('test', { if_not_exists=true }) 253 | -- первичный ключ на нем 254 | box.space.test:create_index('primary', { if_not_exists=true }) 255 | 256 | function busyloop(secs) 257 | local clock = require 'clock' 258 | local d = clock.realtime()+secs 259 | while clock.realtime() < d do end 260 | end 261 | 262 | fiber = require('fiber') 263 | do 264 | fiber.new(busyloop, 2) -- шедулит файбер (не запускает) 265 | fiber.create(function() -- шедулит и запускает 266 | box.begin() -- отключает yield на операциях box'a 267 | box.space.test:put{1, 1} 268 | box.commit() -- делает fiber.yield, и ожидает успешной записи в wal 269 | -- если дошли сюда, то запись успешна 270 | end) 271 | end 272 | ``` 273 | - `too long WAL write: 1 rows at LSN 5: 2.009 sec` 274 | - Если транзакции действительно большие и занимают много времени 275 | можно отредактировать `too_long_threshold` 276 | ```bash 277 | tarantool 278 | ``` 279 | - `tarantool>` 280 | ``` lua 281 | box.cfg{too_long_threshold=10} 282 | -- создадим тестовый спейс 283 | box.schema.space.create('test', { if_not_exists=true }) 284 | -- первичный ключ на нем 285 | box.space.test:create_index('primary', { if_not_exists=true }) 286 | 287 | function busyloop(secs) 288 | local clock = require 'clock' 289 | local d = clock.realtime()+secs 290 | while clock.realtime() < d do end 291 | end 292 | 293 | fiber = require('fiber') 294 | do 295 | fiber.new(busyloop, 2) 296 | fiber.create(function() 297 | box.begin() 298 | box.space.test:put{1, 1} 299 | box.commit() 300 | end) 301 | end 302 | ``` 303 | - Сообщения не будет 304 | 305 | ### Повреждение файла `xlog` (`write ahead log`) 306 | 307 | - Почистим директорию от предыдущего запуска 308 | ``` bash 309 | rm -rf *.snap *.xlog 310 | ``` 311 | - Запустим `tarantool` 312 | ```bash 313 | tarantool 314 | ``` 315 | - `tarantool>` 316 | ``` lua 317 | box.cfg{} 318 | 319 | -- создадим тестовый спейс 320 | box.schema.space.create('test', { if_not_exists=true }) 321 | -- первичный ключ на нем 322 | box.space.test:create_index('primary', { if_not_exists=true }) 323 | 324 | -- сделаем одну транзакцию 325 | box.space.test:put({1, 'first data'}) 326 | 327 | -- Ctrl+c 328 | ``` 329 | 330 | - Посмотреть транзакции можно модулем `xlog` 331 | 332 | ```bash 333 | tarantool 334 | ``` 335 | 336 | - `tarantool>` 337 | ``` bash 338 | yaml = require('yaml') 339 | xlog = require('xlog') 340 | 341 | for _, transaction in xlog.pairs('00000000000000000000.xlog') do -- также для файлов .snap 342 | print(yaml.encode(transaction)) 343 | end 344 | ``` 345 | 346 | - Повредим файл `xlog` с недавними транзакциями 347 | 348 | ``` bash 349 | printf '\x31\xc0\xc3' | dd of=00000000000000000000.xlog bs=1 seek=340 count=3 conv=notrunc 350 | ``` 351 | 352 | - Попытаемся запустить 353 | 354 | ``` bash 355 | tarantool 356 | ``` 357 | 358 | - `tarantool>` 359 | ``` lua 360 | box.cfg{} 361 | ``` 362 | 363 | - `XlogError: tx checksum mismatch` 364 | 365 | - Можно использовать `force_recovery` 366 | 367 | ``` bash 368 | tarantool 369 | ``` 370 | 371 | - `tarantool>` 372 | ``` lua 373 | box.cfg{force_recovery=true} 374 | -- последней транзакции в спейсе не будет 375 | -- она была повреждена 376 | box.space.test:select{} 377 | ``` 378 | 379 | ### Повреждение файла `snap` (`snapshot`) 380 | 381 | - Почистим директорию от предыдущего запуска 382 | ``` bash 383 | rm -rf *.snap *.xlog 384 | ``` 385 | - Запустим `tarantool` 386 | ```bash 387 | tarantool 388 | ``` 389 | - `tarantool>` 390 | ``` lua 391 | box.cfg{} 392 | 393 | -- создадим тестовый спейс 394 | box.schema.space.create('test', { if_not_exists=true }) 395 | -- первичный ключ на нем 396 | box.space.test:create_index('primary', { if_not_exists=true }) 397 | 398 | -- сделаем одну транзакцию 399 | box.space.test:put({1, 'first data'}) 400 | 401 | -- сделаем snapshot данных 402 | box.snapshot() 403 | 404 | -- сделаем ещё одну транзакцию 405 | box.space.test:put({2, 'second data'}) 406 | 407 | -- Ctrl+c 408 | ``` 409 | 410 | - Посмотреть снапшот можно тоже модулем `xlog` 411 | 412 | ```bash 413 | tarantool 414 | ``` 415 | 416 | - `tarantool>` 417 | ``` bash 418 | yaml = require('yaml') 419 | xlog = require('xlog') 420 | 421 | for _, transaction in xlog.pairs('00000000000000000004.snap') do 422 | print(yaml.encode(transaction)) 423 | end 424 | ``` 425 | - Последняя транзакция `[1,"first data"]` 426 | 427 | - Повредим файл snap с последней версий данных 428 | 429 | ``` bash 430 | printf '\x31\xc0\xc3' | dd of=00000000000000000004.snap bs=1 seek=340 count=3 conv=notrunc 431 | ``` 432 | 433 | - Попытаемся запустить 434 | 435 | ``` bash 436 | tarantool 437 | ``` 438 | 439 | - `tarantool>` 440 | ``` lua 441 | box.cfg{} 442 | ``` 443 | 444 | - `XlogError: tx checksum mismatch` 445 | 446 | - `force_recovery` тоже можно использовать для снапшотов 447 | 448 | ``` bash 449 | tarantool 450 | ``` 451 | 452 | - `tarantool>` 453 | ``` lua 454 | box.cfg{force_recovery=true} 455 | ``` 456 | 457 | - Но давайте воспользуемся другим способом 458 | - Проверим, что у нас есть предыдущий снапшот 459 | ```bash 460 | file 00000000000000000000.snap 461 | ``` 462 | - Удалим повреждённый снапшот 463 | ```bash 464 | mv 00000000000000000004.snap 00000000000000000004.snap.bak 465 | ``` 466 | - Запустим tarantool 467 | ``` bash 468 | tarantool 469 | ``` 470 | 471 | - `tarantool>` 472 | ``` lua 473 | box.cfg{} 474 | 475 | box.space.test:select{} 476 | ``` 477 | - `[1, 'first data']` 478 | - `[2, 'second data']` 479 | - Запуск произошел с ближайшего снапшота и с применением всех последующих `xlog` файлов 480 | 481 | 482 | ### Вычисление размера спейса в оперативной памяти (Задача со зведочкой) 483 | 484 | - Просуммировать размер спейса и размер всех его индексов 485 | 486 | ``` bash 487 | tarantool 488 | ``` 489 | 490 | - `tarantool>` 491 | ``` bash 492 | _G.ls = setmetatable({}, { 493 | __serialize = function() 494 | local res = {} 495 | 496 | for _, sp_info in box.space._space:pairs(512, { iterator = "GE" }) do 497 | local sp = box.space[sp_info.name] 498 | local info = {} 499 | info.name = tostring(sp.name) 500 | info.engine = tostring(sp.engine) 501 | info.len = tostring(sp:len()) 502 | info.bsize = sp:bsize() 503 | info.owner = tostring(box.space._user:get(sp_info.owner).name) 504 | -- index numeration starts with zero 505 | if next(sp.index) then 506 | for k, v in pairs(sp.index) do 507 | if type(k) == 'number' then 508 | info.bsize = info.bsize + v:bsize() 509 | end 510 | end 511 | end 512 | info.bsize = tostring(info.bsize):sub(1, -4) -- strip ULL 513 | table.insert(res, info) 514 | end 515 | table.sort(res, function(a, b) return a.engine .. a.name < b.engine .. b.name end) 516 | 517 | --do return res end 518 | 519 | local nice = {} 520 | local nice_l = {} 521 | for _, sp in pairs(res) do 522 | for k, v in pairs(sp) do 523 | if not nice_l[k] then 524 | nice_l[k] = 0 525 | end 526 | 527 | nice_l[k] = math.max(nice_l[k], v:len()) 528 | end 529 | end 530 | 531 | local split_size = tonumber(rawget(_G, '__LS_SPLIT_SIZE')) or 2 532 | for _, sp in pairs(res) do 533 | local str = '' 534 | for _, k in pairs({ 'engine', 'owner', 'bsize', 'len' }) do 535 | str = str .. ("%% -%ds%% %ds"):format(nice_l[k], split_size):format(sp[k], " ") 536 | end 537 | str = str .. sp['name'] 538 | table.insert(nice, str) 539 | end 540 | 541 | return nice 542 | end; 543 | }) 544 | ``` 545 | - Использование 546 | - `tarantool>` 547 | ``` 548 | ls 549 | ``` 550 | 551 | ### Ребутстрап репликации 552 | 553 | - Запустим первый узел (Spb) 554 | - `$` 555 | ``` 556 | mkdir spb 557 | cd spb 558 | tarantool 559 | ``` 560 | - `tarantool>` 561 | ```lua 562 | box.cfg{listen='127.0.0.1:3301'} 563 | box.schema.user.grant('guest', 'super') 564 | box.schema.space.create('test') 565 | box.space.test:create_index('pkey') 566 | 567 | box.space.test:put{1, 'value1'} 568 | 569 | ``` 570 | - Запустим второй узел (Moscow) 571 | - `$` 572 | ``` 573 | mkdir moscow 574 | cd moscow 575 | tarantool 576 | ``` 577 | - `tarantool>` 578 | ```lua 579 | box.cfg{replication={'127.0.0.1:3301'}} 580 | ``` 581 | - Поломаем репликацию конфликтом 582 | - Сначала вставим данные на реплике 583 | ```lua 584 | box.space.test:insert{2, 'value2'} 585 | ``` 586 | - Потом на лидере 587 | ```lua 588 | box.space.test:insert{2, 'value2'} 589 | ``` 590 | - Проверим статус репликации 591 | ``` 592 | box.info.replication 593 | ``` 594 | - Ожидаем увидеть сообщение 595 | ``` 596 | Duplicate key exists in unique index "pkey" 597 | ``` 598 | - Ребутстрап: 599 | - Запомним uuid, cluster.uuid в таблице box.info 600 | ```lua 601 | box.info.uuid 602 | box.info.cluster.uuid 603 | ``` 604 | - Погасим реплику Ctrl-C 605 | - Удалим файлы *.snap, *.xlog 606 | - `$` 607 | ``` 608 | rm -rf *.snap *.xlog 609 | ``` 610 | - `$` 611 | ``` 612 | tarantool 613 | ``` 614 | - `tarantool>` 615 | ``` 616 | box.cfg{replication={'127.0.0.1:3301'}, instance_uuid='XXX', replicaset_uuid='YYY'} 617 | ``` 618 | -------------------------------------------------------------------------------- /practice-4.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tarantool App 3 | tags: tarantool, trainings 4 | description: 5 | --- 6 | 7 | # Практика: Сервис сокращения ссылок 8 | 9 | ## ТЗ 10 | 11 | - Пользователь хотел бы сократить ссылку: 12 | - Чтобы вместить её в смс или в твиттер 13 | - Сохранить в QR код 14 | - Или даже расположить в печатной продукции 15 | - Необходимо хранение соответствий 16 | 17 | ## Общий план 18 | 19 | - Настроить хранилище Tarantool 20 | - Настроить репликацию 21 | - Настроить масштабирование 22 | - Обслуживание и диагностика 23 | - Запустить http-сервер 24 | 25 | ## Часть 5 — сервер приложений 26 | 27 | - Сегодня мы напишем легковесный `http` сервер 28 | - Реализуем `http` `api` в своем приложении 29 | 30 | ## Intro 31 | 32 | ### Lua за 15 минут 33 | 34 | - https://learnxinyminutes.com/docs/ru-ru/lua-ru/ 35 | 36 | ### Код 37 | 38 | - Код на языке `Lua` выполняется в файберах (их также называют корутины, горутины, зеленые треды, легковесные сопрограммы) 39 | - Файберы работают по принципу кооперативной многозадачности 40 | - В текущий момент времени выполняется только один файбер, и этот файбер, если операция ввода-вывода или явно, передаёт управление другому файберу 41 | - `Tarantool` содержит встроенные модули: 42 | - `log` — журналирование событий приложения и базы данных 43 | - `console` — предлагает возможность выполнения и отладки кода в уже запущенном инстансе тарантула 44 | - `clock` — функции для работы с системным временем 45 | - `digest` — вычисление хеша от строки 46 | - `fio` — работа с файлами 47 | - `socket` — работа с сетью 48 | - `box` — настройка и хранение данных 49 | - Некоторыми из них вы уже пользовались 50 | - Подробнее обо всех модулях: https://www.tarantool.io/en/doc/latest/reference/ 51 | 52 | ## Сервер приложений 53 | 54 | - Сделаем легковесный `http` сервер 55 | - `http` сервер под капотом создаёт файбер, который 56 | - слушает `tcp/ip` `server:port` 57 | - для каждого нового клиента запускает `client_handler` в отдельном файбере 58 | - `client_handler` принимает первым аргументом соединение с клиентом 59 | - `client_handler` обрабывает запросы, вызывая обработчики роутов 60 | - Функции обработки `http` запросов: 61 | - `GET /` 62 | вернет html форму для ввода ссылки 63 | - `GET /?url=*` 64 | сгенерирует и вернет короткую ссылку 65 | - `GET /*` 66 | перенаправит на исходную ссылку 67 | - Доступ к данным будет выполняться всё также через `vshard.router.callrw`(`-ro`) 68 | 69 | ### Сервер обработки http запросов 70 | 71 | - Возьмём топологию попроще для удобства запуска и отладки 72 | 73 | - Установим фреймворк шардирования `vshard` и `http` сервер 74 | ```bash 75 | tarantoolctl rocks install vshard 76 | tarantoolctl rocks install http 77 | ``` 78 | - `topology.lua` 79 | ```lua [] 80 | return { 81 | BUCKET_COUNT = 16, 82 | sharding = { 83 | ['aaaaaaaa-0000-0000-0000-000000000000'] = { 84 | -- replicaset #1 85 | replicas = { 86 | ['aaaaaaaa-0000-0000-0000-000000000001'] = { 87 | uri = 'guest@127.0.0.1:3301', 88 | name = 'spb', 89 | master = true 90 | }, 91 | }, 92 | }, 93 | ['bbbbbbbb-0000-0000-0000-000000000000'] = { 94 | -- replicaset #2 95 | replicas = { 96 | ['bbbbbbbb-0000-0000-0000-000000000001'] = { 97 | uri = 'guest@127.0.0.1:3401', 98 | name = 'moscow', 99 | master = true 100 | }, 101 | }, 102 | }, 103 | }, 104 | } 105 | ``` 106 | - `schema.lua` 107 | ```lua 108 | local schema = {} 109 | 110 | function schema.init() 111 | if box.info.ro then return end 112 | --[[ 113 | откроем доступ без пароля 114 | ]] 115 | 116 | box.schema.user.grant( 117 | 'guest', 'super', nil, nil, 118 | { if_not_exists=true } 119 | ) 120 | 121 | --[[ 122 | Создание схемы данных, индексов 123 | ]] 124 | 125 | --[[ 126 | Таблица `redirector` — хранилище коротких ссылок 127 | if_not_exists — флаг, о том чтобы не генерировать исключение, если спейс уже существует 128 | ]] 129 | box.schema.space.create( 130 | 'redirector', 131 | { if_not_exists=true } 132 | ) 133 | 134 | box.space.redirector:format({ 135 | { name='source', type='string' }, 136 | { name='short', type='string' }, 137 | { name='bucket_id', type='unsigned'}, 138 | }) 139 | 140 | --[[ 141 | Первичный ключ, первый создаваемый индекс 142 | ]] 143 | box.space.redirector:create_index( 144 | 'primary', 145 | { 146 | parts = { 'source' }, 147 | if_not_exists = true, 148 | } 149 | ) 150 | 151 | --[[ 152 | Вторичный индекс, для быстрого поиска строки по короткой ссылке 153 | ]] 154 | box.space.redirector:create_index( 155 | 'short', 156 | { 157 | parts = { 'short' }, 158 | if_not_exists = true, 159 | } 160 | ) 161 | 162 | --[[ 163 | Индекс для шардирования данных 164 | ]] 165 | box.space.redirector:create_index( 166 | 'bucket_id', 167 | { 168 | parts = { 'bucket_id' }, 169 | unique = false, 170 | if_not_exists = true, 171 | } 172 | ) 173 | end 174 | 175 | return schema 176 | ``` 177 | - `app.lua` 178 | ```lua 179 | local app = {} 180 | local digest = require 'digest' 181 | local vshard = require 'vshard' 182 | 183 | function app.url(short) 184 | local bucket_id = vshard.router.bucket_id_mpcrc32(short) 185 | local url, err = vshard.router.callro( 186 | bucket_id, 187 | 'box.space.redirector.index.short:get', 188 | {short} 189 | ) 190 | if url ~= nil then 191 | return url[1] 192 | elseif err ~= nil then 193 | error(err) 194 | else 195 | return 196 | end 197 | end 198 | 199 | function app.shorten(url) 200 | -- while true do 201 | --[[ map/reduce для поиска существующего url ]] 202 | local shards, err = vshard.router.routeall() 203 | if err ~= nil then error(err) end 204 | for uid, replica in pairs(shards) do 205 | local rec = replica:callro('box.space.redirector:get',{ url }) 206 | if rec ~= nil then 207 | return rec[2] 208 | end 209 | end 210 | 211 | --[[ пробуем создать ]] 212 | local short = digest.sha1_hex(url):sub(1,10) 213 | local bucket_id = vshard.router.bucket_id_mpcrc32(short) 214 | local success, err = vshard.router.callrw( 215 | bucket_id, 216 | 'box.space.redirector:insert', 217 | { {url, short, bucket_id} }, 218 | { timeout = 1 } 219 | ) 220 | if success then 221 | return short 222 | elseif err ~= nil then 223 | error(err) 224 | end 225 | -- end 226 | end 227 | 228 | return app 229 | ``` 230 | - `server.lua` 231 | ```lua 232 | local log = require('log') 233 | local socket = require('socket') 234 | local digest = require('digest') 235 | local httpd = require('http.server').new('0.0.0.0', tonumber(os.getenv('TT_HTTP_PORT'))) 236 | 237 | --[[ 238 | HTTP сервер обработки запросов 239 | ]] 240 | local hostname = 'localhost' ---<< 248 | 249 | 250 |
251 | 252 | 253 |
254 | 255 | 256 | ]] 257 | 258 | function root(req) 259 | local url = req:param('url') 260 | if url ~= nil then 261 | local short = app.shorten(url) 262 | if short == nil then 263 | return { 264 | status = 404, 265 | } 266 | end 267 | 268 | local pasteable_short = 'http://' .. hostname .. ':' .. os.getenv('TT_HTTP_PORT') .. '/' .. short 269 | return { 270 | status = 200, 271 | headers = { ['content-type'] = 'text/html; charset=utf8' }, 272 | body = pasteable_short 273 | } 274 | end 275 | 276 | return { 277 | status = 200, 278 | headers = { ['content-type'] = 'text/html; charset=utf8' }, 279 | body = form 280 | } 281 | end 282 | 283 | function short(req) 284 | local short = req:stash('short') 285 | local source = app.url(short) 286 | if source == nil then 287 | return {status=404} 288 | end 289 | 290 | return { 291 | status = 301, 292 | headers = { ['location'] = source }, 293 | } 294 | end 295 | 296 | httpd:route( 297 | { path = '/:short' }, short) 298 | 299 | httpd:route( 300 | { path = '/' }, root) 301 | 302 | local function init() 303 | httpd:start() 304 | end 305 | 306 | local function stop() 307 | if httpd ~= nil then 308 | httpd:stop() 309 | end 310 | httpd = nil 311 | end 312 | 313 | return { 314 | init=init, 315 | stop=stop, 316 | } 317 | ``` 318 | 319 | - `init.lua` 320 | ```lua 321 | vshard = require('vshard') 322 | local topology = require('topology') 323 | local fio = require('fio') 324 | local schema = require('schema') 325 | 326 | local instance_name = assert( 327 | os.getenv('TT_INSTANCE_NAME'), "TT_INSTANCE_NAME required" 328 | ) 329 | local data_dir = os.getenv('TT_DATADIR') or "data/"..instance_name 330 | if not fio.stat(data_dir) then 331 | fio.mktree(data_dir) 332 | end 333 | 334 | vshard.storage.cfg({ 335 | bucket_count = topology.BUCKET_COUNT, 336 | sharding = topology.sharding, 337 | memtx_dir = data_dir, 338 | wal_dir = data_dir, 339 | replication_connect_quorum = 1, 340 | }, 341 | assert(os.getenv('TT_INSTANCE_UUID',"TT_INSTANCE_UUID required")) 342 | ) 343 | 344 | vshard.router.cfg{ 345 | bucket_count = topology.BUCKET_COUNT, 346 | sharding = topology.sharding, 347 | } 348 | 349 | schema.init() 350 | app = require 'app' 351 | 352 | server = require 'server' 353 | server.init() 354 | ``` 355 | - `spb.sh` 356 | ```lua 357 | TT_INSTANCE_UUID=aaaaaaaa-0000-0000-0000-000000000001 \ 358 | TT_INSTANCE_NAME=spb \ 359 | TT_HTTP_PORT=8081 \ 360 | tarantool init.lua 361 | ``` 362 | - `moscow.sh` 363 | ```lua 364 | TT_INSTANCE_UUID=bbbbbbbb-0000-0000-0000-000000000001 \ 365 | TT_INSTANCE_NAME=moscow \ 366 | TT_HTTP_PORT=8082 \ 367 | tarantool init.lua 368 | ``` 369 | 370 | ### Запуск 371 | 372 | - Удалим устаревшие рабочие директории 373 | ```bash 374 | rm -rf data/* 375 | ``` 376 | - Запустим два узла 377 | ```bash 378 | ./spb.sh 379 | ``` 380 | ```bash 381 | ./moscow.sh 382 | ``` 383 | - Вновь требуется бутстрап 384 | ``` 385 | tarantoolctl connect 127.0.0.1:3301 386 | ``` 387 | - `tarantool>` 388 | ```lua 389 | vshard.router.bootstrap() 390 | ``` 391 | 392 | ## Оно заработало? 393 | 394 | - Откроем браузер на `http://:8081/` 395 | - Должна появится форма для ввода `url` 396 | - Введем любой `url` 397 | - Получим короткую ссылку 398 | - Перейдем по короткой ссылке 399 | - Или можно взять `curl` 400 | ```bash 401 | curl -G "http://localhost:8081/"\ 402 | --data-urlencode "url=https://mail.ru" 403 | ``` 404 | ```bash 405 | curl -L $(!!) 406 | ``` 407 | - Или можно воспользоваться готовым скриптом, который пытается укоротить `https://google.com/?q=` 408 | - `test.lua` 409 | ```lua 410 | local log = require('log') 411 | local client = require('http.client') 412 | 413 | --[[ 414 | Простой тест для проверки укорачивания ссылок 415 | ]] 416 | local host = '127.0.0.1' 417 | local port = 8081 418 | 419 | local source = 'https://google.com/?q=' .. tostring(os.time()) 420 | local makeshort = 'http://' .. host .. ':' .. port .. '/?url=' 421 | local response = client.get(makeshort .. source) 422 | local short = response.body 423 | print(short, '->', source) 424 | local redirect = client.get(short, {follow_location=false}) 425 | 426 | assert(redirect.headers['location'] == source, 427 | "Source url and redirect not equal: " .. source .. " " .. 428 | redirect.headers['location'] ) 429 | ``` 430 | - Запуск теста 431 | ```bash 432 | tarantool test.lua 433 | ``` 434 | - Примерный вывод 435 | ```bash 436 | http://localhost:8081/XniDtG2jrBa23Q 437 | -> Https://google.com/?q=1607896232 438 | ``` 439 | --------------------------------------------------------------------------------