Skip to content

Commit 87d1883

Browse files
committed
Merge pull request mDialog#46 from damienlevin/sentinel_wip
Added Sentinel support
2 parents d8e0e66 + 06a5e90 commit 87d1883

24 files changed

+2835
-538
lines changed

.travis.yml

+8-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@ language: scala
22
scala:
33
- "2.10.4"
44
- "2.11.0"
5-
services:
6-
- redis-server
75
cache:
86
directories:
97
- $HOME/.ivy2
8+
before_script:
9+
- sudo redis-server `pwd`/test-config/sentinel.conf --sentinel &
10+
- sudo redis-server `pwd`/test-config/redis.conf --loglevel verbose
11+
- sudo mkdir /var/lib/redis-slave
12+
- sudo redis-server `pwd`/test-config/redis-slave.conf --loglevel verbose
13+
- cat /var/log/redis/redis-slave-server.log
14+
- cat /var/log/redis/redis-server.log
15+
- sleep 5

README.md

+86-22
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,23 @@ In your build.sbt
1111

1212
resolvers += "http://chrisdinn.github.io/releases/"
1313

14-
libraryDependencies += "com.digital-achiever" %% "brando" % "2.1.2"
14+
libraryDependencies += "com.digital-achiever" %% "brando" % "3.0.0-SNAPSHOT"
1515

1616
### Getting started
1717

1818
Brando is a lightweight wrapper around the [Redis protocol](http://redis.io/topics/protocol).
1919

20-
Create a Brando actor with your server host and port.
20+
Create a Redis actor with your server host and port.
2121

2222
import brando._
2323

24-
val redis = system.actorOf(Brando("localhost", 6379))
24+
val redis = system.actorOf(Redis("localhost", 6379))
2525

2626
You should specify a database and password if you intend to use them.
2727

28-
val redis = system.actorOf(Brando("localhost", 6379, database = Some(5), auth = Some("password")))
28+
val redis = system.actorOf(Redis("localhost", 6379, database = 5, auth = "password"))
2929

30-
This is important; if your Brando actor restarts you want be sure it reconnects successfully and to the same database.
30+
This is important; if your Redis actor restarts you want be sure it reconnects successfully and to the same database.
3131

3232
Next, send it a command and get your response as a reply.
3333

@@ -47,7 +47,7 @@ Error replies are returned as `akka.actor.Status.Failure` objects containing an
4747

4848
redis ! Request("EXPIRE", "1", "key")
4949
50-
// Response: Failure(brando.BrandoException: ERR value is not an integer or out of range)
50+
// Response: Failure(brando.RedisException: ERR value is not an integer or out of range)
5151

5252
Integer replies are returned as `Option[Long]`.
5353

@@ -75,7 +75,7 @@ NULL replies are returned as `None` and may appear either on their own or nested
7575

7676
If you're not sure what to expect in response to a request, please refer to the Redis command documentation at [http://redis.io/commands](http://redis.io/commands) where the reply type for each is clearly stated.
7777

78-
To ensure that a list of requests are executed back to back, the brando actor can receive the following message :
78+
To ensure that a list of requests are executed back to back, the Redis actor can receive the following message :
7979

8080
redis ! Batch(Request("MULTI"), Request("SET", "mykey", "somevalue"), Request("GET", "mykey"), Request("EXEC"))
8181

@@ -106,28 +106,65 @@ Use the provided response extractors to map your Redis reply to a more appropria
106106
107107
### Monitoring Connection State Changes
108108

109-
If a set of listeners is provided to the Brando actor when it is created , it will inform the those listeners about state changes to the underlying Redis connection. For example (from inside an actor):
109+
If a set of listeners is provided to the Redis actor when it is created , it will inform the those listeners about state changes to the underlying Redis connection. For example (from inside an actor):
110110

111-
val redis = context.actorOf(Brando("localhost", 6379, listeners = Set(self)))
111+
val redis = context.actorOf(Redis("localhost", 6379, listeners = Set(self)))
112112

113113
Currently, the possible messages sent to each listener include the following:
114114

115+
* `Connecting`: When creating a TCP connection.
115116
* `Connected`: When a TCP connection has been created, and Authentication (if applicable) has succeeded.
116-
* `Disconnected`: The connection has been lost. Brando transparently handles disconnects and will automatically reconnect, so typically no user action at all is needed here. During the time that Brando is disconnected, Redis commands sent to Brando will be queued, and will be processed when a connection is established.
117+
* `Disconnected`: The connection has been lost. Redis transparently handles disconnects and will automatically reconnect, so typically no user action at all is needed here. During the time that Redis is disconnected, Redis commands sent will be queued be processed once the connection is reestablished.
117118
* `AuthenticationFailed`: The TCP connected was made, but Redis auth failed.
118-
* `ConnectionFailed`: A connection could not be (re-) established after three attempts. Brando will not attempt to recover from this state; the user should take action.
119+
* `ConnectionFailed`: A connection could not be established after the number of attempts defined during creation `connectionRetryAttempts`. Brando will not attempt to recover from this state; the user should take action.
119120

120-
All these messages inherit from the `BrandoStateChange` trait.
121+
All these messages inherit from the `Connection.StateChange` trait.
122+
123+
124+
### Sentinel
125+
126+
#### Sentinel Client
127+
128+
Sentinel provides support for `monitoring`, `notification` and `automatic failover` using [sentinel](http://redis.io/topics/sentinel). It is implemented based on the following [guidelines](http://redis.io/topics/sentinel-clients) and requires redis 2.8.12 or later.
129+
130+
A sentinel client can be created like this. Here, we are using two servers and we provide a listener to receive `Connection.StateChange` events.
131+
132+
val sentinel = system.actorOf(Sentinel(Seq(
133+
Server("localhost", 26380),
134+
Server("localhost", 26379)), Set(probe.ref)))
135+
136+
You can listen for events using the following:
137+
138+
sentinel ! Request("SENTINEL","SUBSCRIBE", "failover-end")
139+
140+
You can also send commands such as
141+
142+
sentinel ! Request("SENTINEL", "MASTERS")
143+
144+
145+
#### Redis with Sentinel
146+
147+
Redis can be used with Sentinel to provide automatic failover and discovery. To do so you need to create a `Sentinel` and a `RedisSentinel` actor. In this example we are connecting to the master `mymaster`
148+
149+
val sentinel = system.actorOf(Sentinel(Seq(
150+
Server("localhost", 26380),
151+
Server("localhost", 26379))))
152+
153+
val redis = system.actorOf(RedisSentinel("mymaster", sentinel))
154+
155+
redis ! Request("PING")
156+
157+
For reliability we encourage to pass `connectionHeartbeatDelay` when using RedisSentinel, this will generate a heartbeat to Redis and will improve failures detections in the case of network partitions.
121158

122159
### Sharding
123160

124161
Brando provides support for sharding, as outlined [in the Redis documentation](http://redis.io/topics/partitioning) and in [this blog post from antirez](http://oldblog.antirez.com/post/redis-presharding.html).
125162

126-
To use it, simply create an instance of `ShardManager`, passing it a list of Redis shards you'd like it to connect to. From there, we create a pool of `Brando` instances - one for each shard.
163+
To use it, simply create an instance of `ShardManager`, passing it a list of Redis shards you'd like it to connect to. From there, we create a pool of `Redis` instances - one for each shard.
127164

128-
val shards = Seq(Shard("redis1", "10.0.0.1", 6379),
129-
Shard("redis2", "10.0.0.2", 6379),
130-
Shard("redis3", "10.0.0.3", 6379))
165+
val shards = Seq(RedisShard("redis1", "10.0.0.1", 6379),
166+
RedisShard("redis2", "10.0.0.2", 6379),
167+
RedisShard("redis3", "10.0.0.3", 6379))
131168

132169
val shardManager = context.actorOf(ShardManager(shards))
133170

@@ -147,19 +184,46 @@ Note that the `ShardManager` explicitly requires a key for all operations except
147184

148185
Individual shards can have their configuration updated on the fly. To do this, send a `Shard` message to `ShardManager`.
149186

150-
shardManager ! Shard("redis1", "10.0.0.4", 6379)
187+
shardManager ! RedisShard("redis1", "10.0.0.4", 6379)
188+
189+
190+
val shardManager = context.actorOf(ShardManager(shards, listeners = Set(self)))
191+
192+
The `ShardManager` will forward all `Connection.StateChange` messages when a shard changes state.
193+
194+
195+
#### Sharding with sentinel
196+
197+
It's possible to use sharding with Sentinel, to do so you need to use `SentinelShard` instead of `RedisShard`
198+
199+
val shards = Seq(
200+
SentinelShard("mymaster1"),
201+
SentinelShard("mymaster2"))
202+
203+
val sentinel = system.actorOf(Sentinel()) //defaults host and port are localhost:26379
204+
205+
val shardManager = context.actorOf(ShardManager(shards,sentinel))
206+
207+
## Run the tests
208+
209+
* Start sentinel
210+
211+
sudo redis-sentinel redis-config/sentinel.conf --sentinel
212+
213+
* Start a Redis master and slave
151214

152-
This is intended to support failover via [Redis Sentinel](http://redis.io/topics/sentinel). Note that the id of the shard __MUST__ match one of the original shards configured when the `ShardManager` instance was created. Adding new shards is not supported.
215+
sudo redis-server test-config/redis.conf --loglevel verbose
216+
sudo mkdir /var/lib/redis-slave
217+
sudo redis-server test-config/redis-slave.conf --loglevel verbose
153218

154-
State changes such as disconnects and connection failures can be monitored by providing a set of listeners to the `ShardManager`:
219+
* Run the tests
155220

156-
val shardManager = context.actorOf(ShardManager(shards, listeners = Set(self)))
221+
sbt test
157222

158-
The `ShardManager` will send a `ShardStateChange(shard, state)` message when a shard changes state; here `shard` is a shard object indicating which shard has changed state, and `state` is a `BrandoStateChange` object, documented above, indicating which new state the shard has entered.
159223

160224
## Documentation
161225

162-
Read the API documentation here: [http://chrisdinn.github.io/api/brando-2.1.2/](http://chrisdinn.github.io/api/brando-2.1.2/)
226+
Read the API documentation here: [http://chrisdinn.github.io/api/brando-3.0.0-SNAPSHOT/](http://chrisdinn.github.io/api/brando-3.0.0-SNAPSHOT/)
163227

164228
## Mailing list
165229

build.sbt

+5-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name := "brando"
22

33
organization := "com.digital-achiever"
44

5-
version := "2.1.2"
5+
version := "3.0.0-SNAPSHOT"
66

77
scalaVersion := "2.11.4"
88

@@ -11,11 +11,13 @@ crossScalaVersions := Seq("2.10.4", "2.11.4")
1111
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
1212

1313
libraryDependencies ++= Seq(
14-
"com.typesafe.akka" %% "akka-actor" % "2.3.4",
14+
"com.typesafe.akka" %% "akka-actor" % "2.3.9",
1515
"org.scalatest" %% "scalatest" % "2.1.3" % "test",
16-
"com.typesafe.akka" %% "akka-testkit" % "2.3.4" % "test"
16+
"com.typesafe.akka" %% "akka-testkit" % "2.3.9" % "test"
1717
)
1818

19+
parallelExecution in Test := false
20+
1921
resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"
2022

2123
publishTo <<= version { (v: String) =>

src/main/resources/reference.conf

+7-8
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
brando {
2-
# Default connection retry delay on disconnect
3-
connection_retry = 2s
1+
brando.connection{
2+
timeout = 2s
43

5-
#Max number of times to attempt connection before failing
6-
#connection_attempts = 3
7-
}
4+
#Delay before trying to reconnect
5+
retry.delay = 1 s
86

9-
#Redis connection/authentication timeout
10-
redis.timeout = 2s
7+
#Number of connect attempts before failure
8+
retry.attempts = 3
9+
}

0 commit comments

Comments
 (0)