-
Notifications
You must be signed in to change notification settings - Fork 975
Scripting and Functions
Redis functionality can be extended through many ways, of which Lua Scripting and Functions are two approaches that do not require specific pre-requisites on the server.
Lua is a powerful scripting language that is supported at the core of Redis. Lua scripts can be invoked dynamically by providing the script contents to Redis or used as stored procedure by loading the script into Redis and using its digest to invoke it.
String helloWorld = redis.eval("return ARGV[1]", STATUS, new String[0], "Hello World");
Using Lua scripts is straightforward. Consuming results in Java requires additional details to consume the result through a matching type.
As we do not know what your script will return, the API uses call-site generics for you to specify the result type.
Additionally, you must provide a ScriptOutputType
hint to EVAL
so that the driver uses the appropriate output parser. See Output Formats for further details.
Lua scripts can be stored on the server for repeated execution.
Dynamically-generated scripts are an anti-pattern as each script is stored in Redis' script cache.
Generating scripts during the application runtime may, and probably will, exhaust the host’s memory resources for caching them.
Instead, scripts should be as generic as possible and provide customized execution via their arguments.
You can register a script through SCRIPT LOAD
and use its SHA digest to invoke it later:
String digest = redis.scriptLoad("return ARGV[1]", STATUS, new String[0], "Hello World");
// later
String helloWorld = redis.evalsha(digest, STATUS, new String[0], "Hello World");
Redis Functions is an evolution of the scripting API to provide extensibility beyond Lua. Functions can leverage different engines and follow a model where a function library registers functionality to be invoked later with the FCALL
command.
redis.functionLoad("FUNCTION LOAD "#!lua name=mylib\nredis.register_function('knockknock', function() return 'Who\\'s there?' end)");
String response = redis.fcall("knockknock", STATUS);
Using Functions is straightforward. Consuming results in Java requires additional details to consume the result through a matching type.
As we do not know what your function will return, the API uses call-site generics for you to specify the result type.
Additionally, you must provide a ScriptOutputType
hint to EVAL
so that the driver uses the appropriate output parser. See Output Formats for further details.
You can choose from one of the following:
-
BOOLEAN
: Boolean output, expects a number0
or1
to be converted to a boolean value. -
INTEGER
: 64-bit Integer output, represented as JavaLong
. -
MULTI
: List of flat arrays. -
STATUS
: Simple status value such asOK
. The Redis response is parsed as ASCII. -
VALUE
: Value return type decoded throughRedisCodec
. -
OBJECT
: RESP3-defined object output supporting all Redis response structures.
Using dynamic functionality without a documented response structure can impose quite some complexity on your application. If you consider using scripting or functions, then you can use {command-interfaces-link} to declare an interface along with methods that represent your scripting or function landscape. Declaring a method with input arguments and a response type not only makes it obvious how the script or function is supposed to be called, but also how the response structure should look like.
Let’s take a look at a simple function call first:
local function my_hlastmodified(keys, args)
local hash = keys[1]
return redis.call('HGET', hash, '_last_modified_')
end
Long lastModified = redis.fcall("my_hlastmodified", INTEGER, "my_hash");
This example calls the my_hlastmodified
function expecting some Long
response an input argument.
Calling a function from a single place in your code isn’t an issue on its own.
The arrangement becomes problematic once the number of functions grows or you start calling the functions with different arguments from various places in your code.
Without the function code, it becomes impossible to investigate how the response mechanics work or determine the argument semantics, as there is no single place to document the function behavior.
Let’s apply the Command Interface pattern to see how the the declaration and call sites change:
interface MyCustomCommands extends Commands {
/**
* Retrieve the last modified value from the hash key.
* @param hashKey the key of the hash.
* @return the last modified timestamp, can be {@code null}.
*/
@Command("FCALL my_hlastmodified 1 :hashKey")
Long getLastModified(@Param("my_hash") String hashKey);
}
MyCustomCommands myCommands = …;
Long lastModified = myCommands.getLastModified("my_hash");
By declaring a command method, you create a place that allows for storing additional documentation. The method declaration makes clear what the function call expects and what you get in return.
Lettuce documentation was moved to https://redis.github.io/lettuce/overview/
Intro
Getting started
- Getting started
- Redis URI and connection details
- Basic usage
- Asynchronous API
- Reactive API
- Publish/Subscribe
- Transactions/Multi
- Scripting and Functions
- Redis Command Interfaces
- FAQ
HA and Sharding
Advanced usage
- Configuring Client resources
- Client Options
- Dynamic Command Interfaces
- SSL Connections
- Native Transports
- Unix Domain Sockets
- Streaming API
- Events
- Command Latency Metrics
- Tracing
- Stateful Connections
- Pipelining/Flushing
- Connection Pooling
- Graal Native Image
- Custom commands
Integration and Extension
Internals