Короткий посібник зі створення сценаріїв Redis Lua

Redis - це популярна сітка в пам'яті, яка використовується для міжпроцесорного зв'язку та зберігання даних. Можливо, ви чули, що це дозволяє запускати сценарії Lua, але ви все ще не впевнені, чому. Якщо це схоже на вас, читайте далі.

Передумови

Вам слід встановити Redis у вашій системі, щоб слідувати цьому посібнику. Може бути корисно перевірити посилання на команди Redis під час читання.

Навіщо мені потрібні сценарії Lua?

Коротше кажучи: приріст продуктивності. Більшість завдань, які ви виконуєте в Redis, передбачають багато кроків. Замість того, щоб робити ці кроки мовою вашого додатка, ви можете робити це всередині Redis за допомогою Lua.

  • Це може призвести до кращої роботи.
  • Крім того, всі кроки в сценарії виконуються атомарним способом. Жодна інша команда Redis не може виконуватися під час виконання сценарію.

Наприклад, я використовую сценарії Lua для зміни рядків JSON, що зберігаються в Redis. Я докладно описую це ближче до кінця цієї статті.

Але я не знаю жодної Луї

Не хвилюйся, Луа не дуже важко зрозуміти. Якщо ви знаєте будь-яку мову сім’ї Сі, вам слід погодитися з Луа. Крім того, я наводжу робочі приклади в цій статті.

Покажіть приклад

Почнемо із запуску скриптів через redis-cli . Почніть з:

redis-cli

Тепер запустіть таку команду:

eval “redis.call(‘set’, KEYS[1], ARGV[1])” 1 key:name value

Команда EVAL - це те, що говорить Redis запустити наступний сценарій. ”redis.call(‘set’, KEYS[1], ARGV[1])”Рядок наш скрипт , який функціонально ідентичний тим , що Redis ігровий setкоманди. За текстом сценарію слідують три параметри:

  1. Кількість наданих ключів
  2. Назва ключа
  3. Перший аргумент

Аргументи сценарію діляться на дві групи: КЛЮЧІ та ARGV .

Ми вказуємо, скільки клавіш вимагає сценарій, і номер, що йде безпосередньо за ним. У нашому прикладі це 1 . Відразу після цього номера нам потрібно надати ці ключі один за одним. Вони доступні як таблиця KEYS у сценарії. У нашому випадку він містить одне значення key:nameз індексом 1 .

Зауважте, що таблиці, проіндексовані Lua, починаються з індексу 1, а не 0 .

Ми можемо надати будь-яку кількість аргументів після ключів, які будуть доступні в Lua як таблиця ARGV . У цьому прикладі, ми пропонуємо один ARGV -argument: рядок value. Як ви вже здогадалися, наведена команда встановлює для ключа key:nameзначення value.

Вважається гарною практикою надавати ключі, які сценарій використовує як КЛЮЧІ , а всі інші аргументи як ARGV . Тому не слід вказувати KEYS як 0, а потім надавати всі ключі в таблиці ARGV .

Давайте зараз перевіримо, чи сценарій успішно завершено. Ми зробимо це, запустивши інший скрипт, який отримує ключ від Redis:

eval “повернути redis.call ('отримати', КЛЮЧІ [1])” 1 ключ: ім'я

Результат повинен бути ”value”, що означає, що попередній сценарій успішно встановив ключ “key:name”.

Ви можете пояснити сценарій?

Наш перший сценарій складається з одного твердження: redis.callфункції:

redis.call(‘set’, KEYS[1], ARGV[1])

За допомогою redis.callви можете виконати будь-яку команду Redis. Перший аргумент - це назва цієї команди, а потім її параметри. У випадку з setкомандою ці аргументи є ключовими та значеннями . Усі команди Redis підтримуються. Згідно з документацією:

Redis використовує один і той же інтерпретатор Lua для запуску всіх команд

Наш другий сценарій робить трохи більше, ніж просто запускає одну команду - він також повертає значення:

eval “return redis.call(‘get’, KEYS[1])” 1 key:name

Все, що повертається сценарієм, надсилається у процес виклику. У нашому випадку цей процес є повторним, і ви побачите результат у вікні терміналу.

Щось більш складне?

Одного разу я використовував скрипти Lua для повернення елементів з хеш-карти в певному порядку. Сам порядок визначався хеш-ключами, що зберігаються у відсортованому наборі.

Давайте спочатку налаштуємо наші дані, виконавши ці команди в redis-cli :

hmset hkeys key:1 value:1 key:2 value:2 key:3 value:3 key:4 value:4 key:5 value:5 key:6 value:6 zadd order 1 key:3 2 key:1 3 key:2

Ці команди створюють хеш-карту за ключем hkeysта відсортований набір за ключем, orderякий містить вибрані ключі з hkeysу певному порядку.

Можливо, ви захочете перевірити посилання на команди hmset та zadd для деталей.

Давайте запустимо такий сценарій:

eval “local order = redis.call(‘zrange’, KEYS[1], 0, -1); return redis.call(‘hmget’,KEYS[2],unpack(order));” 2 order hkeys

Ви повинні побачити такий результат:

“value:3” “value:1” “value:2”

Which means that we got values of the keys we wanted and in the correct order.

Do I have to specify full script text to run it?

No! Redis allows you to preload a script into memory with the SCRIPT LOAD command:

script load “return redis.call(‘get’, KEYS[1])”

You should see an output like this:

“4e6d8fc8bb01276962cce5371fa795a7763657ae”

This is the unique hash of the script which you need to provide to the EVALSHA command to run the script:

evalsha 4e6d8fc8bb01276962cce5371fa795a7763657ae 1 key:name

Note: you should use actual SHA1 hash returned by the SCRIPT LOAD command, the hash above is only an example.

What did you mention about changing JSON?

Sometimes people store JSON objects in Redis. Whether it is a good idea or not is another story, but in practice, this happens a lot.

If you have to change a key in this JSON object, you need to get it from Redis, parse it, change the key, then serialize and set it back to Redis. There are a couple of problems with this approach:

  1. Concurrency. Another process can change this JSON between our get and set operations. In this case, the change will be lost.
  2. Performance. If you do these changes often enough and if the object is rather big, this might become the bottleneck of your app. You can win some performance by implementing this logic in Lua.

Let’s add a test JSON string to Redis under key obj:

set obj ‘{“a”:”foo”,”b”:”bar”}’

Now let’s run our script:

EVAL ‘local obj = redis.call(“get”,KEYS[1]); local obj2 = string.gsub(obj,”(“ .. ARGV[1] .. “\”:)([^,}]+)”, “%1” .. ARGV[2]); return redis.call(“set”,KEYS[1],obj2);’ 1 obj b bar2

Now we will have the following object under key obj:

{“a”:”foo”,”b”:”bar2"}

You can instead load this script with the SCRIPT LOAD command:

SCRIPT LOAD ‘local obj = redis.call(“get”,KEYS[1]); local obj2 = string.gsub(obj,”(“ .. ARGV[1] .. “\”:)([^,}]+)”, “%1” .. ARGV[2]); return redis.call(“set”,KEYS[1],obj2);’

and then run it like this:

EVALSHA  1 obj b bar2

Some notes:

  • The .. is the string concatenation operator in Lua.
  • We use a RegEx pattern to match key and replace its value. If you don’t understand this Regular Expression, you can check my recent guide.
  • One difference of the Lua RegEx flavor from most other flavors is that we use % as both backreference mark and escape character for RegEx special symbols.
  • We still escape with \ and not % because we escape Lua string delimiter, not RegEx special symbol.

Should I always use Lua scripts?

No. I recommend only using them when you can prove that it results in better performance. Always run benchmarks first.

If all you want is atomicity, then you should check Redis transactions instead.

Also, your script shouldn’t be too long. Remember that while a script is running, everything else is waiting for it to finish. If your script takes quite some time, it can cause bottlenecks instead of improving performance. The script stops after reaching a timeout (5 seconds by default).

Last Word

For more information on Lua check lua.org.

You can check my node.js library on GitHub for some examples of Lua scripts (see src/lua folder). You can also use this library in node.js to change JSON objects without writing any Lua scripts yourself.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Thank you for reading this article. Questions and comments are much appreciated. You are also welcome to follow me on Twitter.