En hurtig guide til Redis Lua-scripting

Redis er et populært net i hukommelsen, der bruges til interprocess-kommunikation og datalagring. Du har måske hørt, at det lader dig køre Lua-scripts, men du er stadig ikke sikker på hvorfor. Hvis dette lyder som dig, skal du læse videre.

Forudsætninger

Du skal have Redis installeret på dit system for at følge denne vejledning. Det kan være nyttigt at kontrollere Redis-kommandoreference under læsning.

Hvorfor har jeg brug for Lua-scripts?

Kort sagt: præstationsgevinst. De fleste opgaver, du udfører i Redis, involverer mange trin. I stedet for at udføre disse trin på sproget i din applikation, kan du gøre det inde i Redis med Lua.

  • Dette kan resultere i bedre ydeevne.
  • Også alle trin i et script udføres på en atomær måde. Ingen anden Redis-kommando kan køre, mens et script udføres.

For eksempel bruger jeg Lua-scripts til at ændre JSON-strenge gemt i Redis. Jeg beskriver dette nærmere tæt på slutningen af ​​denne artikel.

Men jeg kender ingen Lua

Bare rolig, Lua er ikke særlig vanskelig at forstå. Hvis du kender noget sprog i C-familien, skal du være okay med Lua. Jeg leverer også arbejdseksempler i denne artikel.

Vis mig et eksempel

Lad os starte med at køre scripts via redis-cli . Start det med:

redis-cli

Kør nu følgende kommando:

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

Den EVAL kommando er hvad fortæller Redis at køre scriptet som følger. Den ”redis.call(‘set’, KEYS[1], ARGV[1])”snor er vores script der er funktionelt identiske med de Redis s setkommando. Tre parametre følger scriptteksten:

  1. Antallet af medfølgende nøgler
  2. Nøglenavn
  3. Første argument

Scriptargumenter falder i to grupper: KEYS og ARGV .

Vi specificerer, hvor mange nøgler scriptet kræver med antallet umiddelbart efter. I vores eksempel er det 1 . Umiddelbart efter dette nummer skal vi give disse nøgler efter hinanden. De er tilgængelige som KEYS- tabel i scriptet. I vores tilfælde indeholder den en enkelt værdi key:nameved indeks 1 .

Bemærk, at Lua-indekserede tabeller starter med indeks 1, ikke 0 .

Vi kan give et hvilket som helst antal argumenter efter tasterne, som vil være tilgængelige i Lua som ARGV- tabellen. I dette eksempel leverer vi et enkelt ARGV -argument: streng value. Som du allerede gættede, indstiller ovenstående kommando nøglen key:nametil værdi value.

Det betragtes som en god praksis at give nøgler, som scriptet bruger som TASTER , og alle andre argumenter som ARGV . Så du bør ikke angive NØGLER som 0 og derefter angive alle nøgler i ARGV- tabellen.

Lad os nu kontrollere, om scriptet blev gennemført med succes. Vi skal gøre dette ved at køre et andet script, der får nøglen fra Redis:

eval “return redis.call ('get', KEYS [1])" 1 nøgle: navn

Outputtet skal være ”value”, hvilket betyder, at det forrige script med succes indstillede nøglen “key:name”.

Kan du forklare scriptet?

Vores første script består af en enkelt erklæring: redis.callfunktionen:

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

Med redis.callkan du udføre enhver Redis-kommando. Det første argument er navnet på denne kommando efterfulgt af dens parametre. I tilfælde af setkommandoen er disse argumenter nøgle og værdi . Alle Redis-kommandoer understøttes. Ifølge dokumentationen:

Redis bruger den samme Lua-tolk til at køre alle kommandoer

Vores andet script gør lidt mere end bare at køre en enkelt kommando - det returnerer også en værdi:

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

Alt, der returneres af scriptet, sendes til opkaldsprocessen. I vores tilfælde er denne proces redis-cli, og du vil se resultatet i dit terminalvindue.

Noget mere komplekst?

Jeg brugte engang Lua-scripts til at returnere elementer fra et hash-kort i en bestemt rækkefølge. Selve rækkefølgen blev specificeret af hash-nøgler gemt i et sorteret sæt.

Lad os først opsætte vores data ved at køre disse kommandoer i 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

Disse kommandoer opretter et hash-kort ved nøglen hkeysog et sorteret sæt ved nøgle, orderder indeholder valgte taster hkeysi en bestemt rækkefølge.

Det kan være en god idé at tjekke hmset- og zadd- kommandoreferencen for at få flere oplysninger.

Lad os køre følgende script:

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

Du skal se følgende output:

“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.

Også dit script bør ikke være for langt. Husk, at mens et script kører, venter alt andet på, at det afsluttes. Hvis dit script tager lang tid, kan det forårsage flaskehalse i stedet for at forbedre ydeevnen. Scriptet stopper efter at have nået en timeout (5 sekunder som standard).

Sidste ord

For mere information om Lua, se lua.org.

Du kan tjekke mit node.js-bibliotek på GitHub for nogle eksempler på Lua-scripts (se src/luamappe). Du kan også bruge dette bibliotek i node.js til at ændre JSON-objekter uden at skrive nogen Lua-scripts selv.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Tak fordi du læste denne artikel. Spørgsmål og kommentarer er meget værdsat. Du er også velkommen til at følge mig på Twitter .