├── .gitignore ├── .travis.yml ├── README.md ├── lib ├── redis_pool.ex └── redis_pool │ └── supervisor.ex ├── mix.exs ├── mix.lock └── test ├── redis_pool_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | /doc 4 | erl_crash.dump 5 | *.ez 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | services: 3 | - redis 4 | elixir: 5 | - 1.4.2 6 | - 1.3.4 7 | - 1.2.6 8 | otp_release: 9 | - 19.2 10 | - 18.2.1 11 | sudo: false 12 | notifications: 13 | email: false 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RedisPool for Elixir 2 | [![Build Status](https://travis-ci.org/le0pard/redis_pool.png?branch=master)](https://travis-ci.org/le0pard/redis_pool) 3 | 4 | This is redis pool for [Elixir](http://elixir-lang.org/). Build on top of [eredis](https://github.com/wooga/eredis) and [poolboy](https://github.com/devinus/poolboy). 5 | 6 | ## Examples 7 | 8 | Application start: 9 | 10 | ``` 11 | RedisPool.start 12 | ``` 13 | or add it in `application` section in mix: 14 | 15 | ``` 16 | def application do 17 | [mod: {Example, []}, 18 | applications: [ :redis_pool ] ] 19 | end 20 | ``` 21 | 22 | ### Create pools 23 | 24 | ``` 25 | RedisPool.create_pool(:default, 10) 26 | 27 | RedisPool.create_pool(:test, 10, 'localhost', 6379) 28 | 29 | RedisPool.create_pool(:test2, 10, 'localhost', 6379, 0, 'password', 100) 30 | ``` 31 | 32 | Also you can configure redis_pool directly from configuration file to get pools automatically created at application startup. In `config/config.exs`, add : 33 | 34 | ``` 35 | config :redis_pool, :pools, [ 36 | test_pool: [size: 10], 37 | test_pool_2: [size: 10, database: 111], 38 | test_pool_3: [size: 20, host: '127.0.0.1', port: 6379, database: 'user_db', password: 'abc', reconnect_sleep: '20'] 39 | ] 40 | ``` 41 | 42 | ### Delete pools 43 | 44 | ``` 45 | RedisPool.delete_pool(:default) 46 | 47 | RedisPool.delete_pool(:test) 48 | ``` 49 | 50 | ### Usage 51 | 52 | Usage of pools: 53 | 54 | ``` 55 | RedisPool.q({:global, :default}, ["SET", "foo", "bar"]) 56 | 57 | RedisPool.q({:global, :test}, ["GET", "foo"]) 58 | ``` 59 | 60 | Method `transaction` is execute all redis command between `MULTI` and `EXEC` commands: 61 | 62 | ``` 63 | RedisPool.transaction {:global, :redis}, fn(redis) -> 64 | :eredis.q redis, ["SADD", "queues", queue] 65 | :eredis.q redis, ["LPUSH", "queue", "Test" ] 66 | end 67 | ``` 68 | 69 | ## Contributing 70 | 71 | 1. Fork it 72 | 2. Create your feature branch (`git checkout -b my-new-feature`) 73 | 3. Commit your changes (`git commit -am 'Add some feature'`) 74 | 4. Push to the branch (`git push origin my-new-feature`) 75 | 5. Create new Pull Request 76 | -------------------------------------------------------------------------------- /lib/redis_pool.ex: -------------------------------------------------------------------------------- 1 | defmodule RedisPool do 2 | use Application 3 | 4 | def start(_type, _args) do 5 | RedisPool.Supervisor.start_link() 6 | end 7 | 8 | def stop(_state) do 9 | :ok 10 | end 11 | 12 | def start do 13 | :application.start(__MODULE__) 14 | end 15 | 16 | def stop do 17 | :application.stop(__MODULE__) 18 | end 19 | 20 | def create_pool(pool_name, size) do 21 | RedisPool.Supervisor.create_pool(pool_name, size, []) 22 | end 23 | 24 | def create_pool(pool_name, size, host) do 25 | RedisPool.Supervisor.create_pool(pool_name, size, [{:host, host}]) 26 | end 27 | 28 | def create_pool(pool_name, size, host, port) do 29 | RedisPool.Supervisor.create_pool(pool_name, size, [{:host, host}, {:port, port}]) 30 | end 31 | 32 | def create_pool(pool_name, size, host, port, database) do 33 | RedisPool.Supervisor.create_pool(pool_name, size, [{:host, host}, {:port, port}, {:database, database}]) 34 | end 35 | 36 | def create_pool(pool_name, size, host, port, database, password) do 37 | RedisPool.Supervisor.create_pool(pool_name, size, 38 | [{:host, host}, {:port, port}, {:database, database}, {:password, password}]) 39 | end 40 | 41 | def create_pool(pool_name, size, host, port, database, password, reconnect_sleep) do 42 | RedisPool.Supervisor.create_pool(pool_name, size, 43 | [{:host, host}, {:port, port}, {:database, database}, {:password, password}, {:reconnect_sleep, reconnect_sleep}]) 44 | end 45 | 46 | def delete_pool(pool_name) do 47 | RedisPool.Supervisor.delete_pool(pool_name) 48 | end 49 | 50 | def q(pool_name, command) do 51 | q(pool_name, command, timeout()) 52 | end 53 | 54 | def q(pool_name, command, timeout) do 55 | pool_name = resolve_name(pool_name) 56 | :poolboy.transaction(pool_name, fn(worker) -> :eredis.q(worker, command, timeout) end) 57 | end 58 | 59 | def qp(pool_name, pipeline) do 60 | qp(pool_name, pipeline, timeout()) 61 | end 62 | 63 | def qp(pool_name, pipeline, timeout) do 64 | pool_name = resolve_name(pool_name) 65 | :poolboy.transaction(pool_name, fn(worker) -> :eredis.qp(worker, pipeline, timeout) end) 66 | end 67 | 68 | def transaction(pool_name, fun) when is_function(fun) do 69 | f = fn(c) -> 70 | try do 71 | {:ok, _} = :eredis.q(c, ["MULTI"]) 72 | fun.(c) 73 | :eredis.q(c, ["EXEC"]) 74 | rescue 75 | error -> 76 | {:ok, _} = :eredis.q(c, ["DISCARD"]) 77 | {:error, error} 78 | end 79 | end 80 | pool_name = resolve_name(pool_name) 81 | :poolboy.transaction(pool_name, f) 82 | end 83 | 84 | 85 | defp timeout do 86 | 5000 87 | end 88 | 89 | defp resolve_name(name) when is_atom(name) do 90 | case Application.get_env(:redis_pool, :global_or_local, :global) do 91 | :global -> 92 | {:global, name} 93 | :local -> 94 | name 95 | end 96 | end 97 | defp resolve_name(name), do: name 98 | 99 | end 100 | -------------------------------------------------------------------------------- /lib/redis_pool/supervisor.ex: -------------------------------------------------------------------------------- 1 | defmodule RedisPool.Supervisor do 2 | use Supervisor 3 | 4 | def start_link do 5 | pools = Application.get_env(:redis_pool, :pools, []) 6 | global_or_local = Application.get_env(:redis_pool, :global_or_local, :global) 7 | start_link(pools, global_or_local) 8 | end 9 | 10 | def start_link(pools, global_or_local) do 11 | :supervisor.start_link({:local, __MODULE__}, __MODULE__, [pools, global_or_local]) 12 | end 13 | 14 | def create_pool(pool_name, size, options) do 15 | pool_name_scope = Application.get_env(:redis_pool, :global_or_local, :global) 16 | name = case pool_name do 17 | n when is_atom(n) -> 18 | {pool_name_scope, n} 19 | n -> n 20 | end 21 | 22 | args = [ 23 | {:name, name}, 24 | {:worker_module, :eredis}, 25 | {:size, size}, 26 | {:max_overflow, 10}] 27 | pool_spec = :poolboy.child_spec(pool_name, args, options) 28 | :supervisor.start_child(__MODULE__, pool_spec) 29 | end 30 | 31 | def delete_pool(pool_name) do 32 | :supervisor.terminate_child(__MODULE__, pool_name) 33 | :supervisor.delete_child(__MODULE__, pool_name) 34 | end 35 | 36 | def init([pools, global_or_local]) do 37 | spec_fun = fn({pool_name, pool_config}) -> 38 | args = [{:name, {global_or_local, pool_name}}, {:worker_module, :eredis}] ++ pool_config 39 | :poolboy.child_spec(pool_name, args, [pool_config]) 40 | end 41 | pool_specs = Enum.map(pools, spec_fun) 42 | 43 | supervise(pool_specs, strategy: :one_for_one) 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule RedisPool.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ app: :redis_pool, 6 | version: "0.2.6", 7 | elixir: "> 1.0.0", 8 | description: description(), 9 | package: package(), 10 | build_embedded: Mix.env == :prod, 11 | start_permanent: Mix.env == :prod, 12 | deps: deps() ] 13 | end 14 | 15 | # Configuration for the OTP application 16 | def application do 17 | [ 18 | mod: {RedisPool, []}, 19 | applications: [ 20 | :kernel, 21 | :stdlib, 22 | :eredis, 23 | :poolboy 24 | ] 25 | ] 26 | end 27 | 28 | defp description do 29 | """ 30 | Redis pool for Elixir. Build on top of eredis and poolboy. 31 | """ 32 | end 33 | 34 | defp package do 35 | [ 36 | files: ["lib", "test", "mix.exs", "README.md"], 37 | maintainers: ["Alexey Vasiliev"], 38 | licenses: ["MIT"], 39 | links: %{ 40 | "GitHub" => "https://github.com/le0pard/redis_pool", 41 | "Docs" => "http://leopard.in.ua/redis_pool/" 42 | } 43 | ] 44 | end 45 | 46 | # Returns the list of dependencies in the format: 47 | # { :foobar, git: "https://github.com/elixir-lang/foobar.git", tag: "0.1" } 48 | # 49 | # To specify particular versions, regardless of the tag, do: 50 | # { :barbat, "~> 0.1", github: "elixir-lang/barbat" } 51 | defp deps do 52 | [ 53 | { :eredis, "~> 1.0" }, 54 | { :poolboy, "~> 1.5" }, 55 | { :ex_doc, ">= 0.0.0", only: :dev } 56 | ] 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"earmark": {:hex, :earmark, "1.2.0", "bf1ce17aea43ab62f6943b97bd6e3dc032ce45d4f787504e3adf738e54b42f3a", [:mix], []}, 2 | "eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []}, 3 | "ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]}, 4 | "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []}} 5 | -------------------------------------------------------------------------------- /test/redis_pool_test.exs: -------------------------------------------------------------------------------- 1 | defmodule RedisPoolTest do 2 | use ExUnit.Case 3 | alias RedisPool, as: R 4 | 5 | setup do 6 | on_exit fn -> 7 | case R.delete_pool(:default) do 8 | :ok -> :ok 9 | _ -> :error 10 | end 11 | end 12 | 13 | {:ok, _} = R.create_pool(:default, 10) 14 | case R.q {:global, :default}, ["FLUSHDB"] do 15 | {:ok, _} -> :ok 16 | _ -> :error 17 | end 18 | end 19 | 20 | test "work default pool" do 21 | assert {:ok, :undefined} == R.q {:global, :default}, ["GET", "test"] 22 | assert {:ok, "OK"} == R.q {:global, :default}, ["SET", "test", "1"] 23 | assert {:ok, "1"} == R.q {:global, :default}, ["GET", "test"] 24 | end 25 | 26 | test "work with new pool" do 27 | assert {:ok, _} = R.create_pool(:new_pool, 2) 28 | assert {:ok, :undefined} == R.q {:global, :new_pool}, ["GET", "test2"] 29 | assert {:ok, "OK"} == R.q {:global, :new_pool}, ["SET", "test2", "2"] 30 | assert {:ok, "2"} == R.q {:global, :new_pool}, ["GET", "test2"] 31 | assert :ok = R.delete_pool(:new_pool) 32 | end 33 | 34 | test "error if use invalid commands" do 35 | assert {:error, "ERR wrong number of arguments for 'set' command"} == R.q {:global, :default}, ["SET", "test2"] 36 | assert {:error, "ERR wrong number of arguments for 'get' command"} == R.q {:global, :default}, ["GET", "test2", "asdasd"] 37 | end 38 | 39 | test "transaction success" do 40 | assert {:ok, :undefined} == R.q {:global, :default}, ["GET", "test2"] 41 | assert {:ok, :undefined} == R.q {:global, :default}, ["GET", "test4.1"] 42 | R.transaction {:global, :default}, fn(redis) -> 43 | :eredis.q redis, ["SET", "test4", "1"] 44 | :eredis.q redis, ["SET", "test4.1", "2"] 45 | end 46 | assert {:ok, "1"} == R.q {:global, :default}, ["GET", "test4"] 47 | assert {:ok, "2"} == R.q {:global, :default}, ["GET", "test4.1"] 48 | end 49 | 50 | test "transaction rollback" do 51 | assert {:ok, :undefined} == R.q {:global, :default}, ["GET", "test5"] 52 | assert {:ok, :undefined} == R.q {:global, :default}, ["GET", "test5.1"] 53 | R.transaction {:global, :default}, fn(redis) -> 54 | :eredis.q redis, ["SET", "test5", "1"] # valid command 55 | :eredis.q redis, ["SET"] # invalid command 56 | end 57 | assert {:ok, :undefined} == R.q {:global, :default}, ["GET", "test5"] 58 | assert {:ok, :undefined} == R.q {:global, :default}, ["GET", "test5.1"] 59 | end 60 | 61 | test "ttl works" do 62 | assert {:ok, "OK"} == R.q {:global, :default}, ["SET", "test6", "1"] 63 | assert {:ok, "-1"} == R.q {:global, :default}, ["TTL", "test6"] 64 | end 65 | 66 | test "expire works" do 67 | R.transaction {:global, :default}, fn(redis) -> 68 | :eredis.q redis, ["SET", "test7", "1"] 69 | :eredis.q redis, ["EXPIRE", "test7", "3"] 70 | end 71 | {:ok, res} = R.q {:global, :default}, ["TTL", "test7"] 72 | assert 2 <= res 73 | end 74 | 75 | test "short pool name accepted" do 76 | Application.put_env(:redis_pool, :global_or_local, :local) 77 | {:ok, _} = R.create_pool(:local_pool, 5) 78 | assert {:ok, "OK"} == R.q :local_pool, ["SET", "test8", "2"] 79 | assert {:ok, "OK"} == R.q :local_pool, ["SET", "test8", "2"] 80 | 81 | Application.put_env(:redis_pool, :global_or_local, :global) 82 | {:ok, _} = R.create_pool(:global_pool, 5) 83 | assert {:ok, "OK"} == R.q :global_pool, ["SET", "test8", "1"] 84 | assert {:ok, "1"} == R.q {:global, :global_pool}, ["GET", "test8"] 85 | end 86 | 87 | test "use default eredis settings" do 88 | test_settings = fn(pool_settings) -> 89 | Application.stop :redis_pool 90 | Application.put_env :redis_pool, :pools, pool_settings 91 | Application.start :redis_pool 92 | {:ok, "OK"} = R.q :default, ["SET", "test9", "value"] 93 | {:ok, "value"} = R.q :default, ["GET", "test9"] 94 | end 95 | 96 | test_settings.(default: [size: 10]) 97 | test_settings.(default: [size: 10, database: 111]) 98 | test_settings.(default: [size: 10, host: '127.0.0.1', port: 6379]) 99 | test_settings.(default: [size: 10, port: 6379, database: '0']) 100 | end 101 | 102 | end 103 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start 2 | --------------------------------------------------------------------------------