-
Notifications
You must be signed in to change notification settings - Fork 0
/
JedisMasterSlavePool.java
329 lines (283 loc) · 11.3 KB
/
JedisMasterSlavePool.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
package redis.clients.jedis;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.exceptions.JedisException;
public class JedisMasterSlavePool implements Closeable {
// extends JedisPoolAbstract
private static Logger log = Logger.getLogger(JedisMasterSlavePool.class.getSimpleName());
private static final int MAX_RETRIES_GETTING_RESOURCE = 5;
protected GenericObjectPoolConfig poolConfig;
protected GenericObjectPoolConfig poolConfigSlaves;
protected int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
protected int soTimeout = Protocol.DEFAULT_TIMEOUT;
protected String password;
protected int database = Protocol.DEFAULT_DATABASE;
protected String clientName;
protected MasterListener masterListeners = null;
private volatile JedisPool masterPool;
private volatile List<JedisPool> slavesPools = new ArrayList<JedisPool>();
private volatile JedisFactory factory;
private volatile HostAndPort currentHostMaster;
private static final String ROLE_KEY = "role:";
protected boolean useSlaves = false;
protected boolean singleNode = false;
private enum Role {
master, slave
}
public JedisMasterSlavePool(String[] nodes, final GenericObjectPoolConfig poolConfig) {
this(nodes, poolConfig, Protocol.DEFAULT_TIMEOUT, null, Protocol.DEFAULT_DATABASE);
}
public JedisMasterSlavePool(String[] nodes) {
this(nodes, new GenericObjectPoolConfig(), Protocol.DEFAULT_TIMEOUT, null, Protocol.DEFAULT_DATABASE);
}
public JedisMasterSlavePool(String[] nodes, String password) {
this(nodes, new GenericObjectPoolConfig(), Protocol.DEFAULT_TIMEOUT, password);
}
public JedisMasterSlavePool(String[] nodes, final GenericObjectPoolConfig poolConfig, int timeout,
final String password) {
this(nodes, poolConfig, timeout, password, Protocol.DEFAULT_DATABASE);
}
public JedisMasterSlavePool(String[] nodes, final GenericObjectPoolConfig poolConfig, final int timeout) {
this(nodes, poolConfig, timeout, null, Protocol.DEFAULT_DATABASE);
}
public JedisMasterSlavePool(String[] nodes, final GenericObjectPoolConfig poolConfig, final String password) {
this(nodes, poolConfig, Protocol.DEFAULT_TIMEOUT, password);
}
public JedisMasterSlavePool(String[] nodes, final GenericObjectPoolConfig poolConfig, int timeout,
final String password, final int database) {
this(nodes, poolConfig, timeout, timeout, password, database);
}
public JedisMasterSlavePool(String[] nodes, final GenericObjectPoolConfig poolConfig, int timeout,
final String password, final int database, final String clientName) {
this(nodes, poolConfig, poolConfig, timeout, timeout, password, database, clientName);
}
public JedisMasterSlavePool(String[] nodes, final GenericObjectPoolConfig poolConfig, final int timeout,
final int soTimeout, final String password, final int database) {
this(nodes, poolConfig, poolConfig, timeout, soTimeout, password, database, null);
}
public JedisMasterSlavePool(String[] nodes, final GenericObjectPoolConfig poolConfig,
final GenericObjectPoolConfig poolConfigSlaves, final int timeout, final int soTimeout, final String password,
final int database) {
this(nodes, poolConfig, poolConfigSlaves, timeout, soTimeout, password, database, null);
}
public JedisMasterSlavePool(String[] nodes, final GenericObjectPoolConfig poolConfig,
final GenericObjectPoolConfig poolConfigSlaves, final int connectionTimeout, final int soTimeout,
final String password, final int database, final String clientName) {
this.poolConfig = poolConfig;
this.poolConfigSlaves = poolConfigSlaves;
this.connectionTimeout = connectionTimeout;
this.soTimeout = soTimeout;
this.password = password;
this.database = database;
this.clientName = clientName;
initElasticache(nodes);
}
@Override
public void close() throws IOException {
if (masterListeners != null)
masterListeners.shutdown();
masterPool.destroy();
for (JedisPool slave : slavesPools) {
slave.destroy();
}
}
public HostAndPort getCurrentHostMaster() {
return currentHostMaster;
}
private void initPool(HostAndPort master, List<HostAndPort> slaves) {
currentHostMaster = master;
if (factory == null) {
if (masterPool != null)
masterPool.close();
masterPool = new JedisPool(poolConfig, master.getHost(), master.getPort(), connectionTimeout, soTimeout,
password, database, clientName, false, null, null, null);
for (JedisPool slavePool : slavesPools) {
slavePool.close();
}
slavesPools.clear();
for (HostAndPort slave : slaves) {
slavesPools.add(new JedisPool(poolConfig, slave.getHost(), slave.getPort(), connectionTimeout, soTimeout,
password, database, clientName, false, null, null, null));
}
}
log.fine("Created JedisPool to master at " + master);
}
private void initElasticache(String[] nodes) {
log.fine("Trying to find master from available nodes...");
HostAndPort master = null;
List<HostAndPort> slaves = new ArrayList<HostAndPort>();
if (nodes.length == 1) {
singleNode = true;
master = toHostAndPort(Arrays.asList(nodes[0].split(":")));
} else {
for (String node : nodes) {
final HostAndPort hap = toHostAndPort(Arrays.asList(node.split(":")));
log.fine("Connecting to Redis " + hap);
Jedis jedis = null;
try {
jedis = new Jedis(hap.getHost(), hap.getPort());
if (master == null) {
Role role = determineRole(jedis.info("replication"));
if (Role.master.equals(role)) {
log.fine("Found Redis master at " + hap.toString());
master = hap;
}
} else {
useSlaves = true;
slaves.add(hap);
}
} catch (JedisException e) {
log.warning("Cannot connect to elasticache node running @ " + node + ". Reason: " + e + ". Trying next one.");
if (jedis != null)
jedis.close();
}
}
}
initPool(master, slaves);
if (currentHostMaster == null) {
throw new JedisException("Master not found in the list of provided nodes");
}
log.fine("Redis master running at " + currentHostMaster + ", starting Master listeners...");
if (!singleNode) {
Set<HostAndPort> nodesHP = new HashSet<HostAndPort>();
for (String node : nodes) {
nodesHP.add(toHostAndPort(Arrays.asList(node.split(":"))));
}
masterListeners = new MasterListener(nodesHP);
masterListeners.run();
}
}
private Role determineRole(String data) {
for (String s : data.split("\\r\\n")) {
if (s.startsWith(ROLE_KEY)) {
return Role.valueOf(s.substring(ROLE_KEY.length()));
}
}
throw new JedisException("Cannot determine node role from provided 'INFO replication' data" + data);
}
private HostAndPort toHostAndPort(List<String> node) {
String host = node.get(0);
int port = Integer.parseInt(node.get(1));
return new HostAndPort(host, port);
}
public Jedis getResource() {
int retries = 0;
while (retries++ < MAX_RETRIES_GETTING_RESOURCE) {
Jedis jedis = masterPool.getResource();
jedis.setDataSource(masterPool);
// get a reference because it can change concurrently
final HostAndPort master = currentHostMaster;
final HostAndPort connection = new HostAndPort(jedis.getClient().getHost(), jedis.getClient().getPort());
if (master.equals(connection)) {
// connected to the correct master
return jedis;
}
}
throw new JedisException("Failed to get a resource from the master");
}
/**
* Return a slave redis, may return the master if called during the election of a new master
*
* @return
*/
public Jedis getReadOnlyResource() {
if (useSlaves) {
int retries = 0;
while (retries++ < MAX_RETRIES_GETTING_RESOURCE) {
// RoundRobin to get Slave resource
JedisPool slaveRoudRobin = getSlaveRoudRobin(slavesPools);
Jedis jedis = slaveRoudRobin.getResource();
jedis.setDataSource(slaveRoudRobin);
// get a reference because it can change concurrently
final HostAndPort master = currentHostMaster;
final HostAndPort connection = new HostAndPort(jedis.getClient().getHost(), jedis.getClient().getPort());
if (!master.equals(connection)) {
// connected to the correct master
return jedis;
}
}
throw new JedisException("Failed to get a resource from the slaves");
} else {
return getResource();
}
}
private final AtomicInteger index = new AtomicInteger(-1);
public JedisPool getSlaveRoudRobin(List<JedisPool> slaves) {
int ind = Math.abs(index.incrementAndGet() % slaves.size());
return slaves.get(ind);
}
protected class MasterListener extends Thread {
protected String masterName = "";
protected Set<HostAndPort> nodes;
protected long waitBetweenScan = 1000;
protected volatile Jedis j;
protected AtomicBoolean running = new AtomicBoolean(false);
protected MasterListener() {
}
public MasterListener(Set<HostAndPort> nodes) {
this.nodes = nodes;
}
public MasterListener(Set<HostAndPort> nodes, long subscribeRetryWaitTimeMillis) {
this(nodes);
this.waitBetweenScan = subscribeRetryWaitTimeMillis;
}
public void run() {
running.set(true);
while (running.get()) {
HostAndPort master = null;
List<HostAndPort> slavesPools = new ArrayList<HostAndPort>();
log.fine("Scanning for master");
for (HostAndPort node : nodes) {
log.finer("Connecting to Redis " + node);
Jedis jedis = null;
try {
jedis = new Jedis(node.getHost(), node.getPort());
Role role = determineRole(jedis.info("replication"));
if (Role.master.equals(role)) {
log.fine("Found Redis master at " + node);
if (masterName.equals(node)) {
break;
} else {
master = node;
masterName = node.toString();
}
} else {
slavesPools.add(node);
}
} catch (JedisException e) {
log.warning("Cannot connect to elasticache node running @ " + node + ". Reason: " + e
+ ". Trying next one.");
if (jedis != null)
jedis.close();
}
}
// A new master was found
if (master != null)
initPool(master, slavesPools);
try {
Thread.sleep(waitBetweenScan);
} catch (InterruptedException e1) {
log.log(Level.SEVERE, "Sleep interrupted: ", e1);
}
}
}
public void shutdown() {
try {
log.fine("Shutting down elasticache master listener thread");
running.set(false);
} catch (Exception e) {
log.log(Level.SEVERE, "Caught exception while shutting down: ", e);
}
}
}
}