From 4e77922ff350257410fd481bd7ab195e8510ebe7 Mon Sep 17 00:00:00 2001 From: Steve Ungerer Date: Thu, 10 Sep 2015 14:59:57 -0400 Subject: [PATCH] DNS monitoring Resolves the single server endpoint at a configurable interval to determine if the endpoint has been changed to point at a new master node. Designed for use with AWS ElastiCache replication group. Inspired by https://github.com/mrniko/redisson/issues/241 --- .../java/org/redisson/SingleServerConfig.java | 46 +++++++++++++++ .../connection/SingleConnectionManager.java | 58 +++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/src/main/java/org/redisson/SingleServerConfig.java b/src/main/java/org/redisson/SingleServerConfig.java index 3c59b5092..dfa30f453 100644 --- a/src/main/java/org/redisson/SingleServerConfig.java +++ b/src/main/java/org/redisson/SingleServerConfig.java @@ -38,6 +38,21 @@ public class SingleServerConfig extends BaseConfig { */ private int connectionPoolSize = 100; + + /** + * Should the server address be monitored for changes in DNS? Useful for + * AWS ElastiCache where the client is pointed at the endpoint for a replication group + * which is a DNS alias to the current master node.
+ * NB: applications must ensure the JVM DNS cache TTL is low enough to support this. + * e.g., http://docs.aws.amazon.com/AWSSdkDocsJava/latest/DeveloperGuide/java-dg-jvm-ttl.html + */ + private boolean dnsMonitoring = false; + + /** + * Interval in milliseconds to check DNS + */ + private long dnsMonitoringInterval = 5000; + SingleServerConfig() { } @@ -46,6 +61,8 @@ public class SingleServerConfig extends BaseConfig { setAddress(config.getAddress()); setConnectionPoolSize(config.getConnectionPoolSize()); setSubscriptionConnectionPoolSize(config.getSubscriptionConnectionPoolSize()); + setDnsMonitoring(config.isDnsMonitoring()); + setDnsMonitoringInterval(config.getDnsMonitoringInterval()); } /** @@ -93,4 +110,33 @@ public class SingleServerConfig extends BaseConfig { this.address = address; } + /** + * Monitoring of the endpoint address for DNS changes. + * Default is false. + * + * @param dnsMonitoring + * @return + */ + public SingleServerConfig setDnsMonitoring(boolean dnsMonitoring) { + this.dnsMonitoring = dnsMonitoring; + return this; + } + public boolean isDnsMonitoring() { + return dnsMonitoring; + } + + /** + * Interval in milliseconds to check the endpoint DNS if {@link #isDnsMonitoring()} is true. + * Default is 5000. + * + * @param dnsMonitoringInterval + * @return + */ + public SingleServerConfig setDnsMonitoringInterval(long dnsMonitoringInterval) { + this.dnsMonitoringInterval = dnsMonitoringInterval; + return this; + } + public long getDnsMonitoringInterval() { + return dnsMonitoringInterval; + } } diff --git a/src/main/java/org/redisson/connection/SingleConnectionManager.java b/src/main/java/org/redisson/connection/SingleConnectionManager.java index 717725532..f267fc41c 100644 --- a/src/main/java/org/redisson/connection/SingleConnectionManager.java +++ b/src/main/java/org/redisson/connection/SingleConnectionManager.java @@ -15,12 +15,29 @@ */ package org.redisson.connection; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + import org.redisson.Config; import org.redisson.MasterSlaveServersConfig; import org.redisson.SingleServerConfig; +import org.redisson.client.RedisConnectionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.util.concurrent.GlobalEventExecutor; +import io.netty.util.concurrent.ScheduledFuture; public class SingleConnectionManager extends MasterSlaveConnectionManager { + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final AtomicReference currentMaster = new AtomicReference(); + + private ScheduledFuture monitorFuture; + public SingleConnectionManager(SingleServerConfig cfg, Config config) { MasterSlaveServersConfig newconfig = new MasterSlaveServersConfig(); String addr = cfg.getAddress().getHost() + ":" + cfg.getAddress().getPort(); @@ -37,6 +54,16 @@ public class SingleConnectionManager extends MasterSlaveConnectionManager { newconfig.setSlaveSubscriptionConnectionPoolSize(cfg.getSubscriptionConnectionPoolSize()); init(newconfig, config); + + if (cfg.isDnsMonitoring()) { + try { + this.currentMaster.set(InetAddress.getByName(cfg.getAddress().getHost())); + } catch (UnknownHostException e) { + throw new RedisConnectionException("Unknown host", e); + } + log.debug("DNS monitoring enabled; Current master set to {}", currentMaster.get()); + monitorDnsChange(cfg); + } } @Override @@ -44,5 +71,36 @@ public class SingleConnectionManager extends MasterSlaveConnectionManager { SingleEntry entry = new SingleEntry(0, MAX_SLOT, this, config); entries.put(MAX_SLOT, entry); } + + private void monitorDnsChange(final SingleServerConfig cfg) { + monitorFuture = GlobalEventExecutor.INSTANCE.scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + try { + InetAddress master = currentMaster.get(); + InetAddress now = InetAddress.getByName(cfg.getAddress().getHost()); + if (!now.getHostAddress().equals(master.getHostAddress())) { + log.info("Detected DNS change. {} has changed from {} to {}", cfg.getAddress().getHost(), master.getHostAddress(), now.getHostAddress()); + if (currentMaster.compareAndSet(master, now)) { + changeMaster(MAX_SLOT,cfg.getAddress().getHost(), cfg.getAddress().getPort()); + log.info("Master has been changed"); + } + } + + } catch (Exception e) { + log.error(e.getMessage(), e); + } + + } + + }, cfg.getDnsMonitoringInterval(), cfg.getDnsMonitoringInterval(), TimeUnit.MILLISECONDS); + } + @Override + public void shutdown() { + if (monitorFuture != null) { + monitorFuture.cancel(true); + } + super.shutdown(); + } }