Rövid útmutató a Redis Lua szkriptek készítéséhez

A Redis egy népszerű memóriarács, amelyet a folyamatok közötti kommunikációhoz és az adattároláshoz használnak. Talán hallotta, hogy ez lehetővé teszi a Lua szkriptek futtatását, de még mindig nem biztos benne, hogy miért. Ha ez úgy hangzik, mint te, olvass tovább.

Előfeltételek

Az útmutató követése érdekében telepítenie kell a Redist a rendszerére. Hasznos lehet a Redis parancsok hivatkozásának ellenőrzése olvasás közben.

Miért van szükségem Lua Scriptekre?

Röviden: teljesítménynövekedés. A Redisben végzett legtöbb feladat sok lépést tartalmaz. Ahelyett, hogy ezeket a lépéseket az alkalmazás nyelvén hajtaná végre, megteheti Redis-n belül a Lua-val.

  • Ez jobb teljesítményt eredményezhet.
  • Ezenkívül a szkripten belül minden lépést atomi módon hajtanak végre. Semmilyen más Redis parancs nem futtatható a parancsfájl végrehajtása közben.

Például Lua szkriptekkel módosítom a Redisben tárolt JSON karakterláncokat. Ezt a cikk végéhez közelebb ismertetem részletesen.

De nem ismerek Lua-t

Ne aggódj, Lua-t nem nagyon nehéz megérteni. Ha ismeri a C család bármelyik nyelvét, akkor rendben kell lennie Lua-val. Ebben a cikkben működő példákat is bemutatok.

Mutasson nekem egy példát

Kezdjük azzal, hogy a szkripteket futtatjuk a redis-cli segítségével . Kezdje:

redis-cli

Most futtassa a következő parancsot:

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

Az EVAL parancs utasítja Redist, hogy futtassa a következő szkriptet. A ”redis.call(‘set’, KEYS[1], ARGV[1])”karakterlánc a szkriptünk, amely funkcionálisan megegyezik a Redis setparancsával. Három paraméter követi a szkript szövegét:

  1. A megadott kulcsok száma
  2. Kulcs neve
  3. Első érv

A szkript argumentumai két csoportba sorolhatók: KEYS és ARGV .

Megadjuk, hogy a szkript hány kulcsot igényel, a közvetlenül utána következő számmal. Példánkban ez 1 . Közvetlenül ez után a szám után egymás után meg kell adnunk ezeket a kulcsokat. KEYS táblázatként érhetők el a szkripten belül. Esetünkben egyetlen értéket tartalmaz az 1.key:name indexen .

Ne feledje, hogy a Lua indexelt táblázatai az 1-gyel kezdődnek , nem pedig 0-val .

A kulcsok után tetszőleges számú argumentumot megadhatunk, amelyek a Lua-ban lesznek elérhetők ARGV táblázatként. Ebben a példában egyetlen ARGV -argument: karakterláncot adunk meg value. Amint már sejtette, a fenti parancs a kulcsot key:nameértékre állítja value.

Úgy ítélik meg egy jó gyakorlat, hogy gombok, melyek a script felhasználások KEYS , és minden más érveket ARGV . Tehát ne adja meg a KEYS értéket 0-ként, majd adja meg az összes kulcsot az ARGV táblában.

Most ellenőrizzük, hogy a szkript sikeresen elkészült-e. Ezt egy másik szkript futtatásával fogjuk megtenni, amely a kulcsot kapja Redistől:

eval “return redis.call ('get', KEYS [1])” 1 kulcs: név

A kimenet legyen ”value”, ami azt jelenti, hogy az előző szkript sikeresen beállította a kulcsot “key:name”.

El tudja magyarázni a forgatókönyvet?

Az első szkriptünk egyetlen utasításból áll: a redis.callfunction:

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

Ezzel redis.callbármely Redis parancsot végrehajthat. Az első argumentum a parancs neve, majd paraméterei. A setparancs esetében ezek az argumentumok kulcs és érték . Az összes Redis parancs támogatott. A dokumentáció szerint:

Redis ugyanazt a Lua tolmácsot használja az összes parancs futtatásához

A második szkriptünk valamivel többet jelent, mint egyetlen parancs futtatása - értéket is ad vissza:

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

A szkript által visszaadott mindent elküldjük a hívási folyamatnak. Esetünkben ez a folyamat redis-cli, és az eredményt a terminál ablakában látja.

Valami összetettebb?

Egyszer a Lua szkripteket használtam a hash térkép elemeinek adott sorrendben történő visszaadásához. Magát a sorrendet rendezett készletben tárolt kivonatkulcsok határozták meg.

Először állítsuk be adatainkat a parancsok redis-cli futtatásával :

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

Ezek a parancsok egy kivonatkódot hoznak létre a kulcson hkeysés egy rendezett kulcs készletet, orderamely a kiválasztott kulcsokat tartalmazza hkeysegy adott sorrendben.

Érdemes ellenőrizni a részletekért a hmset és a zadd parancsokat.

Futtassuk a következő szkriptet:

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

A következő kimenetet kell látnia:

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