Кратко ръководство за скриптове на Redis Lua

Redis е популярна мрежа в паметта, използвана за междупроцесна комуникация и съхранение на данни. Може да сте чували, че ви позволява да стартирате скриптове на Lua, но все още не сте сигурни защо. Ако това ви звучи като четете.

Предпоставки

Трябва да имате инсталиран Redis на вашата система, за да следвате това ръководство. По време на четенето може да е полезно да проверите справка за командите Redis.

Защо са ми необходими Lua скриптове?

Накратко: повишаване на производителността. Повечето задачи, които правите в Redis, включват много стъпки. Вместо да правите тези стъпки на езика на вашето приложение, можете да го направите в Redis с Lua.

  • Това може да доведе до по-добро представяне.
  • Също така всички стъпки в скрипта се изпълняват по атомен начин. Никоя друга команда Redis не може да се изпълни, докато скриптът се изпълнява.

Например използвам скриптове Lua за промяна на JSON низове, съхранявани в Redis. Описвам това подробно по-близо до края на тази статия.

Но не познавам нито една Луа

Не се притеснявайте, Lua не е много трудна за разбиране. Ако знаете някой език от семейство C, трябва да сте добре с Lua. Също така предоставям работни примери в тази статия.

Покажете ми пример

Нека започнем със стартиране на скриптове чрез 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])”низ е нашата азбука, която е функционално идентични с преразпределяне'S setкоманда. Три параметъра следват текста на скрипта:

  1. Броят на предоставените ключове
  2. Име на ключ
  3. Първи аргумент

Аргументите на скрипта се разделят на две групи: KEYS и ARGV .

Посочваме колко ключове изисква скриптът с номера, непосредствено след него. В нашия пример е 1 . Веднага след този номер трябва да предоставим тези ключове един след друг. Те са достъпни като KEYS таблица в скрипта. В нашия случай той съдържа единична стойност key:nameпри индекс 1 .

Имайте предвид, че индексираните с Lua таблици започват с индекс 1, а не с 0 .

Можем да предоставим произволен брой аргументи след ключовете, които ще бъдат достъпни в Lua като таблица ARGV . В този пример ние предоставяме един ARGV -аргумент: низ value. Както вече се досетихте, горната команда задава ключа key:nameза стойност value.

Счита се за добра практика да се предоставят ключове, които скриптът използва като КЛЮЧОВЕ , а всички други аргументи като ARGV . Така че не трябва да посочвате KEYS като 0 и след това да предоставяте всички ключове в таблицата ARGV .

Нека сега проверим дали скриптът е завършен успешно. Ще направим това, като стартираме друг скрипт, който получава ключа от Redis:

eval “return redis.call ('get', KEYS [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

Всичко, върнато от скрипта, се изпраща в процеса на извикване. В нашия случай този процес е redis-cli и ще видите резултата в прозореца на вашия терминал.

Нещо по-сложно?

Веднъж използвах скриптове 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.