roulette v1.0.4 Roulette
Scalable PubSub client library which uses HashRing-ed gnatsd-cluster
Prepare your own PubSub module
defmodule MyApp.PubSub do
use Roulette, otp_app: :my_app
end
Configuration
Setup configuration like following
config :my_app, MyApp.PubSub,
servers: [
[host: "gnatsd1.example.org", port: 4222],
[host: "gnatsd2.example.org", port: 4222],
[host: "gnatsd3.example.org", port: 4222]
]
# ...
Application
Append your PubSub module onto your application’s supervisor
defmodule 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)
end
end
Simple Usage
defmodule 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
end
end
Anywhere else you want to publish message in your app.
MyApp.PubSub.pub!("foobar", data)
Premised gnatsd Network Architecture
gnatsd supports cluster-mode. This works with full-mesh and one-hop messaging system to sync events.
gnatsd’s full-mesh architecture
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-servers exists 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
.
Detailed Usage
Publish
ok/error style.
topic = "foobar"
case MyApp.PubSub.pub(topic, data) do
:ok -> :ok
:error -> :error
end
If you don’t mind error handling(not recommended on production),
you can use pub!/2
instead
topic = "foobar"
MyApp.PubSub.pub!(topic, data)
Subscribe
ok/error style.
sub/1
returns Supervisor.on_start()
topic = "foobar"
case MyApp.PubSub.sub("foobar") do
{:ok, _pid} -> :ok
other ->
Logger.warn "failed to sub: #{inspect other}"
:error
end
If you don’t mind error handling(not recommended on production),
you can use sub!/1
instead
MyApp.PubSub.sub!(topic)
Unsubscribe
ok/error style.
sub/1
returns Supervisor.on_start()
topic = "foobar"
case MyApp.PubSub.unsub("foobar") do
:ok -> :ok
{:error, :not_found} -> :ok
end
If you don’t mind error handling(not recommended on production),
you can use unsub!/1
instead
MyApp.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.
defmodule 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
end
end