Current section
Files
Jump to
Current section
Files
README.md
# RouletteScalable PubSub client library which uses HashRing-ed gnatsd-cluster## InstallationIf [available in Hex](https://hex.pm/docs/publish), the package can be installedby adding `roulette` to your list of dependencies in `mix.exs`:```elixirdef deps do [ {:roulette, "~> 1.0.4"} ]end```Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)and published on [HexDocs](https://hexdocs.pm). Once published, the docs canbe found at [https://hexdocs.pm/roulette](https://hexdocs.pm/roulette).## Prepare your own PubSub module```elixirdefmodule MyApp.PubSub do use Roulette, otp_app: :my_append```## ConfigurationSetup configuration like following```elixirconfig :my_app, MyApp.PubSub, servers: [ [host: "gnatsd1.example.org", port: 4222], [host: "gnatsd2.example.org", port: 4222], [host: "gnatsd3.example.org", port: 4222] ] # ...```## ApplicationAppend your PubSub module onto your application's supervisor```elixirdefmodule MyApp.Application do use Application def start(_type, _args) do children = [ {MyApp.Pubsub, []} # ... other children ] opts = [strategy: :one_for_one, name: MyApp.Supervisor] Supervisor..start_link(children, opts) endend```## Simple Usage```elixirdefmodule MyApp.Session do use GenServer def start_link(opts) do GenServer.start_link(__MODULE__, opts, name: __MODULE__) end def init(opts) do username = Keyword.fetch!(opts, :username) MyApp.PubSub.sub!(username) {:ok, %{username: username}} end def handle_info({:pubsub_message, topic, msg, pid}, state) do # handle msg {:noreply, state} end def terminate(reason, state) do :ok endend```Anywhere else you want to publish message in your app.```elixirMyApp.PubSub.pub!("foobar", data)```## Premised gnatsd Network Architecturegnatsd supports cluster-mode. This works with full-mesh and one-hop messaging system to sync events.[gnatsd's full-mesh architecture](https://github.com/nats-io/gnatsd#full-mesh-required)Roulette assumes that you put a load-balancer like AWS-NBL in front of each gnatsd-clusters.Roulette doesn't have a responsiblity for health-check and load-balancing between gnatsd-serversexists in a single gnatsd-cluster.Roulette assumes that It's a load-balancers' responsibility.Roulette connects to each gnatsd-server through load-balancers,and doesn't mind which endpoint it connects to.However if your application servers send `PUBLISH` so much,it'll cause troubles eventuallly.Roulette resolves this problem with `Consistent Hashing`.Setup multiple gnatsd-cluster beforehand, and when your app sends`PUBLISH` or `SUBSCRIBE` message,"Which cluster your app sends message to" is decided by the `topic`.## Full Configuration DescriptionHere is a minimum configuration example,You must setup `servers` list.Put your load-balancers' hostname into it.```elixirconfig :my_app, MyApp.PubSub, servers: [ "gnatsd-cluster1.example.org", "gnatsd-cluster2.example.org" ]```Or else, you can use keyword list for each host.```elixirconfig :my_app, MyApp.PubSub, servers: [ [host: "gnatsd-cluster1.example.org", port: 4222], [host: "gnatsd-cluster2.example.org", port: 4222] ]```If there is no `port` setting, 4222 is set by defaut.|key|default|description||:--|:--|:--||role|:both|You can choose **:subscriber**, **:publisher**, or **:both**||servers|required|servers list used as hash-ring||pool_size|5|number of connections for each gnatsd-cluster||ping_interval|5_000|sends PING message to gnatsd with this interval (milliseconds)||max_ping_failure|2|if PONG doesn't return while this number of PING sends, Roulette disconnects the connection.||max_retry|10|When it fails to send PUBLISH or SUBSCRIBE messages, it automatically retries until count of failure reaches to this number||max_backoff|5_000|max duration(milliseconds) used to calculate backoff period||base_backoff|10|base number used to calculate backoff period||show_debug_log|false|if this is true, Roulette dumps many debug logs.||subscription_restart|**:temporary**|You can choose **:temporary** or **:permanent**|### role- :both (default) - setup both `Publisher` and `Subscriber` connections- :subscriber - setup `Subscriber` connections only- :publisher - setup `Publisher` connections only### subscription_restart#### :temporarysubscription-process sends EXIT message to consumer process when gnatsd-connection is disconnected.#### :permanentsubscription-process try to keep subscription.when gnatsd-connection is disconnected, retry to sends SUBSCRIBE message through other connections.## Detailed Usage### Publishok/error style.```elixirtopic = "foobar"case MyApp.PubSub.pub(topic, data) do :ok -> :ok :error -> :errorend```If you don't mind error handling(not recommended on production),you can use `pub!/2` instead```elixirtopic = "foobar"MyApp.PubSub.pub!(topic, data)```### Subscribeok/error style.`sub/1` returns Supervisor.on_start()```elixirtopic = "foobar"case MyApp.PubSub.sub("foobar") do {:ok, _pid} -> :ok other -> Logger.warn "failed to sub: #{inspect other}" :errorend```If you don't mind error handling(not recommended on production),you can use `sub!/1` instead```elixirMyApp.PubSub.sub!(topic)```### Unsubscribeok/error style.`sub/1` returns Supervisor.on_start()```elixirtopic = "foobar"case MyApp.PubSub.unsub("foobar") do :ok -> :ok {:error, :not_found} -> :okend```If you don't mind error handling(not recommended on production),you can use `unsub!/1` instead```elixirMyApp.PubSub.unsub!(topic)```In following example, you don't need to call `unsub/1` on `terminate/2`.Because unsub is automatically handled, the process which calls `sub` terminates.```elixirdefmodule MyApp.Session do use GenServer def start_link(opts) do GenServer.start_link(__MODULE__, opts, name: __MODULE__) end def init(opts) do username = Keyword.fetch!(opts, :username) MyApp.PubSub.sub!(username) {:ok, %{username: username}} end def handle_info({:pubsub_message, topic, msg, pid}, state) do # handle msg {:noreply, state} end def terminate(reason, state) do # You don't need this line # MyApp.PubSub.unsub(state.username) :ok endend```## LICENSEMIT-LICENSE## AuthorLyo Kaot <lyo.kato __at__ gmail.com>