Current section

12 Versions

Jump to

Compare versions

11 files changed
+243 additions
-122 deletions
  @@ -1,5 +1,10 @@
1 1 # Change Log
2 2
3 + ## v3.1.0
4 +
5 + * More resilient to ElastiCache errors: node removal/addition.
6 + * Multiple lib upgrades.
7 +
3 8 ## v3.0.0
4 9
5 10 * Clusters working with libring.
  @@ -2,14 +2,14 @@
2 2
3 3 [![Build Status](https://secure.travis-ci.org/peillis/memcachir.png)](http://travis-ci.org/peillis/memcachir)
4 4
5 - Memcached client for Elixir
5 + Memcached client for Elixir. It supports clusters and AWS Elasticache.
6 6
7 7 ## Installation
8 8
9 9 ```elixir
10 10 defp deps() do
11 11 ...
12 - {:memcachir, "~> 3.0"},
12 + {:memcachir, "~> 3.1"},
13 13 ...
14 14 end
15 15
  @@ -31,6 +31,13 @@ hosts: ["host1", "host2", "host3:11212"] # cluster of servers
31 31 hosts: [{"host1", 10}, {"host2", 30}] # cluster with weights
32 32 ```
33 33
34 + Alternatively you can use the elasticache config option:
35 +
36 + ```elixir
37 + config :memcachir,
38 + elasticache: "your-config-endpoint.cache.amazonaws.com"
39 + ```
40 +
34 41 ## Configuration
35 42
36 43 Complete configuration options with default values:
  @@ -2,12 +2,12 @@
2 2 {<<"build_tools">>,[<<"mix">>]}.
3 3 {<<"description">>,
4 4 <<"Memcached client, with connection pooling and cluster support.">>}.
5 - {<<"elixir">>,<<"~> 1.4">>}.
5 + {<<"elixir">>,<<"~> 1.7">>}.
6 6 {<<"files">>,
7 - [<<"lib/memcachir.ex">>,<<"lib/memcachir/pool.ex">>,
8 - <<"lib/memcachir/supervisor.ex">>,<<"lib/memcachir/util.ex">>,
9 - <<"lib/memcachir/worker.ex">>,<<"mix.exs">>,<<"README.md">>,
10 - <<"CHANGELOG.md">>]}.
7 + [<<"lib/memcachir.ex">>,<<"lib/memcachir/cluster.ex">>,
8 + <<"lib/memcachir/health.ex">>,<<"lib/memcachir/pool.ex">>,
9 + <<"lib/memcachir/supervisor.ex">>,<<"lib/memcachir/util.ex">>,<<"mix.exs">>,
10 + <<"README.md">>,<<"CHANGELOG.md">>]}.
11 11 {<<"licenses">>,[<<"MIT">>]}.
12 12 {<<"links">>,[{<<"Github">>,<<"https://github.com/peillis/memcachir">>}]}.
13 13 {<<"maintainers">>,[<<"Enrique Martinez">>]}.
  @@ -16,17 +16,21 @@
16 16 [[{<<"app">>,<<"memcachex">>},
17 17 {<<"name">>,<<"memcachex">>},
18 18 {<<"optional">>,false},
19 + {<<"repository">>,<<"hexpm">>},
19 20 {<<"requirement">>,<<"~> 0.4">>}],
20 21 [{<<"app">>,<<"poolboy">>},
21 22 {<<"name">>,<<"poolboy">>},
22 23 {<<"optional">>,false},
24 + {<<"repository">>,<<"hexpm">>},
23 25 {<<"requirement">>,<<"~> 1.5">>}],
24 26 [{<<"app">>,<<"elasticachex">>},
25 27 {<<"name">>,<<"elasticachex">>},
26 28 {<<"optional">>,false},
27 - {<<"requirement">>,<<"~> 1.0">>}],
29 + {<<"repository">>,<<"hexpm">>},
30 + {<<"requirement">>,<<"~> 1.1">>}],
28 31 [{<<"app">>,<<"libring">>},
29 32 {<<"name">>,<<"libring">>},
30 33 {<<"optional">>,false},
31 - {<<"requirement">>,<<"~> 1.0">>}]]}.
32 - {<<"version">>,<<"3.0.0">>}.
34 + {<<"repository">>,<<"hexpm">>},
35 + {<<"requirement">>,<<"~> 1.1">>}]]}.
36 + {<<"version">>,<<"3.1.0">>}.
  @@ -12,27 +12,11 @@ defmodule Memcachir do
12 12 """
13 13 use Application
14 14
15 - alias Memcachir.Util
15 + alias Memcachir.{Cluster, Supervisor, Util}
16 16
17 - @doc """
18 - Starts application.
19 - """
20 17 def start(_type, _args) do
21 - servers = get_servers()
22 -
23 - # Build the hashring
24 - {:ok, _pid} = HashRing.Managed.new(:memcachir_ring)
25 - Enum.each(servers, fn({host, port}) ->
26 - :ok = HashRing.Managed.add_node(
27 - :memcachir_ring, Util.host_to_atom(host, port))
28 - end)
29 -
30 - options =
31 - Application.get_all_env(:memcachir)
32 - |> Keyword.put(:servers, servers)
33 - pool_options = Application.get_env(:memcachir, :pool, [])
34 -
35 - Memcachir.Supervisor.start_link(options, pool_options)
18 + opts = Application.get_all_env(:memcachir)
19 + Supervisor.start_link(opts)
36 20 end
37 21
38 22 @doc """
  @@ -40,67 +24,68 @@ defmodule Memcachir do
40 24 if the given key doesn't exist.
41 25 """
42 26 def get(key, opts \\ []) do
43 - node = key_to_node(key)
44 - execute(&Memcache.get/3, node, [key, opts])
27 + case key_to_node(key) do
28 + {:ok, node} -> execute(&Memcache.get/3, node, [key, opts])
29 + {:error, reason} -> {:error, "unable to get: #{reason}"}
30 + end
45 31 end
46 32
47 33 @doc """
48 34 Sets the key to value.
49 35 """
50 36 def set(key, value, opts \\ []) do
51 - node = key_to_node(key)
52 - execute(&Memcache.set/4, node, [key, value, opts])
37 + case key_to_node(key) do
38 + {:ok, node} -> execute(&Memcache.set/4, node, [key, value, opts])
39 + {:error, reason} -> {:error, "unable to set: #{reason}"}
40 + end
53 41 end
54 42
55 43 @doc """
56 44 Removes the item with the specified key. Returns `{:ok, :deleted}`
57 45 """
58 46 def delete(key) do
59 - node = key_to_node(key)
60 - execute(&Memcache.delete/2, node, [key])
47 + case key_to_node(key) do
48 + {:ok, node} -> execute(&Memcache.delete/2, node, [key])
49 + {:error, reason} -> {:error, "unable to delete: #{reason}"}
50 + end
61 51 end
62 52
63 53 @doc """
64 54 Removes all the items from the server. Returns `{:ok}`.
65 55 """
66 56 def flush(opts \\ []) do
67 - nodes = servers_atoms()
68 - execute(&Memcache.flush/2, nodes, [opts])
57 + execute(&Memcache.flush/2, list_nodes(), [opts])
69 58 end
70 59
71 - def execute(fun, nodes, args \\ [])
72 - def execute(fun, [node | nodes], args) do
60 + @doc """
61 + List all currently registered node names, like `[:"localhost:11211"]`.
62 + """
63 + def list_nodes() do
64 + Cluster.servers() |> Enum.map(&Util.to_server_id(&1))
65 + end
66 +
67 + defp execute(_fun, [], _args) do
68 + {:error, "unable to flush: no_nodes"}
69 + end
70 +
71 + defp execute(fun, [node | nodes], args) do
73 72 if length(nodes) > 0 do
74 73 execute(fun, nodes, args)
75 74 end
75 +
76 76 execute(fun, node, args)
77 77 end
78 - def execute(fun, node, args) do
79 - :poolboy.transaction(node, fn(worker) ->
78 +
79 + defp execute(fun, node, args) do
80 + :poolboy.transaction(node, fn worker ->
80 81 apply(fun, [worker | args])
81 82 end)
82 83 end
83 84
84 85 defp key_to_node(key) do
85 - HashRing.Managed.key_to_node(:memcachir_ring, key)
86 - end
87 -
88 - # Returns a list like [:localhost_11211, :localhost_11212]
89 - defp servers_atoms() do
90 - Enum.map(get_servers(), fn({host, port}) ->
91 - Util.host_to_atom(host, port)
92 - end)
93 - end
94 -
95 - # Returns a list like [{host1, port1}, {host2, port2}, ...]
96 - # from the configured hosts parameter or reading it from elasticache
97 - defp get_servers() do
98 - case Application.get_env(:memcachir, :elasticache) do
99 - nil ->
100 - Util.read_config_hosts(Application.get_env(:memcachir, :hosts))
101 - elasticache ->
102 - Util.read_config_elasticache(elasticache)
86 + case Cluster.key_to_node(key) do
87 + {:error, {:invalid_ring, reason}} -> {:error, reason}
88 + node -> {:ok, Util.to_server_id(node)}
103 89 end
104 90 end
105 -
106 91 end
  @@ -0,0 +1,39 @@
1 + defmodule Memcachir.Cluster do
2 + use GenServer
3 + require Logger
4 +
5 + alias Memcachir.Util
6 +
7 + def start_link(options) do
8 + GenServer.start_link(__MODULE__, options, name: __MODULE__)
9 + end
10 +
11 + def init(options) do
12 + servers = Util.get_servers(options)
13 + Logger.info("starting cluster with servers: #{inspect(servers)}")
14 +
15 + ring =
16 + servers
17 + |> Enum.reduce(HashRing.new(), fn {host, port}, ring ->
18 + HashRing.add_node(ring, {host, port})
19 + end)
20 +
21 + {:ok, ring}
22 + end
23 +
24 + def servers() do
25 + GenServer.call(__MODULE__, :servers)
26 + end
27 +
28 + def key_to_node(key) do
29 + GenServer.call(__MODULE__, {:node, key})
30 + end
31 +
32 + def handle_call(:servers, _from, ring) do
33 + {:reply, HashRing.nodes(ring), ring}
34 + end
35 +
36 + def handle_call({:node, key}, _from, ring) do
37 + {:reply, HashRing.key_to_node(ring, key), ring}
38 + end
39 + end
Loading more files…