View Source LibRedis (lib_redis v0.1.3)
LibRedis
LibRedis is an eloquently crafted Redis client implemented in Elixir. It serves as a remarkable encapsulation of the Redix project, while additionally incorporating connection pooling and Redis cluster capabilities. Within this client, two primary interfaces are exposed, namely command/3
and pipeline/3
.
Please refer to the detailed project documentation: hexdocs
WARNING:
The deployment of the Cluster mode in a production environment necessitates meticulous deliberation and vigilance, as it is still undergoing comprehensive and stringent testing.
Features
- [x] Connection pooling
- [x] Standalone mode
- [x] Cluster mode
Installation
To use LibRedis in your Elixir project, simply add it as a dependency in your mix.exs
file:
defp deps do
[
{:lib_redis, "~> 0.1.3"}
]
end
Then, run mix deps.get
to install the dependency.
Usage
Standalone
To use LibRedis in singleton mode, you have to set your client as :standalone
mode:
standalone_options = [
name: :redis,
mode: :standalone,
url: "redis://:pwd@localhost:6379",
pool_size: 5
]
standalone = LibRedis.new(standalone_options)
Then, add the LibRedis
application to your supervision tree:
children = [
{LibRedis, redis: standalone}
]
The options
parameter is a keyword list that contains the connection options for Redis. The pool_size
option specifies the number of connections in the pool.
Thanks to powerful nimble_pool, we can easily use a nimble pool to manage redis connections.
Once you have a instance, you can use the command/3
and pipeline/3
APIs to execute Redis commands:
{:ok, result} = LibRedis.command(standalone, ["GET", "mykey"])
The command/3
function takes a LibRedis object, a Redis command as a list of strings, and an optional timeout in milliseconds. It returns a tuple with the status of the command execution and the result.
{:ok, result} = LibRedis.pipeline(standalone, [
["SET", "mykey", "value1"],
["GET", "mykey"]
])
The pipeline/3
function takes a LibRedis object and a list of Redis commands, where each command is represented as a list of strings. It returns a tuple with the status of the pipeline execution and a list of results.
Redis Cluster
To use LibRedis with Redis cluster, first create a cluster using the LibRedis.Cluster
module:
cluster_options = [
name: :redis,
mode: :cluster,
url: "redis://localhost:6381,redis://localhost:6382,redis://localhost:6383,redis://localhost:6384,redis://localhost:6385",
password: "123456",
pool_size: 5
]
cluster = LibRedis.new(cluster_options)
Then, add the LibRedis
application to your supervision tree:
children = [
{LibRedis, redis: cluster}
]
The cluster_options
parameter is a keyword list that contains the connection options for Redis cluster.
Once you have a cluster, you can use the command/3
and pipeline/3
APIs to execute Redis commands:
{:ok, result} = LibRedis.command(cluster, ["GET", "mykey"])
The command/3
function takes a cluster object, a Redis command as a list of strings, and an optional timeout in milliseconds. It returns a tuple with the status of the command execution and the result.
{:ok, result} = LibRedis.pipeline(cluster, [
["SET", "mykey", "value1"],
["GET", "mykey"]
])
The pipeline/3
function takes a cluster object and a list of Redis commands, where each command is represented as a list of strings. It returns a tuple with the status of the pipeline execution and a list of results.
Design
Connection Pooling
I leverage nimble_pool to encapsulate Redix connections and administer them within a pool. Every time a command or pipeline operation is executed, a connection is acquired from the pool and subsequently returned upon completion of the operation.
Standalone Mode
In standalone mode, the client maintains a solitary connection pool to a singular Redis instance. The client will seamlessly reestablish connection with the Redis instance in the event of any disruption.
Cluster Mode
Cluster mode presents a significantly more intricate setup compared to standalone mode. Within this mode, the client executes the CLUSTER SLOTS
command to obtain the cluster's topology. The client periodically retrieves this topology, typically every 10 seconds (though it can be adjusted), and stores it locally in the form of a cache (known as an Agent).
This cache facilitates the client's management of node addresses and their corresponding connections. If an address is not found within the cache, the client promptly establishes a new connection and stores it accordingly.
The client processes a single command by first identifying the node to which the command should be sent based on the command's key. It then locates the corresponding connection from the cache and executes the command on the connection.
Pipeline operation is far more complex than command operation.
- first, client will group commands in pipelines by it's key;
- then, client will iterate the grouped pipelines and execute them in parallel;
- finnal, client will merge the results of the pipelines and return them to the caller.
Client will automatically retry the command if the command fails due to a Moved
error. If the command fails with a Moved
error, means cluster topology has changed, the client will update the cluster topology and retry the command. If the command fails due to a Redis error, the client will return the error to the caller.
Test
To run unit tests, you can start a Redis service using Docker locally and then execute the mix test
command.
The specific way to start the Redis service can be referred to in the run.md file under the docker-compose directory.
Compiling 8 files (.ex)
Generated lib_redis app
Cover compiling modules ...
Finished in 3.3 seconds (0.00s async, 3.3s sync)
32 tests, 0 failures
Randomized with seed 498986
Generating cover results ...
| Percentage | Module |
| ----------- | -------------------------- |
| 0.00% | LibRedis.Utils |
| 50.00% | LibRedis.Error |
| 50.00% | LibRedis.Standalone |
| 71.88% | LibRedis.Pool |
| 76.47% | LibRedis.SlotFinder |
| 77.91% | LibRedis.Cluster |
| 93.75% | LibRedis.ClientStore |
| 96.15% | LibRedis |
| 100.00% | LibRedis.Client |
| 100.00% | LibRedis.SlotStore |
| 100.00% | LibRedis.Typespecs |
| ----------- | -------------------------- |
| 80.71% | Total |
Coverage test failed, threshold not met:
Coverage: 80.71%
Threshold: 90.00%
Generated HTML coverage results in "cover" directory
License
LibRedis is released under the MIT License.
Summary
Types
@type options() :: [ name: atom(), mode: term(), url: binary(), password: binary(), pool_size: non_neg_integer() ]
@type t() :: %LibRedis{ client: LibRedis.Client.t(), client_store_module: module(), mode: :cluster | :standalone, name: LibRedis.Typespecs.name(), password: LibRedis.Typespecs.password(), pool_size: non_neg_integer(), slot_store_module: module(), url: LibRedis.Typespecs.url() }
Functions
@spec command(t(), LibRedis.Client.command_t(), keyword()) :: LibRedis.Client.resp_t()
execute a redis command
Examples
iex> LibRedis.command(redis, ["SET", "key", "value"])
{:ok, "OK"}
create a new redis client
Options
:name
(atom/0
) - The name of the redis client The default value is:redis
.:mode
- The mode of the redis client, :standalone or :cluster The default value is:standalone
.:url
(String.t/0
) - Required. The url of the redis server, like redis://:123456@localhost:6379:password
(String.t/0
) - The password of the redis server, this option is useful in cluster mode The default value is""
.:pool_size
(non_neg_integer/0
) - The pool size of the redis client's pool The default value is10
.
Examples
iex> standalone_options = [
name: :redis,
mode: :standalone,
url: "redis://:pwd@localhost:6379",
pool_size: 5
]
iex> LibRedis.new(standalone_options)
@spec pipeline(t(), [LibRedis.Client.command_t()], keyword()) :: LibRedis.Client.resp_t()
execute a redis pipeline
Examples
iex> LibRedis.pipeline(redis, [["SET", "key", "value"], ["GET", "key"]])
{:ok, ["OK", "value"]}
@spec start_link(keyword()) :: GenServer.on_start()