
org.apache.dubbo.registry.zookeeper.ZookeeperServiceDiscovery Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.zookeeper;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.function.ThrowableConsumer;
import org.apache.dubbo.common.function.ThrowableFunction;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.registry.client.AbstractServiceDiscovery;
import org.apache.dubbo.registry.client.ServiceDiscovery;
import org.apache.dubbo.registry.client.ServiceInstance;
import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.model.ApplicationModel;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.CuratorWatcher;
import org.apache.curator.x.discovery.ServiceCache;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ZOOKEEPER_EXCEPTION;
import static org.apache.dubbo.common.function.ThrowableFunction.execute;
import static org.apache.dubbo.metadata.RevisionResolver.EMPTY_REVISION;
import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME;
import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getExportedServicesRevision;
import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkUtils.build;
import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkUtils.buildCuratorFramework;
import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkUtils.buildServiceDiscovery;
import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkUtils.getRootPath;
import static org.apache.dubbo.rpc.RpcException.REGISTRY_EXCEPTION;
/**
* Zookeeper {@link ServiceDiscovery} implementation based on
* Apache Curator X Discovery
*
* TODO, replace curator CuratorFramework with dubbo ZookeeperClient
*/
public class ZookeeperServiceDiscovery extends AbstractServiceDiscovery {
private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
public static final String DEFAULT_GROUP = "/services";
private final CuratorFramework curatorFramework;
private final String rootPath;
private final org.apache.curator.x.discovery.ServiceDiscovery serviceDiscovery;
/**
* The Key is watched Zookeeper path, the value is an instance of {@link CuratorWatcher}
*/
private final Map watcherCaches = new ConcurrentHashMap<>();
public ZookeeperServiceDiscovery(ApplicationModel applicationModel, URL registryURL) {
super(applicationModel, registryURL);
try {
this.curatorFramework = buildCuratorFramework(registryURL, this);
this.rootPath = getRootPath(registryURL);
this.serviceDiscovery = buildServiceDiscovery(curatorFramework, rootPath);
this.serviceDiscovery.start();
} catch (Exception e) {
throw new IllegalStateException("Create zookeeper service discovery failed.", e);
}
}
@Override
public void doDestroy() throws Exception {
serviceDiscovery.close();
curatorFramework.close();
watcherCaches.clear();
}
@Override
public void doRegister(ServiceInstance serviceInstance) {
try {
serviceDiscovery.registerService(build(serviceInstance));
} catch (Exception e) {
throw new RpcException(REGISTRY_EXCEPTION, "Failed register instance " + serviceInstance.toString(), e);
}
}
@Override
public void doUnregister(ServiceInstance serviceInstance) throws RuntimeException {
if (serviceInstance != null) {
doInServiceRegistry(serviceDiscovery -> serviceDiscovery.unregisterService(build(serviceInstance)));
}
}
@Override
protected void doUpdate(ServiceInstance oldServiceInstance, ServiceInstance newServiceInstance)
throws RuntimeException {
if (EMPTY_REVISION.equals(getExportedServicesRevision(newServiceInstance))
|| EMPTY_REVISION.equals(
oldServiceInstance.getMetadata().get(EXPORTED_SERVICES_REVISION_PROPERTY_NAME))) {
super.doUpdate(oldServiceInstance, newServiceInstance);
return;
}
org.apache.curator.x.discovery.ServiceInstance oldInstance = build(oldServiceInstance);
org.apache.curator.x.discovery.ServiceInstance newInstance = build(newServiceInstance);
if (!Objects.equals(newInstance.getName(), oldInstance.getName())
|| !Objects.equals(newInstance.getId(), oldInstance.getId())) {
// Ignore if id changed. Should unregister first.
super.doUpdate(oldServiceInstance, newServiceInstance);
return;
}
try {
this.serviceInstance = newServiceInstance;
reportMetadata(newServiceInstance.getServiceMetadata());
serviceDiscovery.updateService(newInstance);
} catch (Exception e) {
throw new RpcException(REGISTRY_EXCEPTION, "Failed register instance " + newServiceInstance.toString(), e);
}
}
@Override
public Set getServices() {
return doInServiceDiscovery(s -> new LinkedHashSet<>(s.queryForNames()));
}
@Override
public List getInstances(String serviceName) throws NullPointerException {
return doInServiceDiscovery(s -> build(registryURL, s.queryForInstances(serviceName)));
}
@Override
public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener)
throws NullPointerException, IllegalArgumentException {
// check if listener has already been added through another interface/service
if (!instanceListeners.add(listener)) {
return;
}
listener.getServiceNames().forEach(serviceName -> registerServiceWatcher(serviceName, listener));
}
@Override
public void removeServiceInstancesChangedListener(ServiceInstancesChangedListener listener)
throws IllegalArgumentException {
if (!instanceListeners.remove(listener)) {
return;
}
listener.getServiceNames().forEach(serviceName -> {
ZookeeperServiceDiscoveryChangeWatcher watcher = watcherCaches.get(serviceName);
if (watcher != null) {
watcher.getListeners().remove(listener);
if (watcher.getListeners().isEmpty()) {
watcherCaches.remove(serviceName);
try {
watcher.getCacheInstance().close();
} catch (IOException e) {
logger.error(
REGISTRY_ZOOKEEPER_EXCEPTION,
"curator stop watch failed",
"",
"Curator Stop service discovery watch failed. Service Name: " + serviceName);
}
}
}
});
}
@Override
public boolean isAvailable() {
// Fix the issue of timeout for all calls to the isAvailable method after the zookeeper is disconnected
return !isDestroy() && isConnected() && CollectionUtils.isNotEmpty(getServices());
}
private boolean isConnected() {
if (curatorFramework == null || curatorFramework.getZookeeperClient() == null) {
return false;
}
return curatorFramework.getZookeeperClient().isConnected();
}
private void doInServiceRegistry(ThrowableConsumer consumer) {
ThrowableConsumer.execute(serviceDiscovery, s -> consumer.accept(s));
}
private R doInServiceDiscovery(ThrowableFunction function) {
return execute(serviceDiscovery, function);
}
protected void registerServiceWatcher(String serviceName, ServiceInstancesChangedListener listener) {
CountDownLatch latch = new CountDownLatch(1);
ZookeeperServiceDiscoveryChangeWatcher watcher = watcherCaches.computeIfAbsent(serviceName, name -> {
ServiceCache serviceCache =
serviceDiscovery.serviceCacheBuilder().name(name).build();
ZookeeperServiceDiscoveryChangeWatcher newer =
new ZookeeperServiceDiscoveryChangeWatcher(this, serviceCache, name, latch);
serviceCache.addListener(newer);
try {
serviceCache.start();
} catch (Exception e) {
throw new RpcException(REGISTRY_EXCEPTION, "Failed subscribe service: " + name, e);
}
return newer;
});
watcher.addListener(listener);
listener.onEvent(new ServiceInstancesChangedEvent(serviceName, this.getInstances(serviceName)));
latch.countDown();
}
}