Skip to content

Commit 1c5c292

Browse files
author
liwang
committed
ZOOKEEPER-4956: Provide a HostProvider that uses DNS SRV record for dynamic server discovery
Author: Li Wang <[email protected]>
1 parent a7fe813 commit 1c5c292

File tree

14 files changed

+1347
-323
lines changed

14 files changed

+1347
-323
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@
573573
<enforcer.version>3.0.0-M3</enforcer.version>
574574
<commons-io.version>2.17.0</commons-io.version>
575575
<burningwave.mockdns.version>0.25.4</burningwave.mockdns.version>
576+
<dnsjava.version>3.5.1</dnsjava.version>
576577
<clover-maven-plugin.version>4.4.1</clover-maven-plugin.version>
577578
<sonar-maven-plugin.version>3.7.0.1746</sonar-maven-plugin.version>
578579

@@ -774,6 +775,11 @@
774775
<artifactId>tools</artifactId>
775776
<version>${burningwave.mockdns.version}</version>
776777
</dependency>
778+
<dependency>
779+
<groupId>dnsjava</groupId>
780+
<artifactId>dnsjava</artifactId>
781+
<version>${dnsjava.version}</version>
782+
</dependency>
777783
</dependencies>
778784
</dependencyManagement>
779785

zookeeper-docs/src/main/resources/markdown/zookeeperProgrammers.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,11 @@ and [SASL authentication for ZooKeeper](https://cwiki.apache.org/confluence/disp
13961396
you want to randomize that.
13971397
Default: false
13981398
1399+
* *zookeeper.hostProvider.dnsSrvRefreshIntervalSeconds* :
1400+
**New in 3.10.0:**
1401+
Specifies the refresh interval in seconds for DNS SRV record lookups when using DnsSrvHostProvider.
1402+
A value of 0 disables periodic refresh.
1403+
Default: 60 seconds
13991404
14001405
<a name="C+Binding"></a>
14011406

zookeeper-server/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@
190190
<artifactId>commons-io</artifactId>
191191
<scope>compile</scope>
192192
</dependency>
193+
<dependency>
194+
<groupId>dnsjava</groupId>
195+
<artifactId>dnsjava</artifactId>
196+
</dependency>
193197
</dependencies>
194198

195199
<build>

zookeeper-server/src/main/java/org/apache/zookeeper/ZooKeeper.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.apache.zookeeper.client.Chroot;
4747
import org.apache.zookeeper.client.ConnectStringParser;
4848
import org.apache.zookeeper.client.HostProvider;
49+
import org.apache.zookeeper.client.HostProviderFactory;
4950
import org.apache.zookeeper.client.StaticHostProvider;
5051
import org.apache.zookeeper.client.ZKClientConfig;
5152
import org.apache.zookeeper.client.ZooKeeperBuilder;
@@ -1140,10 +1141,9 @@ public ZooKeeper(ZooKeeperOptions options) throws IOException {
11401141
if (options.getHostProvider() != null) {
11411142
hostProvider = options.getHostProvider().apply(connectStringParser.getServerAddresses());
11421143
} else {
1143-
hostProvider = new StaticHostProvider(connectStringParser.getServerAddresses(), clientConfig);
1144+
hostProvider = HostProviderFactory.create(connectStringParser, clientConfig);
11441145
}
11451146
this.hostProvider = hostProvider;
1146-
11471147
chroot = Chroot.ofNullable(connectStringParser.getChrootPath());
11481148
cnxn = createConnection(
11491149
hostProvider,
@@ -1329,6 +1329,10 @@ public synchronized void close() throws InterruptedException {
13291329
LOG.debug("Ignoring unexpected exception during close", e);
13301330
}
13311331

1332+
if (hostProvider != null) {
1333+
hostProvider.close();
1334+
}
1335+
13321336
LOG.info("Session: 0x{} closed", Long.toHexString(getSessionId()));
13331337
}
13341338

zookeeper-server/src/main/java/org/apache/zookeeper/client/ConnectStringParser.java

Lines changed: 72 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.List;
2525
import org.apache.zookeeper.common.NetUtils;
2626
import org.apache.zookeeper.common.PathUtils;
27+
import org.apache.zookeeper.common.StringUtils;
2728

2829
/**
2930
* A parser for ZooKeeper Client connect strings.
@@ -38,38 +39,88 @@
3839
public final class ConnectStringParser {
3940

4041
private static final int DEFAULT_PORT = 2181;
42+
private static final String DNS_SRV_PREFIX = "dns-srv://";
4143

42-
private final String chrootPath;
44+
public enum ConnectionType {
45+
DNS_SRV,
46+
HOST_PORT
47+
}
4348

49+
private String chrootPath;
4450
private final ArrayList<InetSocketAddress> serverAddresses = new ArrayList<>();
51+
private final ConnectionType connectionType;
52+
private final String connectString;
4553

4654
/**
47-
* Parse host and port by splitting client connectString
48-
* with support for IPv6 literals
49-
* @throws IllegalArgumentException
50-
* for an invalid chroot path.
55+
* Constructs a ConnectStringParser with given connect string.
56+
*
57+
* <p>Supports two connection string formats:</p>
58+
* <ul>
59+
* <li><strong>Host:Port format:</strong> "host1:2181,host2:2181/chroot"</li>
60+
* <li><strong>DNS SRV format:</strong> "dns-srv://service.domain.com/chroot"</li>
61+
* </ul>
62+
*
63+
* @param connectString the connect string to parse
64+
* @throws IllegalArgumentException if connectString is null/empty or contains invalid chroot path
5165
*/
52-
public ConnectStringParser(String connectString) {
66+
public ConnectStringParser(final String connectString) {
67+
if (StringUtils.isBlank(connectString)) {
68+
throw new IllegalArgumentException("Connect string cannot be null or empty");
69+
}
70+
71+
if (connectString.startsWith(DNS_SRV_PREFIX)) {
72+
connectionType = ConnectionType.DNS_SRV;
73+
parseDnsSrvFormat(connectString);
74+
} else {
75+
connectionType = ConnectionType.HOST_PORT;
76+
parseHostPortFormat(connectString);
77+
}
78+
this.connectString = connectString;
79+
}
80+
81+
public String getChrootPath() {
82+
return chrootPath;
83+
}
84+
85+
public ArrayList<InetSocketAddress> getServerAddresses() {
86+
return serverAddresses;
87+
}
88+
89+
public ConnectionType getConnectionType() {
90+
return connectionType;
91+
}
92+
93+
public String getConnectString() {
94+
return connectString;
95+
}
96+
97+
private String[] parseConnectString(final String connectString) {
98+
String serverPart = connectString;
99+
String chrootPath = null;
100+
53101
// parse out chroot, if any
54-
int off = connectString.indexOf('/');
102+
final int off = connectString.indexOf('/');
55103
if (off >= 0) {
56-
String chrootPath = connectString.substring(off);
104+
chrootPath = connectString.substring(off);
57105
// ignore "/" chroot spec, same as null
58106
if (chrootPath.length() == 1) {
59-
this.chrootPath = null;
107+
chrootPath = null;
60108
} else {
61109
PathUtils.validatePath(chrootPath);
62-
this.chrootPath = chrootPath;
63110
}
64-
connectString = connectString.substring(0, off);
65-
} else {
66-
this.chrootPath = null;
111+
serverPart = connectString.substring(0, off);
67112
}
113+
return new String[]{serverPart, chrootPath};
114+
}
115+
116+
private void parseHostPortFormat(final String connectString) {
117+
final String[] parts = parseConnectString(connectString);
118+
chrootPath = parts[1];
68119

69-
List<String> hostsList = split(connectString, ",");
120+
final List<String> hostsList = split(parts[0], ",");
70121
for (String host : hostsList) {
71122
int port = DEFAULT_PORT;
72-
String[] hostAndPort = NetUtils.getIPV6HostAndPort(host);
123+
final String[] hostAndPort = NetUtils.getIPV6HostAndPort(host);
73124
if (hostAndPort.length != 0) {
74125
host = hostAndPort[0];
75126
if (hostAndPort.length == 2) {
@@ -89,12 +140,11 @@ public ConnectStringParser(String connectString) {
89140
}
90141
}
91142

92-
public String getChrootPath() {
93-
return chrootPath;
143+
private void parseDnsSrvFormat(final String connectString) {
144+
final String[] parts = parseConnectString(connectString.substring(DNS_SRV_PREFIX.length()));
145+
chrootPath = parts[1];
146+
// The DNS service name is stored as a placeholder address
147+
// The actual resolution will be handled by DnsSrvHostProvider
148+
serverAddresses.add(InetSocketAddress.createUnresolved(parts[0], DEFAULT_PORT));
94149
}
95-
96-
public ArrayList<InetSocketAddress> getServerAddresses() {
97-
return serverAddresses;
98-
}
99-
100150
}

0 commit comments

Comments
 (0)