Packages

A wrapper for redix to support cluster mode of redis

Current section

Files

Jump to
redix_cluster lib redix_cluster.ex
Raw

lib/redix_cluster.ex

defmodule RedixCluster do
@moduledoc """
This module provides the main API to interface with Redis Cluster by Redix.
## Overview
Module.Func | Description
-------------------------- | -----------------------------------
RedixCluster.start/2 | start redixcluster application
RedixCluster.command/2 | RedixCluster.command(~w(SET mykey foo))
RedixCluster.pipeline/2 | RedixCluster.pipeline([~w(SET mykey foo)])
RedixCluster.transaction/2 | RedixCluster.transaction([~w(SET mykey foo)])
## Require
Make Sure CROSSSLOT `Keys` in request hash to the `same slot`
## Examples
`Same hash keys`
iex> ~w(mget {user}123 {user}234 {user}456)
`Diff hash keys`
iex> ~w(mget {user1}123 {user2}234 {user3}456)
["mget", "{user1}123", "{user2}234", "{user3}456"]
Make sure all char is {} is the same
"""
use Application
@type command :: [binary]
@max_retry 20
@redis_retry_delay 100
@doc """
Starts RedixCluster Application by config.exs
"""
@spec start(atom, :permanent | :transient | :temporary) :: Supervisor.on_start
def start(_type, _args), do: RedixCluster.Supervisor.start_link
@doc """
`Make sure` CROSSSLOT Keys in request hash to the same slot
This function works exactly like `Redix.command/3`
## Examples
iex> RedixCluster.command(~w(SET mykey foo))
{:ok, "OK"}
iex> RedixCluster.command(~w(GET mykey))
{:ok, "foo"}
iex> RedixCluster.command(~w(INCR mykey zhongwen))
{:error,
%Redix.Error{message: "ERR wrong number of arguments for 'incr' command"}}
iex> RedixCluster.command(~w(mget ym d ))
{:error,
%Redix.Error{message: "CROSSSLOT Keys in request don't hash to the same slot"}}
iex> RedixCluster.command(~w(mset {keysamehash}ym 1 {keysamehash}d 2 ))
{:ok, "OK"}
iex> RedixCluster.command(~w(mget {keysamehash}ym {keysamehash}d ))
{:ok, ["1", "2"]}
"""
@spec command(String.t, Keyword.t) ::
{:ok, Redix.Protocol.redis_value} |
{:error, Redix.Error.t | atom}
def command(command, opts \\[]), do: command(command, opts, 0)
@doc """
This function works exactly like `RedixCluster.command/2` but:
the error will be raised
## Examples
iex> RedixCluster.command!(~w(SET mykey foo))
"OK"
iex> RedixCluster.command!(~w(INCR mykey))
** (Redix.Error) ERR value is not an integer or out of range
(redix_cluster) lib/redix_cluster.ex:40: RedixCluster.command!/2
"""
@spec command!(String.t, Keyword.t) :: Redix.Protocol.redis_value
def command!(command, opts \\[]) do
command(command, opts)
|> parse_error
end
@doc """
`Make sure` CROSSSLOT Keys in request hash to the same slot
This function works exactly like `Redix.pipeline/3`
## Examples
iex> RedixCluster.pipeline([~w(INCR mykey), ~w(INCR mykey), ~w(DECR mykey)])
{:ok, [1, 2, 1]}
iex> RedixCluster.pipeline([~w(SET {samehash}k3 foo), ~w(INCR {samehash}k2), ~w(GET {samehash}k1)])
{:ok, ["OK", 1, nil]}
iex> RedixCluster.pipeline([~w(SET {diffhash3}k3 foo), ~w(INCR {diffhash2}k2), ~w(GET {diffhash1}k1)])
{:error, :key_must_same_slot}
"""
@spec pipeline([command], Keyword.t) ::
{:ok, [Redix.Protocol.redis_value]} |
{:error, atom}
def pipeline(commands, opts\\ []), do: pipeline(commands, opts, 0)
@doc """
`Make sure` CROSSSLOT Keys in request hash to the same slot
This function works exactly like `RedixCluster.pipeline/2` but
the error will be raised
## Examples
iex> RedixCluster.pipeline!([~w(INCR mykey), ~w(INCR mykey), ~w(DECR mykey)])
{:ok, [1, 2, 1]}
iex> RedixCluster.pipeline!([~w(SET {samehash}k3 foo), ~w(INCR {samehash}k2), ~w(GET {samehash}k1)])
{:ok, ["OK", 1, nil]}
iex> RedixCluster.pipeline!([~w(SET {diffhash3}k3 foo), ~w(INCR {diffhash2}k2), ~w(GET {diffhash1}k1)])
** (RedixCluster.Error) CROSSSLOT Keys in request don't hash to the same slot
(redix_cluster) lib/redix_cluster.ex:215: RedixCluster.parse_error/1
"""
@spec pipeline!([command], Keyword.t) :: [Redix.Protocol.redis_value]
def pipeline!(commands, opts\\ []) do
pipeline(commands, opts)
|> parse_error
end
@doc """
`Make sure` CROSSSLOT Keys in request hash to the same slot
## Examples
iex> RedixCluster.transaction([~w(set mykey 1), ~w(INCR mykey), ~w(INCR mykey), ~w(DECR mykey)])
{:ok, ["OK", "QUEUED", "QUEUED", "QUEUED", "QUEUED", ["OK", 2, 3, 2]]}
iex> RedixCluster.transaction([~w(SET {samehash}k3 foo), ~w(INCR {samehash}k2), ~w(GET {samehash}k1)])
{:ok, ["OK", "QUEUED", "QUEUED", "QUEUED", ["OK", 2, nil]]}
"""
@spec transaction([command], Keyword.t) ::
{:ok, [Redix.Protocol.redis_value]} | {:error, term}
def transaction(commands, opts\\ []), do: transaction(commands, opts, 0)
def flushdb(), do: RedixCluster.Run.flushdb()
@doc """
`Make sure` CROSSSLOT Keys in request hash to the same slot
## Examples
iex> RedixCluster.transaction!([~w(set mykey 1), ~w(INCR mykey), ~w(INCR mykey), ~w(DECR mykey)])
{:ok, ["OK", "QUEUED", "QUEUED", "QUEUED", "QUEUED", ["OK", 2, 3, 2]]}
iex> RedixCluster.transaction!([~w(SET {samehash}k3 foo), ~w(INCR {samehash}k2), ~w(GET {samehash}k1)])
{:ok, ["OK", "QUEUED", "QUEUED", "QUEUED", ["OK", 2, nil]]}
iex> RedixCluster.transaction!([~w(SET {diffhash3}k3 foo), ~w(INCR {diffhash2}k2), ~w(GET {diffhash1}k1)])
** (RedixCluster.Error) CROSSSLOT Keys in request don't hash to the same slot
(redix_cluster) lib/redix_cluster.ex:215: RedixCluster.parse_error/1
"""
@spec transaction!([command], Keyword.t) :: [Redix.Protocol.redis_value]
def transaction!(commands, opts\\ []) do
transaction(commands, opts)
|> parse_error
end
# whenever the application is updated.
def config_change(_changed, _new, _removed), do: :ok
defp command(_command, _opts, count) when count >= @max_retry, do: {:error, :no_connection}
defp command(command, opts, count) do
unless count==0, do: :timer.sleep(@redis_retry_delay)
RedixCluster.Run.command(command, opts)
|> need_retry(command, opts, count, :command)
end
defp pipeline(_commands, _opts, count) when count >= @max_retry, do: {:error, :no_connection}
defp pipeline(commands, opts, count) do
unless count==0, do: :timer.sleep(@redis_retry_delay)
RedixCluster.Run.pipeline(commands, opts)
|> need_retry(commands, opts, count, :pipeline)
end
defp transaction(_commands, _opts, count) when count >= @max_retry, do: {:error, :no_connection}
defp transaction(commands, opts, count) do
unless count==0, do: :timer.sleep(@redis_retry_delay)
RedixCluster.Run.transaction(commands, opts)
|> need_retry(commands, opts, count, :transaction)
end
defp need_retry({:error, :retry}, command, opts, count, :command), do: command(command, opts, count+1)
defp need_retry({:error, :retry}, commands, opts, count, :pipeline), do: pipeline(commands, opts, count+1)
defp need_retry({:error, :retry}, commands, opts, count, :transaction), do: transaction(commands, opts, count+1)
defp need_retry(result, _command, _count, _opts, _type), do: result
defp parse_error({:ok, result}), do: result
defp parse_error({:error, %Redix.Error{} = error}), do: raise error
defp parse_error({:error, reason}), do: raise RedixCluster.Error, reason
end