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

Link to this function

command(redis, command, opts \\ [])

View Source

execute a redis command

Examples

iex> LibRedis.command(redis, ["SET", "key", "value"])
{:ok, "OK"}
@spec new(options()) :: t()

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 is 10.

Examples

iex> standalone_options = [
  name: :redis,
  mode: :standalone,
  url: "redis://:pwd@localhost:6379",
  pool_size: 5
]
iex> LibRedis.new(standalone_options)
Link to this function

pipeline(redis, commands, opts \\ [])

View Source

execute a redis pipeline

Examples

iex> LibRedis.pipeline(redis, [["SET", "key", "value"], ["GET", "key"]])
{:ok, ["OK", "value"]}
@spec start_link(keyword()) :: GenServer.on_start()