/* * Copyright 2009-2016 Weibo, Inc. * * 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.networknt.registry.support; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import com.networknt.registry.URLParamType; import com.networknt.exception.FrameworkException; import com.networknt.registry.NotifyListener; import com.networknt.registry.URL; import com.networknt.status.Status; import com.networknt.utility.ConcurrentHashSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * Failback registry * * @author fishermen */ public abstract class FailbackRegistry extends AbstractRegistry { private final static Logger logger = LoggerFactory.getLogger(FailbackRegistry.class); private static final String REGISTER_ERROR = "ERR10020"; private static final String UNREGISTER_ERROR = "ERR10021"; private static final String SUBSCRIBE_ERROR = "ERR10022"; private static final String UNSUBSCRIBE_ERROR = "ERR10023"; private Set<URL> failedRegistered = new ConcurrentHashSet<>(); private Set<URL> failedUnregistered = new ConcurrentHashSet<>(); private ConcurrentHashMap<URL, ConcurrentHashSet<NotifyListener>> failedSubscribed = new ConcurrentHashMap<>(); private ConcurrentHashMap<URL, ConcurrentHashSet<NotifyListener>> failedUnsubscribed = new ConcurrentHashMap<>(); private static ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(1); public FailbackRegistry(URL url) { super(url); long retryPeriod = url.getIntParameter(URLParamType.registryRetryPeriod.getName(), URLParamType.registryRetryPeriod.getIntValue()); retryExecutor.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { retry(); } catch (Exception e) { logger.error(String.format("[%s] False when retry in failback registry", registryClassName), e); } } }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS); } @Override public void register(URL url) { failedRegistered.remove(url); failedUnregistered.remove(url); try { super.register(url); } catch (Exception e) { if (isCheckingUrls(getUrl(), url)) { throw new FrameworkException(new Status(REGISTER_ERROR, registryClassName, url, getUrl()), e); } failedRegistered.add(url); } } @Override public void unregister(URL url) { failedRegistered.remove(url); failedUnregistered.remove(url); try { super.unregister(url); } catch (Exception e) { if (isCheckingUrls(getUrl(), url)) { throw new FrameworkException(new Status(UNREGISTER_ERROR, registryClassName, url, getUrl()), e); } failedUnregistered.add(url); } } @Override public void subscribe(URL url, NotifyListener listener) { removeForFailedSubAndUnsub(url, listener); try { super.subscribe(url, listener); } catch (Exception e) { List<URL> cachedUrls = getCachedUrls(url); if (cachedUrls != null && cachedUrls.size() > 0) { listener.notify(getUrl(), cachedUrls); } else if (isCheckingUrls(getUrl(), url)) { logger.error(String.format("[%s] false to subscribe %s from %s", registryClassName, url, getUrl()), e); throw new FrameworkException(new Status(SUBSCRIBE_ERROR, registryClassName, url, getUrl()), e); } addToFailedMap(failedSubscribed, url, listener); } } @Override public void unsubscribe(URL url, NotifyListener listener) { removeForFailedSubAndUnsub(url, listener); try { super.unsubscribe(url, listener); } catch (Exception e) { if (isCheckingUrls(getUrl(), url)) { throw new FrameworkException(new Status(UNSUBSCRIBE_ERROR, registryClassName, url, getUrl()), e); } addToFailedMap(failedUnsubscribed, url, listener); } } @Override @SuppressWarnings("unchecked") public List<URL> discover(URL url) { try { return super.discover(url); } catch (Exception e) { // If discover fails, return an empty list logger.error(String.format("Failed to discover url:%s in registry (%s)", url, getUrl()), e); return Collections.EMPTY_LIST; } } private boolean isCheckingUrls(URL... urls) { for (URL url : urls) { if (!Boolean.parseBoolean(url.getParameter(URLParamType.check.getName(), URLParamType.check.getValue()))) { return false; } } return true; } private void removeForFailedSubAndUnsub(URL url, NotifyListener listener) { Set<NotifyListener> listeners = failedSubscribed.get(url); if (listeners != null) { listeners.remove(listener); } listeners = failedUnsubscribed.get(url); if (listeners != null) { listeners.remove(listener); } } private void addToFailedMap(ConcurrentHashMap<URL, ConcurrentHashSet<NotifyListener>> failedMap, URL url, NotifyListener listener) { Set<NotifyListener> listeners = failedMap.get(url); if (listeners == null) { failedMap.putIfAbsent(url, new ConcurrentHashSet<>()); listeners = failedMap.get(url); } listeners.add(listener); } private void retry() { if (!failedRegistered.isEmpty()) { Set<URL> failed = new HashSet<>(failedRegistered); logger.info("[{}] Retry register {}", registryClassName, failed); try { for (URL url : failed) { super.register(url); failedRegistered.remove(url); } } catch (Exception e) { logger.error(String.format("[%s] Failed to retry register, retry later, failedRegistered.size=%s, cause=%s", registryClassName, failedRegistered.size(), e.getMessage()), e); } } if (!failedUnregistered.isEmpty()) { Set<URL> failed = new HashSet<>(failedUnregistered); logger.info("[{}] Retry unregister {}", registryClassName, failed); try { for (URL url : failed) { super.unregister(url); failedUnregistered.remove(url); } } catch (Exception e) { logger.error(String.format("[%s] Failed to retry unregister, retry later, failedUnregistered.size=%s, cause=%s", registryClassName, failedUnregistered.size(), e.getMessage()), e); } } if (!failedSubscribed.isEmpty()) { Map<URL, Set<NotifyListener>> failed = new HashMap<>(failedSubscribed); for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<>(failed).entrySet()) { if (entry.getValue() == null || entry.getValue().size() == 0) { failed.remove(entry.getKey()); } } if (failed.size() > 0) { logger.info("[{}] Retry subscribe {}", registryClassName, failed); try { for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) { URL url = entry.getKey(); Set<NotifyListener> listeners = entry.getValue(); for (NotifyListener listener : listeners) { super.subscribe(url, listener); listeners.remove(listener); } } } catch (Exception e) { logger.error(String.format("[%s] Failed to retry subscribe, retry later, failedSubscribed.size=%s, cause=%s", registryClassName, failedSubscribed.size(), e.getMessage()), e); } } } if (!failedUnsubscribed.isEmpty()) { Map<URL, Set<NotifyListener>> failed = new HashMap<>(failedUnsubscribed); for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<>(failed).entrySet()) { if (entry.getValue() == null || entry.getValue().size() == 0) { failed.remove(entry.getKey()); } } if (failed.size() > 0) { logger.info("[{}] Retry unsubscribe {}", registryClassName, failed); try { for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) { URL url = entry.getKey(); Set<NotifyListener> listeners = entry.getValue(); for (NotifyListener listener : listeners) { super.unsubscribe(url, listener); listeners.remove(listener); } } } catch (Exception e) { logger.error(String.format("[%s] Failed to retry unsubscribe, retry later, failedUnsubscribed.size=%s, cause=%s", registryClassName, failedUnsubscribed.size(), e.getMessage()), e); } } } } }