Skip to content

Commit dde9334

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 dde9334

File tree

14 files changed

+1399
-327
lines changed

14 files changed

+1399
-327
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/zookeeperAdmin.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,14 @@ of servers -- that is, when deploying clusters of servers.
13151315
leader election. If you want to test multiple servers on a single machine, then
13161316
different ports can be used for each server.
13171317

1318+
* *hostProvider.dnsSrvRefreshIntervalSecond* :
1319+
(Java system property: **zookeeper.hostProvider.dnsSrvRefreshIntervalSeconds**)
1320+
**New in 3.10.0:**
1321+
The refresh interval in seconds for DNS SRV record lookups when using DnsSrvHostProvider.
1322+
This property controls how frequently the DNS SRV records are queried to update the server list.
1323+
A value of 0 disables periodic refresh.
1324+
1325+
The default value is 60 (60 seconds).
13181326

13191327
<a name="id_multi_address"></a>
13201328
Since ZooKeeper 3.6.0 it is possible to specify **multiple addresses** for each

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: 7 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,11 @@ public synchronized void close() throws InterruptedException {
13291329
LOG.debug("Ignoring unexpected exception during close", e);
13301330
}
13311331

1332+
// Close the host provider to release any resources
1333+
if (hostProvider != null) {
1334+
hostProvider.close();
1335+
}
1336+
13321337
LOG.info("Session: 0x{} closed", Long.toHexString(getSessionId()));
13331338
}
13341339

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

Lines changed: 82 additions & 27 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.
@@ -37,39 +38,82 @@
3738
*/
3839
public final class ConnectStringParser {
3940

40-
private static final int DEFAULT_PORT = 2181;
41+
public enum ConnectionType {
42+
DNS_SRV,
43+
HOST_PORT
44+
}
4145

42-
private final String chrootPath;
46+
private static final String DNS_SRV_PREFIX = "dns-srv://";
47+
private static final int DEFAULT_PORT = 2181;
4348

49+
private final String connectString;
50+
private final ConnectionType connectionType;
51+
private String chrootPath;
4452
private final ArrayList<InetSocketAddress> serverAddresses = new ArrayList<>();
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 connection 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 connection string to parse
64+
* @throws IllegalArgumentException if connectString is null/empty or contains invalid chroot path
5165
*/
52-
public ConnectStringParser(String connectString) {
53-
// parse out chroot, if any
54-
int off = connectString.indexOf('/');
55-
if (off >= 0) {
56-
String chrootPath = connectString.substring(off);
57-
// ignore "/" chroot spec, same as null
58-
if (chrootPath.length() == 1) {
59-
this.chrootPath = null;
60-
} else {
61-
PathUtils.validatePath(chrootPath);
62-
this.chrootPath = chrootPath;
63-
}
64-
connectString = connectString.substring(0, off);
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+
this.connectString = connectString;
72+
if (connectString.startsWith(DNS_SRV_PREFIX)) {
73+
connectionType = ConnectionType.DNS_SRV;
74+
parseDnsSrvFormat(connectString);
6575
} else {
66-
this.chrootPath = null;
76+
connectionType = ConnectionType.HOST_PORT;
77+
parseHostPortFormat(connectString);
6778
}
79+
}
80+
81+
public String getChrootPath() {
82+
return chrootPath;
83+
}
84+
85+
public ArrayList<InetSocketAddress> getServerAddresses() {
86+
return serverAddresses;
87+
}
6888

69-
List<String> hostsList = split(connectString, ",");
89+
public String getConnectString() {
90+
return connectString;
91+
}
92+
93+
public ConnectionType getConnectionType() {
94+
return connectionType;
95+
}
96+
97+
private void parseDnsSrvFormat(final String connectString) {
98+
final String dnsName = connectString.substring(DNS_SRV_PREFIX.length());
99+
final String[] parts = extractChrootPath(dnsName);
100+
final String dnsServiceName = parts[0];
101+
102+
chrootPath = parts[1];
103+
// The DNS service name is stored as a placeholder address
104+
// The actual resolution will be handled by DnsSrvHostProvider
105+
serverAddresses.add(InetSocketAddress.createUnresolved(dnsServiceName, DEFAULT_PORT));
106+
}
107+
108+
private void parseHostPortFormat(String connectString) {
109+
final String[] parts = extractChrootPath(connectString);
110+
final String serverPart = parts[0];
111+
chrootPath = parts[1];
112+
113+
final List<String> hostsList = split(serverPart, ",");
70114
for (String host : hostsList) {
71115
int port = DEFAULT_PORT;
72-
String[] hostAndPort = NetUtils.getIPV6HostAndPort(host);
116+
final String[] hostAndPort = NetUtils.getIPV6HostAndPort(host);
73117
if (hostAndPort.length != 0) {
74118
host = hostAndPort[0];
75119
if (hostAndPort.length == 2) {
@@ -89,12 +133,23 @@ public ConnectStringParser(String connectString) {
89133
}
90134
}
91135

92-
public String getChrootPath() {
93-
return chrootPath;
94-
}
136+
private String[] extractChrootPath(final String connectionString) {
137+
String serverPart = connectionString;
138+
String chrootPath = null;
95139

96-
public ArrayList<InetSocketAddress> getServerAddresses() {
97-
return serverAddresses;
140+
// parse out chroot, if any
141+
final int chrootIndex = connectionString.indexOf('/');
142+
if (chrootIndex >= 0) {
143+
chrootPath = connectionString.substring(chrootIndex);
144+
// ignore "/" chroot spec, same as null
145+
if (chrootPath.length() == 1) {
146+
chrootPath = null;
147+
} else {
148+
PathUtils.validatePath(chrootPath);
149+
}
150+
serverPart = connectionString.substring(0, chrootIndex);
151+
}
152+
return new String[]{serverPart, chrootPath};
98153
}
99154

100155
}

0 commit comments

Comments
 (0)