nimble install ready
Ready is a Redis client that is built to work well in multi-threaded programs. A great use-case for Ready is in a multi-threaded HTTP server like Mummy.
Check out the examples/ folder for more sample code using Ready.
First you'll need to open a Redis connection. By default Ready connects to the default Redis server at localhost:6379. You can easily specify a different address and port in newRedisConn when needed.
import ready
let redis = newRedisConn() # Defaults to localhost:6379After opening a connection you can start sending commands. You can send any of Redis's vast set of commands.
import ready, std/options
let redis = newRedisConn() # Defaults to localhost:6379
let value = redis.command("GET", "key").to(Option[string])We use Option[string] above since the reply may be nil if the key is not present. Alternatively, if you know the key exists, you could just use string.
You can also easily work with replies to more complex commands:
import ready
let redis = newRedisConn() # Defaults to localhost:6379
let values = redis.command("MGET", "key1", "key2", "key3").to(seq[string])Here we are using MGET to request multiple keys in one command. Since we expect multiple reply entries, we can use to to convert the reply to a seq[string].
A call to command or receive will return a RedisReply object. You'll want to convert that into the types you expect. Ready makes that easy by providing the to proc.
# Basic conversions:
echo reply.to(int)
echo reply.to(string)
echo reply.to(Option[string]) # If the reply can be nil
# Convert array replies to seq:
echo reply.to(seq[int])
echo reply.to(seq[string])
echo reply.to(seq[Option[string]])
# Convert array replies to tuples:
echo reply.to((int, string))
echo reply.to((int, Option[string]))
echo reply.to((string, Option[string], int))
# Mix and match:
echo reply.to((string, Option[string], seq[int]))
# Index access, if you know the reply is an array you can access its elements
echo reply[0].to(string)
A call to reply.to for a type Ready does not know how to convert to will fail at compile time.
If Ready is unable to convert the reply from Redis to your requested type, a RedisError is raised.
Ready includes a built-in connection pool when compiled with --threads:on:
import ready
let redisPool = newRedisPool(3) # Defaults to localhost:6379
# This automatically removes a connection from the pool, runs the command
# and then returns it back to the pool
redisPool.command("PING")Or, if you want to run more than one command with the same connection:
import ready
let redisPool = newRedisPool(3) # Defaults to localhost:6379
redisPool.withConnection conn:
# `conn` is automatically recycled back into the pool after this block
discard conn.command("PING")Reusing Redis connections is much faster and more efficient than opening new connections for every command.
Ready also includes separate send and receive calls as an alternative to the command call. These commands make pipelining commands easy:
import ready
let redis = newRedisConn()
redis.send("MULTI")
redis.send("INCR", "mycount")
redis.send("SET", "mykey", "myvalue")
redis.send("EXEC")
## OR:
# redis.send([
# ("MULTI", @[]),
# ("INCR", @["mycount"]),
# ("SET", @["mykey", "myvalue"]),
# ("EXEC", @[])
#])
# Match the number of `receive` calls to the number of commands sent
discard redis.receive() # OK
discard redis.receive() # QUEUED
discard redis.receive() # QUEUED
let (num, _) = redis.receive().to((int, string))Pipelining as an advanced technique when using Redis that can drastically increase performance when possible.
Important! Remember to match the number of receive calls to the number of commands sent.
Ready makes it easy to use Redis's PubSub functionality.
Here we dedicate a thread to receiving messages on a PubSub connection while our other thread is free to send commands like SUBSCRIBE and UNSUBSCRIBE to manage the PubSub connection.
let pubsub = newRedisConn() # Defaults to localhost:6379
proc receiveThreadProc() =
try:
while true:
let reply = pubsub.receive()
echo "Event: ", reply[0].to(string)
echo "Channel: ", reply[1].to(string)
echo "Raw: ", reply
except RedisError as e:
echo e.msg
var receiveThread: Thread[void]
createThread(receiveThread, receiveThreadProc)
pubsub.send("SUBSCRIBE", "mychannel")Note that using PubSub with Ready requires threads.
You can use Ready in two ways, either by calling command or by calling send and receive. Calling command is the equivalent of calling send and then calling receive immediately.
Whenever a command or receive call gets an error reply from Redis a RedisError is raised. This means discarding the reply in discard redis.command("PING") is perfectly ok. If the reply was an error an exception would have been raised.
If you open a short-lived Redis connection, remember to call close when you no longer need it. The connections are not garbage collected. (For HTTP servers this is unlikely, see #1 for a brief discussion.)
First, where possible, it is more efficient to pipeline many Redis commands. This is easy to do with Ready, just call send multiple times (or ideally call send with a seq of commands).
Second, you may want to have a separate thread be sending vs receiving. A common use of this is PubSub, where one thread is dedicated to receiving messages and the sending thread manages what channels are subscribed to. See this example.