com.alibaba.nacos.naming.core.ServiceManager Maven / Gradle / Ivy
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed 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 com.alibaba.nacos.naming.core;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.alibaba.nacos.common.utils.InternetAddressUtil;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.core.cluster.Member;
import com.alibaba.nacos.core.cluster.ServerMemberManager;
import com.alibaba.nacos.naming.consistency.ConsistencyService;
import com.alibaba.nacos.naming.consistency.Datum;
import com.alibaba.nacos.naming.consistency.KeyBuilder;
import com.alibaba.nacos.naming.consistency.RecordListener;
import com.alibaba.nacos.naming.consistency.persistent.raft.RaftPeer;
import com.alibaba.nacos.naming.consistency.persistent.raft.RaftPeerSet;
import com.alibaba.nacos.naming.core.v2.cleaner.EmptyServiceAutoCleaner;
import com.alibaba.nacos.naming.misc.GlobalExecutor;
import com.alibaba.nacos.naming.misc.Loggers;
import com.alibaba.nacos.naming.misc.Message;
import com.alibaba.nacos.naming.misc.NetUtils;
import com.alibaba.nacos.naming.misc.ServiceStatusSynchronizer;
import com.alibaba.nacos.naming.misc.SwitchDomain;
import com.alibaba.nacos.naming.misc.Synchronizer;
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import com.alibaba.nacos.naming.pojo.InstanceOperationContext;
import com.alibaba.nacos.naming.pojo.InstanceOperationInfo;
import com.alibaba.nacos.naming.push.UdpPushService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.alibaba.nacos.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.alibaba.nacos.naming.misc.UtilsAndCommons.UPDATE_INSTANCE_METADATA_ACTION_REMOVE;
import static com.alibaba.nacos.naming.misc.UtilsAndCommons.UPDATE_INSTANCE_METADATA_ACTION_UPDATE;
/**
* Core manager storing all services in Nacos.
*
* @author nkorange
*/
@Component
public class ServiceManager implements RecordListener {
/**
* Map(namespace, Map(group::serviceName, Service)).
*/
private final Map> serviceMap = new ConcurrentHashMap<>();
private final LinkedBlockingDeque toBeUpdatedServicesQueue = new LinkedBlockingDeque<>(1024 * 1024);
private final Synchronizer synchronizer = new ServiceStatusSynchronizer();
@Resource(name = "consistencyDelegate")
private ConsistencyService consistencyService;
private final SwitchDomain switchDomain;
private final DistroMapper distroMapper;
private final ServerMemberManager memberManager;
private final UdpPushService pushService;
private final RaftPeerSet raftPeerSet;
@Value("${nacos.naming.empty-service.auto-clean:false}")
private boolean emptyServiceAutoClean;
@Value("${nacos.naming.empty-service.clean.initial-delay-ms:60000}")
private int cleanEmptyServiceDelay;
@Value("${nacos.naming.empty-service.clean.period-time-ms:20000}")
private int cleanEmptyServicePeriod;
public ServiceManager(SwitchDomain switchDomain, DistroMapper distroMapper, ServerMemberManager memberManager,
UdpPushService pushService, RaftPeerSet raftPeerSet) {
this.switchDomain = switchDomain;
this.distroMapper = distroMapper;
this.memberManager = memberManager;
this.pushService = pushService;
this.raftPeerSet = raftPeerSet;
}
/**
* Init service maneger.
*/
@PostConstruct
public void init() {
GlobalExecutor.scheduleServiceReporter(new ServiceReporter(), 60000, TimeUnit.MILLISECONDS);
GlobalExecutor.submitServiceUpdateManager(new UpdatedServiceProcessor());
if (emptyServiceAutoClean) {
Loggers.SRV_LOG.info("open empty service auto clean job, initialDelay : {} ms, period : {} ms",
cleanEmptyServiceDelay, cleanEmptyServicePeriod);
// delay 60s, period 20s;
// This task is not recommended to be performed frequently in order to avoid
// the possibility that the service cache information may just be deleted
// and then created due to the heartbeat mechanism
GlobalExecutor
.scheduleServiceAutoClean(new EmptyServiceAutoCleaner(this, distroMapper), cleanEmptyServiceDelay,
cleanEmptyServicePeriod);
}
try {
Loggers.SRV_LOG.info("listen for service meta change");
consistencyService.listen(KeyBuilder.SERVICE_META_KEY_PREFIX, this);
} catch (NacosException e) {
Loggers.SRV_LOG.error("listen for service meta change failed!");
}
}
public Map chooseServiceMap(String namespaceId) {
return serviceMap.get(namespaceId);
}
/**
* Add a service into queue to update.
*
* @param namespaceId namespace
* @param serviceName service name
* @param serverIP target server ip
* @param checksum checksum of service
*/
public synchronized void addUpdatedServiceToQueue(String namespaceId, String serviceName, String serverIP, String checksum) {
try {
toBeUpdatedServicesQueue
.offer(new ServiceKey(namespaceId, serviceName, serverIP, checksum), 5, TimeUnit.MILLISECONDS);
} catch (Exception e) {
toBeUpdatedServicesQueue.poll();
toBeUpdatedServicesQueue.add(new ServiceKey(namespaceId, serviceName, serverIP, checksum));
Loggers.SRV_LOG.error("[DOMAIN-STATUS] Failed to add service to be updated to queue.", e);
}
}
@Override
public boolean interests(String key) {
return KeyBuilder.matchServiceMetaKey(key) && !KeyBuilder.matchSwitchKey(key);
}
@Override
public boolean matchUnlistenKey(String key) {
return KeyBuilder.matchServiceMetaKey(key) && !KeyBuilder.matchSwitchKey(key);
}
@Override
public void onChange(String key, Service service) throws Exception {
try {
if (service == null) {
Loggers.SRV_LOG.warn("received empty push from raft, key: {}", key);
return;
}
if (StringUtils.isBlank(service.getNamespaceId())) {
service.setNamespaceId(Constants.DEFAULT_NAMESPACE_ID);
}
Loggers.RAFT.info("[RAFT-NOTIFIER] datum is changed, key: {}, value: {}", key, service);
Service oldDom = getService(service.getNamespaceId(), service.getName());
if (oldDom != null) {
oldDom.update(service);
// re-listen to handle the situation when the underlying listener is removed:
consistencyService
.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true),
oldDom);
consistencyService
.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false),
oldDom);
} else {
putServiceAndInit(service);
}
} catch (Throwable e) {
Loggers.SRV_LOG.error("[NACOS-SERVICE] error while processing service update", e);
}
}
@Override
public void onDelete(String key) throws Exception {
String namespace = KeyBuilder.getNamespace(key);
String name = KeyBuilder.getServiceName(key);
Service service = chooseServiceMap(namespace).get(name);
Loggers.RAFT.info("[RAFT-NOTIFIER] datum is deleted, key: {}", key);
if (service != null) {
cleanupService(namespace, name, service);
}
chooseServiceMap(namespace).remove(name);
}
private class UpdatedServiceProcessor implements Runnable {
//get changed service from other server asynchronously
@Override
public void run() {
ServiceKey serviceKey = null;
try {
while (true) {
try {
serviceKey = toBeUpdatedServicesQueue.take();
} catch (Exception e) {
Loggers.EVT_LOG.error("[UPDATE-DOMAIN] Exception while taking item from LinkedBlockingDeque.");
}
if (serviceKey == null) {
continue;
}
GlobalExecutor.submitServiceUpdate(new ServiceUpdater(serviceKey));
}
} catch (Exception e) {
Loggers.EVT_LOG.error("[UPDATE-DOMAIN] Exception while update service: {}", serviceKey, e);
}
}
}
private class ServiceUpdater implements Runnable {
String namespaceId;
String serviceName;
String serverIP;
public ServiceUpdater(ServiceKey serviceKey) {
this.namespaceId = serviceKey.getNamespaceId();
this.serviceName = serviceKey.getServiceName();
this.serverIP = serviceKey.getServerIP();
}
@Override
public void run() {
try {
updatedHealthStatus(namespaceId, serviceName, serverIP);
} catch (Exception e) {
Loggers.SRV_LOG
.warn("[DOMAIN-UPDATER] Exception while update service: {} from {}, error: {}", serviceName,
serverIP, e);
}
}
}
public RaftPeer getMySelfClusterState() {
return raftPeerSet.local();
}
/**
* Update health status of instance in service.
*
* @param namespaceId namespace
* @param serviceName service name
* @param serverIP source server Ip
*/
public void updatedHealthStatus(String namespaceId, String serviceName, String serverIP) {
Message msg = synchronizer.get(serverIP, UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));
JsonNode serviceJson = JacksonUtils.toObj(msg.getData());
ArrayNode ipList = (ArrayNode) serviceJson.get("ips");
Map ipsMap = new HashMap<>(ipList.size());
for (int i = 0; i < ipList.size(); i++) {
String ip = ipList.get(i).asText();
String[] strings = ip.split("_");
ipsMap.put(strings[0], strings[1]);
}
Service service = getService(namespaceId, serviceName);
if (service == null) {
return;
}
boolean changed = false;
List instances = service.allIPs();
for (Instance instance : instances) {
boolean valid = Boolean.parseBoolean(ipsMap.get(instance.toIpAddr()));
if (valid != instance.isHealthy()) {
changed = true;
instance.setHealthy(valid);
Loggers.EVT_LOG.info("{} {SYNC} IP-{} : {}:{}@{}", serviceName,
(instance.isHealthy() ? "ENABLED" : "DISABLED"), instance.getIp(), instance.getPort(),
instance.getClusterName());
}
}
if (changed) {
pushService.serviceChanged(service);
if (Loggers.EVT_LOG.isDebugEnabled()) {
StringBuilder stringBuilder = new StringBuilder();
List allIps = service.allIPs();
for (Instance instance : allIps) {
stringBuilder.append(instance.toIpAddr()).append('_').append(instance.isHealthy()).append(',');
}
Loggers.EVT_LOG
.debug("[HEALTH-STATUS-UPDATED] namespace: {}, service: {}, ips: {}", service.getNamespaceId(),
service.getName(), stringBuilder.toString());
}
}
}
public Set getAllServiceNames(String namespaceId) {
return serviceMap.get(namespaceId).keySet();
}
public Map> getAllServiceNames() {
Map> namesMap = new HashMap<>(16);
for (String namespaceId : serviceMap.keySet()) {
namesMap.put(namespaceId, serviceMap.get(namespaceId).keySet());
}
return namesMap;
}
public Set getAllNamespaces() {
return serviceMap.keySet();
}
public List getAllServiceNameList(String namespaceId) {
if (chooseServiceMap(namespaceId) == null) {
return new ArrayList<>();
}
return new ArrayList<>(chooseServiceMap(namespaceId).keySet());
}
public Map> getResponsibleServices() {
Map> result = new HashMap<>(16);
for (String namespaceId : serviceMap.keySet()) {
result.put(namespaceId, new HashSet<>());
for (Map.Entry entry : serviceMap.get(namespaceId).entrySet()) {
Service service = entry.getValue();
if (distroMapper.responsible(entry.getKey())) {
result.get(namespaceId).add(service);
}
}
}
return result;
}
public int getResponsibleServiceCount() {
int serviceCount = 0;
for (String namespaceId : serviceMap.keySet()) {
for (Map.Entry entry : serviceMap.get(namespaceId).entrySet()) {
if (distroMapper.responsible(entry.getKey())) {
serviceCount++;
}
}
}
return serviceCount;
}
public int getResponsibleInstanceCount() {
Map> responsibleServices = getResponsibleServices();
int count = 0;
for (String namespaceId : responsibleServices.keySet()) {
for (Service service : responsibleServices.get(namespaceId)) {
count += service.allIPs().size();
}
}
return count;
}
/**
* Fast remove service.
*
* Remove service bu async.
*
* @param namespaceId namespace
* @param serviceName service name
* @throws NacosException exception
*/
public void easyRemoveService(String namespaceId, String serviceName) throws NacosException {
Service service = getService(namespaceId, serviceName);
if (service == null) {
throw new NacosException(NacosException.INVALID_PARAM,
"specified service not exist, serviceName : " + serviceName);
}
consistencyService.remove(KeyBuilder.buildServiceMetaKey(namespaceId, serviceName));
}
public void addOrReplaceService(Service service) throws NacosException {
consistencyService.put(KeyBuilder.buildServiceMetaKey(service.getNamespaceId(), service.getName()), service);
}
public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {
createServiceIfAbsent(namespaceId, serviceName, local, null);
}
/**
* Create service if not exist.
*
* @param namespaceId namespace
* @param serviceName service name
* @param local whether create service by local
* @param cluster cluster
* @throws NacosException nacos exception
*/
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
throws NacosException {
Service service = getService(namespaceId, serviceName);
if (service == null) {
Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
service = new Service();
service.setName(serviceName);
service.setNamespaceId(namespaceId);
service.setGroupName(NamingUtils.getGroupName(serviceName));
// now validate the service. if failed, exception will be thrown
service.setLastModifiedMillis(System.currentTimeMillis());
service.recalculateChecksum();
if (cluster != null) {
cluster.setService(service);
service.getClusterMap().put(cluster.getName(), cluster);
}
service.validate();
putServiceAndInit(service);
if (!local) {
addOrReplaceService(service);
}
}
}
/**
* Register an instance to a service in AP mode.
*
*
This method creates service or cluster silently if they don't exist.
*
* @param namespaceId id of namespace
* @param serviceName service name
* @param instance instance to register
* @throws Exception any error occurred in the process
*/
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
Service service = getService(namespaceId, serviceName);
checkServiceIsNull(service, namespaceId, serviceName);
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
/**
* Update instance to service.
*
* @param namespaceId namespace
* @param serviceName service name
* @param instance instance
* @throws NacosException nacos exception
*/
public void updateInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
Service service = getService(namespaceId, serviceName);
checkServiceIsNull(service, namespaceId, serviceName);
if (!service.allIPs().contains(instance)) {
throw new NacosException(NacosException.INVALID_PARAM, "instance not exist: " + instance);
}
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
/**
* Update instance's metadata.
*
* @param namespaceId namespace
* @param serviceName service name
* @param action update or remove
* @param ips need update instances
* @param metadata target metadata
* @return update succeed instances
* @throws NacosException nacos exception
*/
public List updateMetadata(String namespaceId, String serviceName, boolean isEphemeral, String action,
boolean all, List ips, Map metadata) throws NacosException {
Service service = getService(namespaceId, serviceName);
checkServiceIsNull(service, namespaceId, serviceName);
List locatedInstance = getLocatedInstance(namespaceId, serviceName, isEphemeral, all, ips);
if (CollectionUtils.isEmpty(locatedInstance)) {
throw new NacosException(NacosException.INVALID_PARAM, "not locate instances, input instances: " + ips);
}
if (UPDATE_INSTANCE_METADATA_ACTION_UPDATE.equals(action)) {
locatedInstance.forEach(ele -> ele.getMetadata().putAll(metadata));
} else if (UPDATE_INSTANCE_METADATA_ACTION_REMOVE.equals(action)) {
Set removeKeys = metadata.keySet();
for (String removeKey : removeKeys) {
locatedInstance.forEach(ele -> ele.getMetadata().remove(removeKey));
}
}
Instance[] instances = new Instance[locatedInstance.size()];
locatedInstance.toArray(instances);
addInstance(namespaceId, serviceName, isEphemeral, instances);
return locatedInstance;
}
/**
* Check if the service is null.
*
* @param service service
* @param namespaceId namespace
* @param serviceName service name
* @throws NacosException nacos exception
*/
public void checkServiceIsNull(Service service, String namespaceId, String serviceName) throws NacosException {
if (service == null) {
throw new NacosException(NacosException.INVALID_PARAM,
"service not found, namespace: " + namespaceId + ", serviceName: " + serviceName);
}
}
/**
* Locate consistency's datum by all or instances provided.
*
* @param namespaceId namespace
* @param serviceName serviceName
* @param isEphemeral isEphemeral
* @param all get from consistencyService directly
* @param waitLocateInstance instances provided
* @return located instances
* @throws NacosException nacos exception
*/
public List getLocatedInstance(String namespaceId, String serviceName, boolean isEphemeral, boolean all,
List waitLocateInstance) throws NacosException {
List locatedInstance;
//need the newest data from consistencyService
Datum datum = consistencyService.get(KeyBuilder.buildInstanceListKey(namespaceId, serviceName, isEphemeral));
if (datum == null) {
throw new NacosException(NacosException.NOT_FOUND,
"instances from consistencyService not exist, namespace: " + namespaceId + ", service: "
+ serviceName + ", ephemeral: " + isEphemeral);
}
if (all) {
locatedInstance = ((Instances) datum.value).getInstanceList();
} else {
locatedInstance = new ArrayList<>();
for (Instance instance : waitLocateInstance) {
Instance located = locateInstance(((Instances) datum.value).getInstanceList(), instance);
if (located == null) {
continue;
}
locatedInstance.add(located);
}
}
return locatedInstance;
}
private Instance locateInstance(List sources, Instance target) {
if (CollectionUtils.isEmpty(sources)) {
return null;
}
for (Instance element : sources) {
//also need clusterName equals, the same instance maybe exist in two cluster.
if (Objects.equals(element, target) && Objects.equals(element.getClusterName(), target.getClusterName())) {
return element;
}
}
return null;
}
/**
* Add instance to service.
*
* @param namespaceId namespace
* @param serviceName service name
* @param ephemeral whether instance is ephemeral
* @param ips instances
* @throws NacosException nacos exception
*/
public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
throws NacosException {
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
Service service = getService(namespaceId, serviceName);
synchronized (service) {
List instanceList = addIpAddresses(service, ephemeral, ips);
Instances instances = new Instances();
instances.setInstanceList(instanceList);
consistencyService.put(key, instances);
}
}
/**
* Remove instance from service.
*
* @param namespaceId namespace
* @param serviceName service name
* @param ephemeral whether instance is ephemeral
* @param ips instances
* @throws NacosException nacos exception
*/
public void removeInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
throws NacosException {
Service service = getService(namespaceId, serviceName);
synchronized (service) {
removeInstance(namespaceId, serviceName, ephemeral, service, ips);
}
}
private void removeInstance(String namespaceId, String serviceName, boolean ephemeral, Service service,
Instance... ips) throws NacosException {
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
List instanceList = substractIpAddresses(service, ephemeral, ips);
Instances instances = new Instances();
instances.setInstanceList(instanceList);
consistencyService.put(key, instances);
}
public Instance getInstance(String namespaceId, String serviceName, String cluster, String ip, int port) {
Service service = getService(namespaceId, serviceName);
if (service == null) {
return null;
}
List clusters = new ArrayList<>();
clusters.add(cluster);
List ips = service.allIPs(clusters);
if (ips == null || ips.isEmpty()) {
return null;
}
for (Instance instance : ips) {
if (instance.getIp().equals(ip) && instance.getPort() == port) {
return instance;
}
}
return null;
}
/**
* batch operate kinds of resources.
*
* @param namespace namespace.
* @param operationInfo operation resources description.
* @param operateFunction some operation defined by kinds of situation.
*/
public List batchOperate(String namespace, InstanceOperationInfo operationInfo,
Function> operateFunction) {
List operatedInstances = new ArrayList<>();
try {
String serviceName = operationInfo.getServiceName();
NamingUtils.checkServiceNameFormat(serviceName);
// type: ephemeral/persist
InstanceOperationContext operationContext;
String type = operationInfo.getConsistencyType();
if (!StringUtils.isEmpty(type)) {
switch (type) {
case UtilsAndCommons.EPHEMERAL:
operationContext = new InstanceOperationContext(namespace, serviceName, true, true);
operatedInstances.addAll(operateFunction.apply(operationContext));
break;
case UtilsAndCommons.PERSIST:
operationContext = new InstanceOperationContext(namespace, serviceName, false, true);
operatedInstances.addAll(operateFunction.apply(operationContext));
break;
default:
Loggers.SRV_LOG
.warn("UPDATE-METADATA: services.all value is illegal, it should be ephemeral/persist. ignore the service '"
+ serviceName + "'");
break;
}
} else {
List instances = (List) operationInfo.getInstances();
if (!CollectionUtils.isEmpty(instances)) {
//ephemeral:instances or persist:instances
Map> instanceMap = instances.stream()
.collect(Collectors.groupingBy(ele -> ele.isEphemeral()));
for (Map.Entry> entry : instanceMap.entrySet()) {
operationContext = new InstanceOperationContext(namespace, serviceName, entry.getKey(), false,
entry.getValue());
operatedInstances.addAll(operateFunction.apply(operationContext));
}
}
}
} catch (Exception e) {
Loggers.SRV_LOG.warn("UPDATE-METADATA: update metadata failed, ignore the service '" + operationInfo
.getServiceName() + "'", e);
}
return operatedInstances;
}
/**
* Compare and get new instance list.
*
* @param service service
* @param action {@link UtilsAndCommons#UPDATE_INSTANCE_ACTION_REMOVE} or {@link UtilsAndCommons#UPDATE_INSTANCE_ACTION_ADD}
* @param ephemeral whether instance is ephemeral
* @param ips instances
* @return instance list after operation
* @throws NacosException nacos exception
*/
public List updateIpAddresses(Service service, String action, boolean ephemeral, Instance... ips)
throws NacosException {
Datum datum = consistencyService
.get(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), ephemeral));
List currentIPs = service.allIPs(ephemeral);
Map currentInstances = new HashMap<>(currentIPs.size());
Set currentInstanceIds = CollectionUtils.set();
for (Instance instance : currentIPs) {
currentInstances.put(instance.toIpAddr(), instance);
currentInstanceIds.add(instance.getInstanceId());
}
Map instanceMap;
if (datum != null && null != datum.value) {
instanceMap = setValid(((Instances) datum.value).getInstanceList(), currentInstances);
} else {
instanceMap = new HashMap<>(ips.length);
}
for (Instance instance : ips) {
if (!service.getClusterMap().containsKey(instance.getClusterName())) {
Cluster cluster = new Cluster(instance.getClusterName(), service);
cluster.init();
service.getClusterMap().put(instance.getClusterName(), cluster);
Loggers.SRV_LOG
.warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",
instance.getClusterName(), instance.toJson());
}
if (UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE.equals(action)) {
instanceMap.remove(instance.getDatumKey());
} else {
Instance oldInstance = instanceMap.get(instance.getDatumKey());
if (oldInstance != null) {
instance.setInstanceId(oldInstance.getInstanceId());
} else {
instance.setInstanceId(instance.generateInstanceId(currentInstanceIds));
}
instanceMap.put(instance.getDatumKey(), instance);
}
}
if (instanceMap.size() <= 0 && UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD.equals(action)) {
throw new IllegalArgumentException(
"ip list can not be empty, service: " + service.getName() + ", ip list: " + JacksonUtils
.toJson(instanceMap.values()));
}
return new ArrayList<>(instanceMap.values());
}
private List substractIpAddresses(Service service, boolean ephemeral, Instance... ips)
throws NacosException {
return updateIpAddresses(service, UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE, ephemeral, ips);
}
private List addIpAddresses(Service service, boolean ephemeral, Instance... ips) throws NacosException {
return updateIpAddresses(service, UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD, ephemeral, ips);
}
private Map setValid(List oldInstances, Map map) {
Map instanceMap = new HashMap<>(oldInstances.size());
for (Instance instance : oldInstances) {
Instance instance1 = map.get(instance.toIpAddr());
if (instance1 != null) {
instance.setHealthy(instance1.isHealthy());
instance.setLastBeat(instance1.getLastBeat());
}
instanceMap.put(instance.getDatumKey(), instance);
}
return instanceMap;
}
public Service getService(String namespaceId, String serviceName) {
if (serviceMap.get(namespaceId) == null) {
return null;
}
return chooseServiceMap(namespaceId).get(serviceName);
}
public boolean containService(String namespaceId, String serviceName) {
return getService(namespaceId, serviceName) != null;
}
/**
* Put service into manager.
*
* @param service service
*/
public void putService(Service service) {
if (!serviceMap.containsKey(service.getNamespaceId())) {
serviceMap.putIfAbsent(service.getNamespaceId(), new ConcurrentSkipListMap<>());
}
serviceMap.get(service.getNamespaceId()).putIfAbsent(service.getName(), service);
}
private void putServiceAndInit(Service service) throws NacosException {
putService(service);
service = getService(service.getNamespaceId(), service.getName());
service.init();
consistencyService
.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
consistencyService
.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJson());
}
/**
* Search services.
*
* @param namespaceId namespace
* @param regex search regex
* @return list of service which searched
*/
public List searchServices(String namespaceId, String regex) {
List result = new ArrayList<>();
for (Map.Entry entry : chooseServiceMap(namespaceId).entrySet()) {
Service service = entry.getValue();
String key = service.getName() + ":" + service.getOwners().toString();
if (key.matches(regex)) {
result.add(service);
}
}
return result;
}
public int getServiceCount() {
int serviceCount = 0;
for (String namespaceId : serviceMap.keySet()) {
serviceCount += serviceMap.get(namespaceId).size();
}
return serviceCount;
}
public int getInstanceCount() {
int total = 0;
for (String namespaceId : serviceMap.keySet()) {
for (Service service : serviceMap.get(namespaceId).values()) {
total += service.allIPs().size();
}
}
return total;
}
public int getPagedService(String namespaceId, int startPage, int pageSize, String param, String containedInstance,
List serviceList, boolean hasIpCount) {
List matchList;
if (chooseServiceMap(namespaceId) == null) {
return 0;
}
if (StringUtils.isNotBlank(param)) {
StringJoiner regex = new StringJoiner(Constants.SERVICE_INFO_SPLITER);
for (String s : param.split(Constants.SERVICE_INFO_SPLITER, Constants.SERVICE_INFO_SPLIT_COUNT)) {
regex.add(StringUtils.isBlank(s) ? Constants.ANY_PATTERN
: Constants.ANY_PATTERN + s + Constants.ANY_PATTERN);
}
matchList = searchServices(namespaceId, regex.toString());
} else {
matchList = new ArrayList<>(chooseServiceMap(namespaceId).values());
}
if (!CollectionUtils.isEmpty(matchList) && hasIpCount) {
matchList = matchList.stream().filter(s -> !CollectionUtils.isEmpty(s.allIPs()))
.collect(Collectors.toList());
}
if (StringUtils.isNotBlank(containedInstance)) {
boolean contained;
for (int i = 0; i < matchList.size(); i++) {
Service service = matchList.get(i);
contained = false;
List instances = service.allIPs();
for (Instance instance : instances) {
if (InternetAddressUtil.containsPort(containedInstance)) {
if (StringUtils.equals(instance.getIp() + InternetAddressUtil.IP_PORT_SPLITER + instance.getPort(),
containedInstance)) {
contained = true;
break;
}
} else {
if (StringUtils.equals(instance.getIp(), containedInstance)) {
contained = true;
break;
}
}
}
if (!contained) {
matchList.remove(i);
i--;
}
}
}
if (pageSize >= matchList.size()) {
serviceList.addAll(matchList);
return matchList.size();
}
for (int i = 0; i < matchList.size(); i++) {
if (i < startPage * pageSize) {
continue;
}
serviceList.add(matchList.get(i));
if (serviceList.size() >= pageSize) {
break;
}
}
return matchList.size();
}
/**
* Shut down service manager v1.x.
*
* @throws NacosException nacos exception during shutdown
*/
public void shutdown() throws NacosException {
try {
long start = System.nanoTime();
Loggers.SRV_LOG
.info("Start to destroy ALL services. namespaces: {}, services: {}", serviceMap.keySet().size(),
getServiceCount());
for (Iterator>> iterator = serviceMap.entrySet().iterator();
iterator.hasNext(); ) {
Map.Entry> entry = iterator.next();
destroyAllService(entry.getKey(), entry.getValue());
iterator.remove();
}
Loggers.SRV_LOG.info(String.format("Successfully destroy ALL services. costs %.2fms",
((float) (System.nanoTime() - start)) * 1e-6));
} catch (Exception e) {
throw new NacosException(NacosException.SERVER_ERROR, "shutdown serviceManager failed", e);
}
}
private void destroyAllService(String namespace, Map serviceMap) throws Exception {
for (Iterator> iterator = serviceMap.entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry entry = iterator.next();
Service service = entry.getValue();
String name = service.getName();
cleanupService(namespace, name, service);
iterator.remove();
}
}
private void cleanupService(String namespace, String name, Service service) throws Exception {
service.destroy();
String ephemeralInstanceListKey = KeyBuilder.buildInstanceListKey(namespace, name, true);
String persistInstanceListKey = KeyBuilder.buildInstanceListKey(namespace, name, false);
String serviceMetaKey = KeyBuilder.buildServiceMetaKey(namespace, name);
consistencyService.remove(ephemeralInstanceListKey);
consistencyService.remove(persistInstanceListKey);
consistencyService.remove(serviceMetaKey);
// remove listeners of key to avoid mem leak
consistencyService.unListen(ephemeralInstanceListKey, service);
consistencyService.unListen(persistInstanceListKey, service);
consistencyService.unListen(serviceMetaKey, service);
Loggers.SRV_LOG.info("[DEAD-SERVICE] {}", service.toJson());
}
public static class ServiceChecksum {
public String namespaceId;
public Map serviceName2Checksum = new HashMap();
public ServiceChecksum() {
this.namespaceId = Constants.DEFAULT_NAMESPACE_ID;
}
public ServiceChecksum(String namespaceId) {
this.namespaceId = namespaceId;
}
/**
* Add service checksum.
*
* @param serviceName service name
* @param checksum checksum of service
*/
public void addItem(String serviceName, String checksum) {
if (StringUtils.isEmpty(serviceName) || StringUtils.isEmpty(checksum)) {
Loggers.SRV_LOG.warn("[DOMAIN-CHECKSUM] serviceName or checksum is empty,serviceName: {}, checksum: {}",
serviceName, checksum);
return;
}
serviceName2Checksum.put(serviceName, checksum);
}
}
private class ServiceReporter implements Runnable {
@Override
public void run() {
try {
Map> allServiceNames = getAllServiceNames();
if (allServiceNames.size() <= 0) {
//ignore
return;
}
for (String namespaceId : allServiceNames.keySet()) {
ServiceChecksum checksum = new ServiceChecksum(namespaceId);
for (String serviceName : allServiceNames.get(namespaceId)) {
if (!distroMapper.responsible(serviceName)) {
continue;
}
Service service = getService(namespaceId, serviceName);
if (service == null || service.isEmpty()) {
continue;
}
service.recalculateChecksum();
checksum.addItem(serviceName, service.getChecksum());
}
Message msg = new Message();
msg.setData(JacksonUtils.toJson(checksum));
Collection sameSiteServers = memberManager.allMembers();
if (sameSiteServers == null || sameSiteServers.size() <= 0) {
return;
}
for (Member server : sameSiteServers) {
if (server.getAddress().equals(NetUtils.localServer())) {
continue;
}
synchronizer.send(server.getAddress(), msg);
}
}
} catch (Exception e) {
Loggers.SRV_LOG.error("[DOMAIN-STATUS] Exception while sending service status", e);
} finally {
GlobalExecutor.scheduleServiceReporter(this, switchDomain.getServiceStatusSynchronizationPeriodMillis(),
TimeUnit.MILLISECONDS);
}
}
}
private static class ServiceKey {
private String namespaceId;
private String serviceName;
private String serverIP;
private String checksum;
public String getChecksum() {
return checksum;
}
public String getServerIP() {
return serverIP;
}
public String getServiceName() {
return serviceName;
}
public String getNamespaceId() {
return namespaceId;
}
public ServiceKey(String namespaceId, String serviceName, String serverIP, String checksum) {
this.namespaceId = namespaceId;
this.serviceName = serviceName;
this.serverIP = serverIP;
this.checksum = checksum;
}
@Override
public String toString() {
return JacksonUtils.toJson(this);
}
}
}