Skip to content

Commit 0cecd8a

Browse files
Merge pull request #56 from artkoshelev/make-polling-strategy-customizable
ISSUE-55 make config polling customizable
2 parents 48d18f4 + d6312f3 commit 0cecd8a

11 files changed

Lines changed: 246 additions & 28 deletions

src/main/java/net/spy/memcached/ConfigurationPoller.java

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@
77

88
import java.net.InetSocketAddress;
99
import java.util.ArrayList;
10-
import java.util.Collection;
1110
import java.util.Collections;
1211
import java.util.Date;
13-
import java.util.Iterator;
1412
import java.util.List;
1513
import java.util.concurrent.Executors;
1614
import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -20,6 +18,8 @@
2018
import net.spy.memcached.compat.SpyThread;
2119
import net.spy.memcached.config.ClusterConfiguration;
2220
import net.spy.memcached.config.ClusterConfigurationObserver;
21+
import net.spy.memcached.config.ConfigEndpointSelectionStrategy;
22+
import net.spy.memcached.config.RoundRobinConfigEndpointSelectionStrategy;
2323
import net.spy.memcached.config.NodeEndPoint;
2424
import net.spy.memcached.ops.ConfigurationType;
2525
import net.spy.memcached.transcoders.SerializingTranscoder;
@@ -46,21 +46,23 @@ public class ConfigurationPoller extends SpyThread{
4646
private ClusterConfiguration currentClusterConfiguration;
4747
//The transcoder used for config.
4848
private Transcoder<Object> configTranscoder = new SerializingTranscoder();
49-
private int currentIndex = 0;
5049
private Date date = new Date();
5150
private long lastSuccessfulPoll = date.getTime();
5251
private int pollingErrorCount = 0;
52+
private final ConfigEndpointSelectionStrategy configEndpointSelectionStrategy;
5353

5454
//The executor is used to keep the task and it's execution independent. The scheduled thread polls takes care of
5555
//the periodic polling.
5656
private ScheduledThreadPoolExecutor scheduledExecutor;
5757

5858
public ConfigurationPoller(final MemcachedClient client){
59-
this(client, DEFAULT_POLL_INTERVAL, false);
59+
this(client, DEFAULT_POLL_INTERVAL, false, new RoundRobinConfigEndpointSelectionStrategy());
6060
}
6161

62-
public ConfigurationPoller(final MemcachedClient client, long pollingInterval, final boolean useDaemonThreads){
62+
public ConfigurationPoller(final MemcachedClient client, long pollingInterval, final boolean useDaemonThreads,
63+
final ConfigEndpointSelectionStrategy configEndpointSelectionStrategy){
6364
this.client = client;
65+
this.configEndpointSelectionStrategy = configEndpointSelectionStrategy;
6466
this.scheduledExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
6567
@Override
6668
public Thread newThread(Runnable runnable) {
@@ -90,21 +92,8 @@ public void run(){
9092
try{
9193
getLogger().info("Starting configuration poller.");
9294
String newConfigResponse = null;
93-
NodeEndPoint endpointToGetConfig = null;
94-
95-
Collection<NodeEndPoint> endpoints = client.getAvailableNodeEndPoints();
96-
if(endpoints.isEmpty()){
97-
//If no nodes are available status, then get all the endpoints. This provides an
98-
//oppurtunity to re-resolve the hostname by recreating InetSocketAddress instance in "NodeEndPoint".getInetSocketAddress().
99-
endpoints = client.getAllNodeEndPoints();
100-
}
101-
currentIndex = (currentIndex+1)%endpoints.size();
102-
Iterator<NodeEndPoint> iterator = endpoints.iterator();
103-
for(int i =0;i<currentIndex;i++){
104-
iterator.next();
105-
}
95+
NodeEndPoint endpointToGetConfig = configEndpointSelectionStrategy.getConfigEndpoint(client);
10696

107-
endpointToGetConfig = iterator.next();
10897
InetSocketAddress socketAddressToGetConfig = endpointToGetConfig.getInetSocketAddress();
10998

11099
getLogger().info("Endpoint to use for configuration access in this poll " + endpointToGetConfig.toString());

src/main/java/net/spy/memcached/ConnectionFactory.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.concurrent.ExecutorService;
3939

4040
import net.spy.memcached.auth.AuthDescriptor;
41+
import net.spy.memcached.config.ConfigEndpointSelectionStrategy;
4142
import net.spy.memcached.metrics.MetricCollector;
4243
import net.spy.memcached.metrics.MetricType;
4344
import net.spy.memcached.ops.Operation;
@@ -128,7 +129,13 @@ MemcachedNode createMemcachedNode(SocketAddress sa, SocketChannel c,
128129
* @return the interval in milliseconds
129130
*/
130131
long getDynamicModePollingInterval();
131-
132+
133+
/**
134+
* The strategy used for selecting which endpoint to use when polling for cluster configuration.
135+
* @return the configuration endpoint selection strategy
136+
*/
137+
ConfigEndpointSelectionStrategy getConfigEndpointSelectionStrategy();
138+
132139
/**
133140
* Get the operation timeout used by this connection.
134141
*/

src/main/java/net/spy/memcached/ConnectionFactoryBuilder.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.concurrent.ExecutorService;
3636

3737
import net.spy.memcached.auth.AuthDescriptor;
38+
import net.spy.memcached.config.ConfigEndpointSelectionStrategy;
3839
import net.spy.memcached.metrics.MetricCollector;
3940
import net.spy.memcached.metrics.MetricType;
4041
import net.spy.memcached.ops.Operation;
@@ -57,7 +58,9 @@ public class ConnectionFactoryBuilder {
5758
protected Transcoder<Object> transcoder;
5859

5960
protected ClientMode clientMode;
60-
61+
62+
protected ConfigEndpointSelectionStrategy configEndpointSelectionStrategy;
63+
6164
protected FailureMode failureMode;
6265

6366
protected Collection<ConnectionObserver> initialObservers =
@@ -121,6 +124,7 @@ public ConnectionFactoryBuilder(ConnectionFactory cf) {
121124
setSSLContext(cf.getSSLContext());
122125
setHostnameForTlsVerification(cf.getHostnameForTlsVerification());
123126
setSkipTlsHostnameVerification(cf.skipTlsHostnameVerification());
127+
setConfigEndpointSelectionStrategy(cf.getConfigEndpointSelectionStrategy());
124128
}
125129

126130
/**
@@ -133,6 +137,11 @@ public ConnectionFactoryBuilder setClientMode(ClientMode clientMode){
133137
return this;
134138
}
135139

140+
public ConnectionFactoryBuilder setConfigEndpointSelectionStrategy(ConfigEndpointSelectionStrategy configEndpointSelectionStrategy){
141+
this.configEndpointSelectionStrategy = configEndpointSelectionStrategy;
142+
return this;
143+
}
144+
136145
public ConnectionFactoryBuilder setOpQueueFactory(OperationQueueFactory q) {
137146
opQueueFactory = q;
138147
return this;
@@ -388,6 +397,13 @@ public ConnectionFactory build() {
388397
public ClientMode getClientMode(){
389398
return clientMode == null ? super.getClientMode() : clientMode;
390399
}
400+
401+
@Override
402+
public ConfigEndpointSelectionStrategy getConfigEndpointSelectionStrategy() {
403+
return configEndpointSelectionStrategy == null
404+
? super.getConfigEndpointSelectionStrategy()
405+
: configEndpointSelectionStrategy;
406+
}
391407

392408
@Override
393409
public BlockingQueue<Operation> createOperationQueue() {

src/main/java/net/spy/memcached/DefaultConnectionFactory.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242

4343
import net.spy.memcached.auth.AuthDescriptor;
4444
import net.spy.memcached.compat.SpyObject;
45+
import net.spy.memcached.config.ConfigEndpointSelectionStrategy;
46+
import net.spy.memcached.config.RoundRobinConfigEndpointSelectionStrategy;
4547
import net.spy.memcached.metrics.DefaultMetricCollector;
4648
import net.spy.memcached.metrics.MetricCollector;
4749
import net.spy.memcached.metrics.MetricType;
@@ -136,6 +138,7 @@ public class DefaultConnectionFactory extends SpyObject implements
136138
protected final int opQueueLen;
137139
private final int readBufSize;
138140
private final HashAlgorithm hashAlg;
141+
private final ConfigEndpointSelectionStrategy configEndpointSelectionStrategy;
139142

140143
private MetricCollector metrics;
141144

@@ -162,6 +165,7 @@ public DefaultConnectionFactory(ClientMode clientMode, int qLen, int bufSize, Ha
162165
this.readBufSize = bufSize;
163166
this.hashAlg = hash;
164167
this.metrics = null;
168+
this.configEndpointSelectionStrategy = new RoundRobinConfigEndpointSelectionStrategy();
165169
}
166170

167171
/**
@@ -215,6 +219,10 @@ public long getDynamicModePollingInterval(){
215219
return ConfigurationPoller.DEFAULT_POLL_INTERVAL;
216220
}
217221

222+
public ConfigEndpointSelectionStrategy getConfigEndpointSelectionStrategy() {
223+
return configEndpointSelectionStrategy;
224+
}
225+
218226
public MemcachedNode createMemcachedNode(SocketAddress sa, SocketChannel c,
219227
int bufSize) {
220228

src/main/java/net/spy/memcached/MemcachedClient.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import net.spy.memcached.compat.SpyObject;
3434
import net.spy.memcached.config.ClusterConfiguration;
3535
import net.spy.memcached.ConfigurationPoller;
36+
import net.spy.memcached.config.ConfigEndpointSelectionStrategy;
3637
import net.spy.memcached.config.NodeEndPoint;
3738
import net.spy.memcached.internal.BulkFuture;
3839
import net.spy.memcached.internal.BulkGetFuture;
@@ -191,12 +192,14 @@ public class MemcachedClient extends SpyObject implements MemcachedClientIF,
191192

192193
protected final ExecutorService executorService;
193194

195+
protected final ConfigEndpointSelectionStrategy configEndpointSelectionStrategy;
196+
194197
private NodeEndPoint configurationNode;
195198
//Set default value to true to attempt config API first. The value is set to false if
196199
//OperationNotSupportedException is thrown.
197200
private boolean isConfigurationProtocolSupported = true;
198201

199-
//This is used to dynamic mode to track whether the client is initialized with set of cache nodes for the first time.
202+
// This is used in dynamic mode to track whether the client is initialized with a set of cache nodes for the first time.
200203
private boolean isConfigurationInitialized = false;
201204

202205
private Transcoder<Object> configTranscoder = new SerializingTranscoder();
@@ -290,6 +293,8 @@ private MemcachedClient(ConnectionFactory cf, List<InetSocketAddress> addrs, boo
290293
authDescriptor = cf.getAuthDescriptor();
291294
executorService = cf.getListenerExecutorService();
292295

296+
configEndpointSelectionStrategy = cf.getConfigEndpointSelectionStrategy();
297+
293298
if(clientMode == ClientMode.Dynamic){
294299
initializeClientUsingConfigEndPoint(cf, addrs.get(0));
295300
} else {
@@ -352,14 +357,14 @@ private void initializeClientUsingConfigEndPoint(ConnectionFactory cf, InetSocke
352357
}
353358

354359
//Initialize and start the poller.
355-
configPoller = new ConfigurationPoller(this, cf.getDynamicModePollingInterval(), cf.isDaemon());
360+
configPoller = new ConfigurationPoller(this, cf.getDynamicModePollingInterval(), cf.isDaemon(), configEndpointSelectionStrategy);
356361
configPoller.subscribeForClusterConfiguration(mconn);
357362
}
358363

359364
private void setupConnection(ConnectionFactory cf, List<InetSocketAddress> addrs)
360365
throws IOException {
361366

362-
mconn = cf.createConnection(addrs);
367+
mconn = configEndpointSelectionStrategy.setupMemcachedConnection(cf, addrs);
363368
assert mconn != null : "Connection factory failed to make a connection";
364369
}
365370

@@ -1291,7 +1296,7 @@ public void complete() {
12911296
}
12921297
});
12931298
rv.setOperation(op);
1294-
mconn.enqueueOperation(sa, op);
1299+
configEndpointSelectionStrategy.getConfigConnection().enqueueOperation(sa, op);
12951300

12961301
return rv;
12971302
}
@@ -1996,7 +2001,7 @@ public void complete() {
19962001
}
19972002
});
19982003
rv.setOperation(op);
1999-
mconn.enqueueOperation(addr, op);
2004+
configEndpointSelectionStrategy.getConfigConnection().enqueueOperation(addr, op);
20002005
return rv;
20012006
}
20022007

@@ -2046,7 +2051,7 @@ public void complete() {
20462051
}
20472052
});
20482053
rv.setOperation(op);
2049-
mconn.enqueueOperation(addr, op);
2054+
configEndpointSelectionStrategy.getConfigConnection().enqueueOperation(addr, op);
20502055
return rv;
20512056
}
20522057

@@ -2073,7 +2078,7 @@ public void complete() {
20732078
}
20742079
});
20752080
rv.setOperation(op);
2076-
mconn.enqueueOperation(addr, op);
2081+
configEndpointSelectionStrategy.getConfigConnection().enqueueOperation(addr, op);
20772082
return rv;
20782083
}
20792084

@@ -2988,6 +2993,7 @@ public boolean shutdown(long timeout, TimeUnit unit) {
29882993
mconn.setName(baseName + " - SHUTTING DOWN (telling client)");
29892994
mconn.shutdown();
29902995
mconn.setName(baseName + " - SHUTTING DOWN (informed client)");
2996+
configEndpointSelectionStrategy.shutdownConfigConnection();
29912997
tcService.shutdown();
29922998
//terminate all pending Auth Threads
29932999
authMonitor.interruptAllPendingAuth();

src/main/java/net/spy/memcached/MemcachedClientIF.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ <T> CASResponse cas(String key, long casId, int exp, T value,
101101

102102
Future<Boolean> replace(String key, int exp, Object o);
103103

104+
/**
105+
* @return point-in-time view of currently available servers
106+
*/
107+
Collection<net.spy.memcached.config.NodeEndPoint> getAvailableNodeEndPoints();
108+
109+
/**
110+
* @return point-in-time view of all servers
111+
*/
112+
Collection<net.spy.memcached.config.NodeEndPoint> getAllNodeEndPoints();
113+
104114
<T> Future<T> asyncGet(String key, Transcoder<T> tc);
105115

106116
Future<Object> asyncGet(String key);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package net.spy.memcached.config;
2+
3+
import net.spy.memcached.ConnectionFactory;
4+
import net.spy.memcached.MemcachedClientIF;
5+
import net.spy.memcached.MemcachedConnection;
6+
7+
import java.io.IOException;
8+
import java.net.InetSocketAddress;
9+
import java.util.List;
10+
11+
/**
12+
* A configuration endpoint selection strategy which
13+
* holds information and lifecycle relating to the
14+
* configuration endpoint connection opened.
15+
*/
16+
public interface ConfigEndpointSelectionStrategy {
17+
/**
18+
* An endpoint is different from a connection;
19+
* A connection could be composed of several endpoints
20+
*/
21+
NodeEndPoint getConfigEndpoint(MemcachedClientIF client);
22+
/**
23+
* Delegate the responsibility of setting up a memcached connection to this class
24+
* as, depending on the strategy, a copy of this memcached connection may be required
25+
* to be created and kept alive
26+
* @param cf connection factory used to build the Memcached connection
27+
* @param addrs list of addresses that the connection factory should initialize connections with
28+
* @return a Memcached connection to enqueue operations
29+
* @throws IOException
30+
*/
31+
MemcachedConnection setupMemcachedConnection(ConnectionFactory cf, List<InetSocketAddress> addrs) throws IOException;
32+
void shutdownConfigConnection() throws IOException;
33+
MemcachedConnection getConfigConnection();
34+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package net.spy.memcached.config;
2+
3+
import net.spy.memcached.ConnectionFactory;
4+
import net.spy.memcached.MemcachedClientIF;
5+
import net.spy.memcached.MemcachedConnection;
6+
7+
import java.io.IOException;
8+
import java.net.InetSocketAddress;
9+
import java.util.Collection;
10+
import java.util.Iterator;
11+
import java.util.List;
12+
13+
public class RoundRobinConfigEndpointSelectionStrategy implements ConfigEndpointSelectionStrategy {
14+
private int currentIndex = 0;
15+
private MemcachedConnection configurationConnection;
16+
17+
@Override
18+
public NodeEndPoint getConfigEndpoint(MemcachedClientIF client) {
19+
Collection<NodeEndPoint> endpoints = client.getAvailableNodeEndPoints();
20+
if (endpoints.isEmpty()) {
21+
//If no nodes are available status, then get all the endpoints. This provides an
22+
//opportunity to re-resolve the hostname by recreating InetSocketAddress instance in "NodeEndPoint".getInetSocketAddress().
23+
endpoints = client.getAllNodeEndPoints();
24+
}
25+
currentIndex = (currentIndex + 1) % endpoints.size();
26+
Iterator<NodeEndPoint> iterator = endpoints.iterator();
27+
for (int i = 0; i < currentIndex; i++) {
28+
iterator.next();
29+
}
30+
return iterator.next();
31+
}
32+
33+
/**
34+
* The Round Robin strategy (AWS default) reuses the same connection to retrieve configuration.
35+
* In this case, we only need to hold a reference to the connection to return when
36+
* `getConfigConnection` is called
37+
*
38+
*/
39+
@Override
40+
public MemcachedConnection setupMemcachedConnection(ConnectionFactory cf, List<InetSocketAddress> addrs) throws IOException {
41+
configurationConnection = cf.createConnection(addrs);
42+
return configurationConnection;
43+
}
44+
45+
@Override
46+
public void shutdownConfigConnection() {
47+
// As the Memcached connection doubles as a configuration connection,
48+
// this operation is a no-op as that should be handled by the consumer of the
49+
// Memcached connection
50+
}
51+
52+
@Override
53+
public MemcachedConnection getConfigConnection() {
54+
return configurationConnection;
55+
}
56+
}

0 commit comments

Comments
 (0)