All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tencent.trpc.registry.center.AbstractFailedRetryRegistryCenter Maven / Gradle / Ivy

There is a newer version: 1.4.0
Show newest version
/*
 * Tencent is pleased to support the open source community by making tRPC available.
 *
 * Copyright (C) 2023 THL A29 Limited, a Tencent company. 
 * All rights reserved.
 *
 * If you have downloaded a copy of the tRPC source code from Tencent,
 * please note that tRPC source code is licensed under the Apache 2.0 License,
 * A copy of the Apache 2.0 License can be found in the LICENSE file.
 */

package com.tencent.trpc.registry.center;

import static com.tencent.trpc.registry.common.Constants.TICKS_PER_WHEEL;

import com.tencent.trpc.core.common.NamedThreadFactory;
import com.tencent.trpc.core.common.config.PluginConfig;
import com.tencent.trpc.core.common.timer.HashedWheelTimer;
import com.tencent.trpc.core.exception.TRpcExtensionException;
import com.tencent.trpc.core.logger.Logger;
import com.tencent.trpc.core.logger.LoggerFactory;
import com.tencent.trpc.core.registry.RegisterInfo;
import com.tencent.trpc.registry.common.ConfigConstants;
import com.tencent.trpc.registry.common.RegistryCenterListenerSet;
import com.tencent.trpc.registry.task.AbstractRetryTask;
import com.tencent.trpc.registry.task.RetryNotifyTask;
import com.tencent.trpc.registry.task.RetryRegisterTask;
import com.tencent.trpc.registry.task.RetrySubscribeTask;
import com.tencent.trpc.registry.task.RetryUnregisterTask;
import com.tencent.trpc.registry.task.RetryUnsubscribeTask;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;

/**
 * An abstract class for registry center that encapsulates the operation of failure retry.
 * 

Based on {@link AbstractRegistryCenter}, this class encapsulates the logic of failure retry. * Subclasses can directly inherit and implement the corresponding methods. * The underlying uses the HashedWheelTimer time wheel algorithm to schedule retry tasks efficiently using threads, but * the accuracy is not very high. * The default retry interval is {@link ConfigConstants#DEFAULT_REGISTRY_CENTER_RETRY_PERIOD_MS}. * Here, local optimization is done. Considering that a large number of retries of multiple services in a short period * of time may put pressure on the registry center, * a random number is added, and the actual retry time is: retry interval + random.nextInt(retry interval) ms. * Later, it can be considered to continuously double the retry time when multiple failures reach the threshold within a * certain period of time.

*

Interfaces that subclasses must implement:

*
1. {@link #init()}: Initialization interface
*
2. {@link #doRegister(RegisterInfo)}: Interface for registering services
*
3. {@link #doUnregister(RegisterInfo)}: Interface for unregistering services
*
4. {@link #subscribe(RegisterInfo, NotifyListener)}: Interface for subscribing to services
*
5. {@link #unsubscribe(RegisterInfo, NotifyListener)}: Interface for unsubscribing from services
*/ public abstract class AbstractFailedRetryRegistryCenter extends AbstractRegistryCenter { private static final Logger logger = LoggerFactory.getLogger(AbstractFailedRetryRegistryCenter.class); /** * Cache for retry tasks. */ private final ConcurrentMap failedTasks = new ConcurrentHashMap<>(); /** * Task scheduler based on time wheel. */ private HashedWheelTimer retryTimer; /** * Random interval for task retry interval. */ private Random random = new Random(); @Override public void setPluginConfig(PluginConfig pluginConfig) throws TRpcExtensionException { super.setPluginConfig(pluginConfig); this.retryTimer = new HashedWheelTimer( new NamedThreadFactory("TrpcFailedRegistryRetryTimer", true), this.config.getRetryPeriod(), TimeUnit.MILLISECONDS, TICKS_PER_WHEEL); } /** * Register the service and remove the retry task from the failure retry task. * * @param registerInfo The service to be registered. */ @Override public void register(RegisterInfo registerInfo) { super.register(registerInfo); removeFailedRegisteredTask(registerInfo); removeFailedUnregisteredTask(registerInfo); try { doRegister(registerInfo); } catch (Exception e) { logger.warn("Failed to register registerInfo: {}, and waiting to retry. Cause: ", registerInfo, e); addFailedRegisteredTask(registerInfo); } } /** * Unregister the service and remove the retry task from the failure retry task. * * @param registerInfo The service to be unregistered. */ @Override public void unregister(RegisterInfo registerInfo) { super.unregister(registerInfo); removeFailedRegisteredTask(registerInfo); removeFailedUnregisteredTask(registerInfo); try { doUnregister(registerInfo); } catch (Exception e) { logger.warn("Failed to unregister registerInfo: {}, and waiting to retry. Cause: ", registerInfo, e); addFailedUnregisteredTask(registerInfo); } } /** * Subscribe to the service and remove it from the failure retry task. * If the operation fails here, it will try to read the subscribed data from the cached data. * * @param registerInfo The service to be subscribed. * @param notifyListener The listener for the service operation. */ @Override public void subscribe(RegisterInfo registerInfo, NotifyListener notifyListener) { super.subscribe(registerInfo, notifyListener); removeFailedSubscribedTask(registerInfo, notifyListener); removeFailedUnsubscribedTask(registerInfo, notifyListener); removeFailedNotifyTask(registerInfo, notifyListener); try { doSubscribe(registerInfo, notifyListener); } catch (Exception e) { // Read cached data. List registerInfos = cache.getRegisterInfos(registerInfo.getServiceName()); if (!registerInfos.isEmpty()) { notify(registerInfo, notifyListener, registerInfos); logger.warn("Failed to subscribe registerInfo: {}, notifyListener: {}, and using cache: {}, Cause: ", registerInfo, notifyListener, registerInfos, e); } else { logger.warn("Failed to subscribe registerInfo: {}, notifyListener: {}, and waiting to retry. Cause: ", registerInfo, notifyListener, e); } addFailedSubscribedTask(registerInfo, notifyListener); } } /** * Unsubscribe from the service and remove it from the failure retry task. * * @param registerInfo The service to be unsubscribed. * @param notifyListener The listener for the service operation. */ @Override public void unsubscribe(RegisterInfo registerInfo, NotifyListener notifyListener) { super.unsubscribe(registerInfo, notifyListener); removeFailedSubscribedTask(registerInfo, notifyListener); removeFailedUnsubscribedTask(registerInfo, notifyListener); removeFailedNotifyTask(registerInfo, notifyListener); try { doUnsubscribe(registerInfo, notifyListener); } catch (Exception e) { logger.warn("Failed to unsubscribe registerInfo: {}, notifyListener: {}, and waiting to retry. Cause: ", registerInfo, notifyListener, e); addFailedUnsubscribedTask(registerInfo, notifyListener); } } /** * Callback interface for service subscription when data changes. * * @param registerInfo The subscribed service. * @param notifyListener The listener for the service. * @param updatingRegisterInfos The data updated by the subscribed service. */ @Override public void notify(RegisterInfo registerInfo, NotifyListener notifyListener, List updatingRegisterInfos) { Objects.requireNonNull(registerInfo, "registerInfo can not be null"); Objects.requireNonNull(notifyListener, "notifyListener can not be null"); Objects.requireNonNull(updatingRegisterInfos, "updatingRegisterInfos can not be null"); try { doNotify(registerInfo, notifyListener, updatingRegisterInfos); } catch (Exception e) { logger.warn("Failed to notify registerInfo: {}, notifyListener: {}, and waiting to retry. Cause: ", registerInfo, notifyListener, e); addFailedNotifiedTask(registerInfo, notifyListener, updatingRegisterInfos); } } /** * Add a retry task for registering the service. * * @param registerInfo The service to be operated on. */ public void addFailedRegisteredTask(RegisterInfo registerInfo) { this.addFailedTask(new RegisterInfoListenerHolder(registerInfo, null), new RetryRegisterTask(this, registerInfo)); } /** * Remove the retry task for registering the service. * * @param registerInfo The service to be operated on. */ public void removeFailedRegisteredTask(RegisterInfo registerInfo) { this.removeFailedTask(new RegisterInfoListenerHolder(registerInfo, null)); } /** * Add a retry task for unregistering the service. * * @param registerInfo The service to be operated on. */ public void addFailedUnregisteredTask(RegisterInfo registerInfo) { this.addFailedTask(new RegisterInfoListenerHolder(registerInfo, null), new RetryUnregisterTask(this, registerInfo)); } /** * Remove the retry task for unregistering the service. * * @param registerInfo The service to be operated on. */ public void removeFailedUnregisteredTask(RegisterInfo registerInfo) { this.removeFailedTask(new RegisterInfoListenerHolder(registerInfo, null)); } /** * Add a retry task for subscribing to the service. * * @param registerInfo The service to be operated on. * @param notifyListener The listener for the service operation. */ public void addFailedSubscribedTask(RegisterInfo registerInfo, NotifyListener notifyListener) { this.addFailedTask(new RegisterInfoListenerHolder(registerInfo, notifyListener), new RetrySubscribeTask(this, registerInfo, notifyListener)); } /** * Remove the retry task for subscribing to the service. * * @param registerInfo The service to be operated on. * @param notifyListener The listener for the service operation. */ public void removeFailedSubscribedTask(RegisterInfo registerInfo, NotifyListener notifyListener) { this.removeFailedTask(new RegisterInfoListenerHolder(registerInfo, notifyListener)); } /** * Add a retry task for unsubscribing from the service. * * @param registerInfo The service to be operated on. * @param notifyListener The listener for the service operation. */ public void addFailedUnsubscribedTask(RegisterInfo registerInfo, NotifyListener notifyListener) { this.addFailedTask(new RegisterInfoListenerHolder(registerInfo, notifyListener), new RetryUnsubscribeTask(this, registerInfo, notifyListener)); } /** * Remove the retry task for unsubscribing from the service. * * @param registerInfo The service to be operated on. * @param notifyListener The listener for the service operation. */ public void removeFailedUnsubscribedTask(RegisterInfo registerInfo, NotifyListener notifyListener) { this.removeFailedTask(new RegisterInfoListenerHolder(registerInfo, notifyListener)); } /** * Add a retry task for notifying the callback of a subscribed service. * * @param registerInfo The subscribed service. * @param notifyListener The listener for the service operation. * @param updatingRegisterInfos The data that needs to be updated for the subscribed service. */ public void addFailedNotifiedTask(RegisterInfo registerInfo, NotifyListener notifyListener, List updatingRegisterInfos) { RegisterInfoListenerHolder holder = new RegisterInfoListenerHolder(registerInfo, notifyListener); RetryNotifyTask newTask = new RetryNotifyTask(this, registerInfo, notifyListener); newTask.addRegisterInfoToRetry(updatingRegisterInfos); RetryNotifyTask oldTask = (RetryNotifyTask) failedTasks.putIfAbsent(holder, newTask); if (oldTask == null) { retryTimer.newTimeout(newTask, this.config.getRetryPeriod(), TimeUnit.MILLISECONDS); } } /** * Remove the retry task for notifying the callback of a subscribed service. * * @param registerInfo The service to be operated on. * @param notifyListener The listener for the service operation. */ public void removeFailedNotifyTask(RegisterInfo registerInfo, NotifyListener notifyListener) { this.removeFailedTask(new RegisterInfoListenerHolder(registerInfo, notifyListener)); } /** * Unsubscribe and unregister all services. Automatically called when the framework exits. */ @Override public void destroy() { super.destroy(); retryTimer.stop(); } /** * Get the actual retry time for a retry task. Add or subtract a random number to avoid putting too much pressure on * the registry center when multiple services retry frequently in a short period of time. * * @return The retry time for the retry task. */ public int getRealRetryPeriod() { return this.config.getRetryPeriod() + this.random.nextInt(this.config.getRetryPeriod()); } public ConcurrentMap getFailedTasks() { return failedTasks; } /** * Service registration operation that needs to be implemented by subclasses. * * @param registerInfo The service to be registered. */ public abstract void doRegister(RegisterInfo registerInfo); /** * Service unregistration operation that needs to be implemented by subclasses. * * @param registerInfo The service to be unregistered. */ public abstract void doUnregister(RegisterInfo registerInfo); /** * Service subscription operation that needs to be implemented by subclasses. * * @param registerInfo The service to be subscribed. * @param notifyListener The listener for the service. */ public abstract void doSubscribe(RegisterInfo registerInfo, NotifyListener notifyListener); /** * Service unsubscription operation that needs to be implemented by subclasses. * * @param registerInfo The service to be unsubscribed. * @param notifyListener The listener for the service. */ public abstract void doUnsubscribe(RegisterInfo registerInfo, NotifyListener notifyListener); /** * Callback interface for service subscription when data changes. Generally, it does not need to be overridden. * * @param registerInfo The subscribed service. * @param notifyListener The listener for the service. * @param updatingRegisterInfos The updated data for the subscribed service. */ public void doNotify(RegisterInfo registerInfo, NotifyListener notifyListener, List updatingRegisterInfos) { super.notify(registerInfo, notifyListener, updatingRegisterInfos); } /** * Restore registered services. */ @Override protected void recoverRegistered() { Set recoverRegistered = getRegisteredRegisterInfos(); if (CollectionUtils.isEmpty(recoverRegistered)) { return; } recoverRegistered.forEach(registerInfo -> { logger.debug("[Recover] Register registerInfo: {}", registerInfo); addFailedRegisteredTask(registerInfo); }); } /** * Restore subscribed services. */ @Override protected void recoverSubscribed() { Map recoverSubscribed = getSubscribedRegisterInfos(); if (MapUtils.isEmpty(recoverSubscribed)) { return; } recoverSubscribed.forEach((registerInfo, registryCenterListenerSet) -> registryCenterListenerSet.getNotifyListeners().forEach(notifyListener -> { logger.debug("[Recover] Subscribe registerInfo: {}, listener: {}", registerInfo, notifyListener); addFailedSubscribedTask(registerInfo, notifyListener); }) ); } /** * Add a retry task for use when subscribing/unsubscribing. When the task does not exist in the map, create a new * task and start it. * * @param registerInfoListenerHolder The service and its listener for the operation. * @param newTask The retry task. */ private void addFailedTask(RegisterInfoListenerHolder registerInfoListenerHolder, AbstractRetryTask newTask) { AbstractRetryTask oldTask = failedTasks.get(registerInfoListenerHolder); if (oldTask != null) { return; } oldTask = failedTasks.putIfAbsent(registerInfoListenerHolder, newTask); if (oldTask == null) { retryTimer.newTimeout(newTask, this.getRealRetryPeriod(), TimeUnit.MILLISECONDS); } } /** * Remove a retry task for use when registering/unsubscribing. * * @param registerInfoListenerHolder The service and its listener for the operation. */ private void removeFailedTask(RegisterInfoListenerHolder registerInfoListenerHolder) { AbstractRetryTask abstractRetryTask = failedTasks.remove(registerInfoListenerHolder); if (abstractRetryTask != null) { abstractRetryTask.cancel(); } } /** * Binding holder for the service and its listener for subscription, only for binding relationship management. It * has no practical use. */ static class RegisterInfoListenerHolder { private final RegisterInfo registerInfo; private final NotifyListener notifyListener; RegisterInfoListenerHolder(RegisterInfo url, NotifyListener notifyListener) { Objects.requireNonNull(url, "url of RegisterInfo is null"); this.registerInfo = url; this.notifyListener = notifyListener; } public RegisterInfo getRegisterInfo() { return registerInfo; } public NotifyListener getNotifyListener() { return notifyListener; } @Override public int hashCode() { final int prime = 31; int result = 1; result = result * prime + registerInfo.hashCode(); result = result * prime + ((null == notifyListener) ? 0 : notifyListener.hashCode()); return result; } @Override public boolean equals(Object obj) { if (obj instanceof RegisterInfoListenerHolder) { RegisterInfoListenerHolder h = (RegisterInfoListenerHolder) obj; if (this.notifyListener == null && h.notifyListener == null) { return this.registerInfo.equals(h.registerInfo); } if (this.notifyListener != null && h.notifyListener != null) { return this.registerInfo.equals(h.registerInfo) && this.notifyListener .equals(h.notifyListener); } } return false; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy