Skip to content

Commit cf23d61

Browse files
ralf0131beiwei30
authored andcommitted
[Dubbo-3653] etcd as config center (#3663)
* Minor refactor, no functinoal change. * Separate ConnectionStateListener * Simplify code * Fix typo * Support get external config from etcd config center * Polish diamond operator * Initial etcd support as config center * Add a put interface for JEtcdClient * Enhanced Etcd config center support with the ability to watch and cancel watch * Polish code * Distinguish modification event and delete event * Add etcd registry and configcenter to dubbo-all * Watch again when connection is re-established
1 parent 7fefb81 commit cf23d61

File tree

15 files changed

+811
-158
lines changed

15 files changed

+811
-158
lines changed

dubbo-all/pom.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,13 @@
241241
<scope>compile</scope>
242242
<optional>true</optional>
243243
</dependency>
244+
<dependency>
245+
<groupId>org.apache.dubbo</groupId>
246+
<artifactId>dubbo-registry-etcd3</artifactId>
247+
<version>${project.version}</version>
248+
<scope>compile</scope>
249+
<optional>true</optional>
250+
</dependency>
244251
<dependency>
245252
<groupId>org.apache.dubbo</groupId>
246253
<artifactId>dubbo-monitor-api</artifactId>
@@ -360,6 +367,13 @@
360367
<scope>compile</scope>
361368
<optional>true</optional>
362369
</dependency>
370+
<dependency>
371+
<groupId>org.apache.dubbo</groupId>
372+
<artifactId>dubbo-configcenter-etcd</artifactId>
373+
<version>${project.version}</version>
374+
<scope>compile</scope>
375+
<optional>true</optional>
376+
</dependency>
363377
<dependency>
364378
<groupId>org.apache.dubbo</groupId>
365379
<artifactId>dubbo-compatible</artifactId>
@@ -494,6 +508,7 @@
494508
<include>org.apache.dubbo:dubbo-registry-zookeeper</include>
495509
<include>org.apache.dubbo:dubbo-registry-redis</include>
496510
<include>org.apache.dubbo:dubbo-registry-consul</include>
511+
<include>org.apache.dubbo:dubbo-registry-etcd3</include>
497512
<include>org.apache.dubbo:dubbo-monitor-api</include>
498513
<include>org.apache.dubbo:dubbo-monitor-default</include>
499514
<include>org.apache.dubbo:dubbo-config-api</include>
@@ -515,6 +530,7 @@
515530
<include>org.apache.dubbo:dubbo-configcenter-apollo</include>
516531
<include>org.apache.dubbo:dubbo-configcenter-zookeeper</include>
517532
<include>org.apache.dubbo:dubbo-configcenter-consul</include>
533+
<include>org.apache.dubbo:dubbo-configcenter-etcd</include>
518534
<include>org.apache.dubbo:dubbo-metadata-report-api</include>
519535
<include>org.apache.dubbo:dubbo-metadata-definition</include>
520536
<include>org.apache.dubbo:dubbo-metadata-report-redis</include>

dubbo-bom/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,11 @@
343343
<artifactId>dubbo-configcenter-consul</artifactId>
344344
<version>${project.version}</version>
345345
</dependency>
346+
<dependency>
347+
<groupId>org.apache.dubbo</groupId>
348+
<artifactId>dubbo-configcenter-etcd</artifactId>
349+
<version>${project.version}</version>
350+
</dependency>
346351
<dependency>
347352
<groupId>org.apache.dubbo</groupId>
348353
<artifactId>dubbo-metadata-definition</artifactId>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Licensed to the Apache Software Foundation (ASF) under one or more
4+
~ contributor license agreements. See the NOTICE file distributed with
5+
~ this work for additional information regarding copyright ownership.
6+
~ The ASF licenses this file to You under the Apache License, Version 2.0
7+
~ (the "License"); you may not use this file except in compliance with
8+
~ the License. You may obtain a copy of the License at
9+
~
10+
~ http://www.apache.org/licenses/LICENSE-2.0
11+
~
12+
~ Unless required by applicable law or agreed to in writing, software
13+
~ distributed under the License is distributed on an "AS IS" BASIS,
14+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
~ See the License for the specific language governing permissions and
16+
~ limitations under the License.
17+
-->
18+
19+
<project xmlns="http://maven.apache.org/POM/4.0.0"
20+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
21+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
22+
<parent>
23+
<artifactId>dubbo-configcenter</artifactId>
24+
<groupId>org.apache.dubbo</groupId>
25+
<version>2.7.1-SNAPSHOT</version>
26+
</parent>
27+
<modelVersion>4.0.0</modelVersion>
28+
29+
<artifactId>dubbo-configcenter-etcd</artifactId>
30+
<packaging>jar</packaging>
31+
<name>${project.artifactId}</name>
32+
<description>The etcd implementation of the config-center api</description>
33+
34+
<dependencies>
35+
<dependency>
36+
<groupId>org.apache.dubbo</groupId>
37+
<artifactId>dubbo-configcenter-api</artifactId>
38+
<version>${project.parent.version}</version>
39+
</dependency>
40+
<dependency>
41+
<groupId>org.apache.dubbo</groupId>
42+
<artifactId>dubbo-remoting-etcd3</artifactId>
43+
<version>${project.parent.version}</version>
44+
</dependency>
45+
</dependencies>
46+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.dubbo.configcenter.support.etcd;
19+
20+
import com.google.protobuf.ByteString;
21+
import io.etcd.jetcd.api.Event;
22+
import io.etcd.jetcd.api.WatchCancelRequest;
23+
import io.etcd.jetcd.api.WatchCreateRequest;
24+
import io.etcd.jetcd.api.WatchGrpc;
25+
import io.etcd.jetcd.api.WatchRequest;
26+
import io.etcd.jetcd.api.WatchResponse;
27+
import io.grpc.ManagedChannel;
28+
import io.grpc.stub.StreamObserver;
29+
import org.apache.dubbo.common.URL;
30+
import org.apache.dubbo.common.utils.StringUtils;
31+
import org.apache.dubbo.configcenter.ConfigChangeEvent;
32+
import org.apache.dubbo.configcenter.ConfigChangeType;
33+
import org.apache.dubbo.configcenter.ConfigurationListener;
34+
import org.apache.dubbo.configcenter.DynamicConfiguration;
35+
import org.apache.dubbo.remoting.etcd.StateListener;
36+
import org.apache.dubbo.remoting.etcd.jetcd.JEtcdClient;
37+
38+
import java.util.concurrent.ConcurrentHashMap;
39+
import java.util.concurrent.ConcurrentMap;
40+
41+
import static java.nio.charset.StandardCharsets.UTF_8;
42+
import static org.apache.dubbo.common.Constants.CONFIG_NAMESPACE_KEY;
43+
import static org.apache.dubbo.common.Constants.PATH_SEPARATOR;
44+
45+
/**
46+
* The etcd implementation of {@link DynamicConfiguration}
47+
*/
48+
public class EtcdDynamicConfiguration implements DynamicConfiguration {
49+
50+
/**
51+
* The final root path would be: /$NAME_SPACE/config
52+
*/
53+
private String rootPath;
54+
55+
/**
56+
* The etcd client
57+
*/
58+
private final JEtcdClient etcdClient;
59+
60+
/**
61+
* The map store the key to {@link EtcdConfigWatcher} mapping
62+
*/
63+
private final ConcurrentMap<ConfigurationListener, EtcdConfigWatcher> watchListenerMap;
64+
65+
EtcdDynamicConfiguration(URL url) {
66+
rootPath = "/" + url.getParameter(CONFIG_NAMESPACE_KEY, DEFAULT_GROUP) + "/config";
67+
etcdClient = new JEtcdClient(url);
68+
etcdClient.addStateListener(state -> {
69+
if (state == StateListener.CONNECTED) {
70+
try {
71+
recover();
72+
} catch (Exception e) {
73+
// ignore
74+
}
75+
}
76+
});
77+
watchListenerMap = new ConcurrentHashMap<>();
78+
}
79+
80+
@Override
81+
public void addListener(String key, String group, ConfigurationListener listener) {
82+
if (watchListenerMap.get(listener) == null) {
83+
String normalizedKey = convertKey(key);
84+
EtcdConfigWatcher watcher = new EtcdConfigWatcher(normalizedKey, listener);
85+
watchListenerMap.put(listener, watcher);
86+
watcher.watch();
87+
}
88+
}
89+
90+
@Override
91+
public void removeListener(String key, String group, ConfigurationListener listener) {
92+
EtcdConfigWatcher watcher = watchListenerMap.get(listener);
93+
watcher.cancelWatch();
94+
}
95+
96+
// TODO Abstract the logic into super class
97+
@Override
98+
public String getConfig(String key, String group, long timeout) throws IllegalStateException {
99+
if (StringUtils.isNotEmpty(group)) {
100+
key = group + PATH_SEPARATOR + key;
101+
} else {
102+
int i = key.lastIndexOf(".");
103+
key = key.substring(0, i) + PATH_SEPARATOR + key.substring(i + 1);
104+
}
105+
return (String) getInternalProperty(rootPath + PATH_SEPARATOR + key);
106+
}
107+
108+
@Override
109+
public Object getInternalProperty(String key) {
110+
return etcdClient.getKVValue(key);
111+
}
112+
113+
114+
private String convertKey(String key) {
115+
int index = key.lastIndexOf('.');
116+
return rootPath + PATH_SEPARATOR + key.substring(0, index) + PATH_SEPARATOR + key.substring(index + 1);
117+
}
118+
119+
private void recover() {
120+
for (EtcdConfigWatcher watcher: watchListenerMap.values()) {
121+
watcher.watch();
122+
}
123+
}
124+
125+
public class EtcdConfigWatcher implements StreamObserver<WatchResponse> {
126+
127+
private ConfigurationListener listener;
128+
protected WatchGrpc.WatchStub watchStub;
129+
private StreamObserver<WatchRequest> observer;
130+
protected long watchId;
131+
private ManagedChannel channel;
132+
private String key;
133+
134+
public EtcdConfigWatcher(String key, ConfigurationListener listener) {
135+
this.key = key;
136+
this.listener = listener;
137+
this.channel = etcdClient.getChannel();
138+
}
139+
140+
@Override
141+
public void onNext(WatchResponse watchResponse) {
142+
this.watchId = watchResponse.getWatchId();
143+
for (Event etcdEvent : watchResponse.getEventsList()) {
144+
ConfigChangeType type = ConfigChangeType.MODIFIED;
145+
if (etcdEvent.getType() == Event.EventType.DELETE) {
146+
type = ConfigChangeType.DELETED;
147+
}
148+
ConfigChangeEvent event = new ConfigChangeEvent(
149+
etcdEvent.getKv().getKey().toString(UTF_8),
150+
etcdEvent.getKv().getValue().toString(UTF_8), type);
151+
listener.process(event);
152+
}
153+
}
154+
155+
@Override
156+
public void onError(Throwable throwable) {
157+
// ignore
158+
}
159+
160+
@Override
161+
public void onCompleted() {
162+
// ignore
163+
}
164+
165+
public long getWatchId() {
166+
return watchId;
167+
}
168+
169+
private void watch() {
170+
watchStub = WatchGrpc.newStub(channel);
171+
observer = watchStub.watch(this);
172+
WatchCreateRequest.Builder builder = WatchCreateRequest.newBuilder()
173+
.setKey(ByteString.copyFromUtf8(key))
174+
.setProgressNotify(true);
175+
WatchRequest req = WatchRequest.newBuilder().setCreateRequest(builder).build();
176+
observer.onNext(req);
177+
}
178+
179+
private void cancelWatch() {
180+
WatchCancelRequest watchCancelRequest =
181+
WatchCancelRequest.newBuilder().setWatchId(watchId).build();
182+
WatchRequest cancelRequest = WatchRequest.newBuilder()
183+
.setCancelRequest(watchCancelRequest).build();
184+
observer.onNext(cancelRequest);
185+
}
186+
}
187+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.dubbo.configcenter.support.etcd;
19+
20+
import org.apache.dubbo.common.URL;
21+
import org.apache.dubbo.configcenter.AbstractDynamicConfigurationFactory;
22+
import org.apache.dubbo.configcenter.DynamicConfiguration;
23+
24+
/**
25+
* The etcd implementation of {@link AbstractDynamicConfigurationFactory}
26+
*/
27+
public class EtcdDynamicConfigurationFactory extends AbstractDynamicConfigurationFactory {
28+
29+
@Override
30+
protected DynamicConfiguration createDynamicConfiguration(URL url) {
31+
return new EtcdDynamicConfiguration(url);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
etcd=org.apache.dubbo.configcenter.support.etcd.EtcdDynamicConfigurationFactory

0 commit comments

Comments
 (0)