Current section
12 Versions
Jump to
Current section
12 Versions
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 | [](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…