diff --git a/api/src/main/java/com/alibaba/nacos/api/common/Constants.java b/api/src/main/java/com/alibaba/nacos/api/common/Constants.java index 509972f07f0..c903a54ba2d 100644 --- a/api/src/main/java/com/alibaba/nacos/api/common/Constants.java +++ b/api/src/main/java/com/alibaba/nacos/api/common/Constants.java @@ -99,10 +99,6 @@ public class Constants { public static final Integer CLUSTER_GRPC_PORT_DEFAULT_OFFSET = 1001; - public static final String NAMESPACE_ID_SPLITTER = ">>"; - - public static final String DATA_ID_SPLITTER = "@@"; - /** * second. */ @@ -212,8 +208,6 @@ public class Constants { public static final String ALL_PATTERN = "*"; - public static final String FUZZY_LISTEN_PATTERN_WILDCARD = "*"; - public static final String COLON = ":"; public static final String LINE_BREAK = "\n"; @@ -237,16 +231,6 @@ public class Constants { public static final int DEFAULT_REDO_THREAD_COUNT = 1; - public static class ConfigChangeType { - - public static final String ADD_CONFIG = "ADD_CONFIG"; - - public static final String DELETE_CONFIG = "DELETE_CONFIG"; - - public static final String FINISH_LISTEN_INIT = "FINISH_LISTEN_INIT"; - - public static final String LISTEN_INIT = "LISTEN_INIT"; - } public static final String APP_CONN_LABELS_KEY = "nacos.app.conn.labels"; public static final String DOT = "."; @@ -292,6 +276,66 @@ public static class Naming { public static final String CMDB_CONTEXT_TYPE = "CMDB"; } + public static final String FUZZY_WATCH_PATTERN_SPLITTER = ">>"; + + /** + * fuzzy watch sync type of watch init notify. + */ + public static final String FUZZY_WATCH_INIT_NOTIFY = "FUZZY_WATCH_INIT_NOTIFY"; + + /** + * fuzzy watch sync type of watch init notify finish. + */ + public static final String FINISH_FUZZY_WATCH_INIT_NOTIFY = "FINISH_FUZZY_WATCH_INIT_NOTIFY"; + + /** + * fuzzy watch sync type of watch diff sync notify. + */ + public static final String FUZZY_WATCH_DIFF_SYNC_NOTIFY = "FUZZY_WATCH_DIFF_SYNC_NOTIFY"; + + /** + * fuzzy watch sync type of watch resource changed. + */ + public static final String FUZZY_WATCH_RESOURCE_CHANGED = "FUZZY_WATCH_RESOURCE_CHANGED"; + + /** + * watch type of watch. + */ + public static final String WATCH_TYPE_WATCH = "WATCH"; + + /** + * watch type of cancel watch. + */ + public static final String WATCH_TYPE_CANCEL_WATCH = "CANCEL_WATCH"; + + /** + * The constants in config fuzzy watch changed type directory. + */ + public static class ConfigChangedType { + + public static final String ADD_CONFIG = "ADD_CONFIG"; + + public static final String DELETE_CONFIG = "DELETE_CONFIG"; + + public static final String CONFIG_CHANGED = "CONFIG_CHANGED"; + + } + + /** + * The constants in naming fuzzy watch changed type directory. + */ + public static class ServiceChangedType { + + public static final String ADD_SERVICE = "ADD_SERVICE"; + + public static final String DELETE_SERVICE = "DELETE_SERVICE"; + + public static final String INSTANCE_CHANGED = "INSTANCE_CHANGED"; + + public static final String HEART_BEAT = "HEART_BEAT"; + + } + /** * The constants in remote directory. */ diff --git a/api/src/main/java/com/alibaba/nacos/api/config/ConfigService.java b/api/src/main/java/com/alibaba/nacos/api/config/ConfigService.java index a1017860f61..2c6699a1392 100644 --- a/api/src/main/java/com/alibaba/nacos/api/config/ConfigService.java +++ b/api/src/main/java/com/alibaba/nacos/api/config/ConfigService.java @@ -17,12 +17,12 @@ package com.alibaba.nacos.api.config; import com.alibaba.nacos.api.config.filter.IConfigFilter; -import com.alibaba.nacos.api.config.listener.AbstractFuzzyListenListener; +import com.alibaba.nacos.api.config.listener.FuzzyWatchEventWatcher; import com.alibaba.nacos.api.config.listener.Listener; import com.alibaba.nacos.api.exception.NacosException; -import java.util.Collection; -import java.util.concurrent.CompletableFuture; +import java.util.Set; +import java.util.concurrent.Future; /** * Config Service Interface. @@ -63,8 +63,8 @@ String getConfigAndSignListener(String dataId, String group, long timeoutMs, Lis /** * Add a listener to the configuration, after the server modified the configuration, the client will use the * incoming listener callback. Recommended asynchronous processing, the application can implement the getExecutor - * method in the ManagerListener, provide a thread pool of execution. If not provided, use the main thread callback, - * May block other configurations or be blocked by other configurations. + * method in the ManagerListener, provide a thread pool of execution. If not provided, use the main thread callback, May + * block other configurations or be blocked by other configurations. * * @param dataId dataId * @param group group @@ -73,78 +73,6 @@ String getConfigAndSignListener(String dataId, String group, long timeoutMs, Lis */ void addListener(String dataId, String group, Listener listener) throws NacosException; - /** - * Add a fuzzy listener to the configuration. After the server modifies the configuration matching the specified - * fixed group name, the client will utilize the incoming fuzzy listener callback. Fuzzy listeners allow for - * pattern-based subscription to configurations, where the fixed group name represents the group and dataId patterns - * specified for subscription. - * - * @param fixedGroupName The fixed group name representing the group and dataId patterns to subscribe to. - * @param listener The fuzzy listener to be added. - * @throws NacosException NacosException - */ - void addFuzzyListener(String fixedGroupName, AbstractFuzzyListenListener listener) throws NacosException; - - /** - * Add a fuzzy listener to the configuration. After the server modifies the configuration matching the specified - * dataId pattern and fixed group name, the client will utilize the incoming fuzzy listener callback. Fuzzy - * listeners allow for pattern-based subscription to configurations. - * - * @param dataIdPattern The pattern to match dataIds for subscription. - * @param fixedGroupName The fixed group name representing the group and dataId patterns to subscribe to. - * @param listener The fuzzy listener to be added. - * @throws NacosException NacosException - */ - void addFuzzyListener(String dataIdPattern, String fixedGroupName, AbstractFuzzyListenListener listener) - throws NacosException; - - /** - * Add a fuzzy listener to the configuration and retrieve all configs that match the specified fixed group name. - * Fuzzy listeners allow for pattern-based subscription to configs, where the fixed group name represents the group - * and dataId patterns specified for subscription. - * - * @param fixedGroupName The fixed group name representing the group and dataId patterns to subscribe to. - * @param listener The fuzzy listener to be added. - * @return CompletableFuture containing collection of configs that match the specified fixed group name. - * @throws NacosException NacosException - */ - CompletableFuture> addFuzzyListenerAndGetConfigs(String fixedGroupName, - AbstractFuzzyListenListener listener) throws NacosException; - - /** - * Add a fuzzy listener to the configuration and retrieve all configs that match the specified dataId pattern and - * fixed group name. Fuzzy listeners allow for pattern-based subscription to configs. - * - * @param dataIdPattern The pattern to match dataIds for subscription. - * @param fixedGroupName The fixed group name representing the group and dataId patterns to subscribe to. - * @param listener The fuzzy listener to be added. - * @return CompletableFuture containing collection of configs that match the specified dataId pattern and fixed - * group name. - * @throws NacosException NacosException - */ - CompletableFuture> addFuzzyListenerAndGetConfigs(String dataIdPattern, String fixedGroupName, - AbstractFuzzyListenListener listener) throws NacosException; - - /** - * Cancel fuzzy listen and remove the event listener for a specified fixed group name. - * - * @param fixedGroupName The fixed group name for fuzzy watch. - * @param listener The event listener to be removed. - * @throws NacosException If an error occurs during the cancellation process. - */ - void cancelFuzzyListen(String fixedGroupName, AbstractFuzzyListenListener listener) throws NacosException; - - /** - * Cancel fuzzy listen and remove the event listener for a specified service name pattern and fixed group name. - * - * @param dataIdPatter The pattern to match dataId for fuzzy watch. - * @param fixedGroupName The fixed group name for fuzzy watch. - * @param listener The event listener to be removed. - * @throws NacosException If an error occurs during the cancellation process. - */ - void cancelFuzzyListen(String dataIdPatter, String fixedGroupName, AbstractFuzzyListenListener listener) - throws NacosException; - /** * Publish config. * @@ -220,10 +148,10 @@ boolean publishConfigCas(String dataId, String group, String content, String cas * @return whether health */ String getServerStatus(); - + /** - * add config filter. It is recommended to use {@link com.alibaba.nacos.api.config.filter.AbstractConfigFilter} to - * expand the filter. + * add config filter. + * It is recommended to use {@link com.alibaba.nacos.api.config.filter.AbstractConfigFilter} to expand the filter. * * @param configFilter filter * @since 2.3.0 @@ -236,4 +164,83 @@ boolean publishConfigCas(String dataId, String group, String content, String cas * @throws NacosException exception. */ void shutDown() throws NacosException; + + /** + * Add a fuzzy listener to the configuration. After the server modifies the configuration matching the specified + * fixed group name, the client will utilize the incoming fuzzy listener callback. Fuzzy listeners allow for + * pattern-based subscription to configurations, where the fixed group name represents the group and dataId patterns + * specified for subscription. + * + * @param fixedGroupName The fixed group name representing the group and dataId patterns to subscribe to. + * @param watcher The fuzzy watcher to be added. + * @throws NacosException NacosException + * @since 3.0 + */ + void fuzzyWatch(String fixedGroupName, FuzzyWatchEventWatcher watcher) throws NacosException; + + /** + * Add a fuzzy listener to the configuration. After the server modifies the configuration matching the specified + * dataId pattern and fixed group name, the client will utilize the incoming fuzzy listener callback. Fuzzy + * listeners allow for pattern-based subscription to configurations. + * + * @param dataIdPattern The pattern to match dataIds for subscription. + * @param fixedGroupName The fixed group name representing the group and dataId patterns to subscribe to. + * @param watcher The fuzzy listener to be added. + * @throws NacosException NacosException + * @since 3.0 + */ + void fuzzyWatch(String dataIdPattern, String fixedGroupName, FuzzyWatchEventWatcher watcher) + throws NacosException; + + /** + * Add a fuzzy listener to the configuration and retrieve all configs that match the specified fixed group name. + * Fuzzy listeners allow for pattern-based subscription to configs, where the fixed group name represents the group + * and dataId patterns specified for subscription. + * + * @param fixedGroupName The fixed group name representing the group and dataId patterns to subscribe to. + * @param watcher The fuzzy watcher to be added. + * @return CompletableFuture containing collection of configs that match the specified fixed group name. + * @throws NacosException NacosException + * @since 3.0 + */ + Future> fuzzyWatchWithGroupKeys(String fixedGroupName, + FuzzyWatchEventWatcher watcher) throws NacosException; + + /** + * Add a fuzzy listener to the configuration and retrieve all configs that match the specified dataId pattern and + * fixed group name. Fuzzy listeners allow for pattern-based subscription to configs. + * + * @param dataIdPattern The pattern to match dataIds for subscription. + * @param fixedGroupName The fixed group name representing the group and dataId patterns to subscribe to. + * @param watcher The fuzzy watcher to be added. + * @return CompletableFuture containing collection of configs that match the specified dataId pattern and fixed + * group name. + * @throws NacosException NacosException + * @since 3.0 + */ + Future> fuzzyWatchWithGroupKeys(String dataIdPattern, String fixedGroupName, + FuzzyWatchEventWatcher watcher) throws NacosException; + + /** + * Cancel fuzzy listen and remove the event listener for a specified fixed group name. + * + * @param fixedGroupName The fixed group name for fuzzy watch. + * @param watcher The event watcher to be removed. + * @throws NacosException If an error occurs during the cancellation process. + * @since 3.0 + */ + void cancelFuzzyWatch(String fixedGroupName, FuzzyWatchEventWatcher watcher) throws NacosException; + + /** + * Cancel fuzzy listen and remove the event listener for a specified service name pattern and fixed group name. + * + * @param dataIdPattern The pattern to match dataId for fuzzy watch. + * @param fixedGroupName The fixed group name for fuzzy watch. + * @param watcher The event listener to be removed. + * @throws NacosException If an error occurs during the cancellation process. + * @since 3.0 + */ + void cancelFuzzyWatch(String dataIdPattern, String fixedGroupName, FuzzyWatchEventWatcher watcher) + throws NacosException; + } diff --git a/api/src/main/java/com/alibaba/nacos/api/config/listener/AbstractFuzzyListenListener.java b/api/src/main/java/com/alibaba/nacos/api/config/listener/AbstractFuzzyListenListener.java deleted file mode 100644 index bd317884a48..00000000000 --- a/api/src/main/java/com/alibaba/nacos/api/config/listener/AbstractFuzzyListenListener.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 1999-2023 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.api.config.listener; - -import java.util.Objects; - -/** - * AbstractFuzzyListenListener is an abstract class that provides basic functionality for listening to fuzzy - * configuration changes in Nacos. - * - * @author stone-98 - * @date 2024/3/4 - */ -public abstract class AbstractFuzzyListenListener extends AbstractListener { - - /** - * Unique identifier for the listener. - */ - private String uuid; - - /** - * Get the UUID (Unique Identifier) of the listener. - * - * @return The UUID of the listener - */ - public String getUuid() { - return uuid; - } - - /** - * Set the UUID (Unique Identifier) of the listener. - * - * @param uuid The UUID to be set - */ - public void setUuid(String uuid) { - this.uuid = uuid; - } - - /** - * Callback method invoked when a fuzzy configuration change event occurs. - * - * @param event The fuzzy configuration change event - */ - public abstract void onEvent(FuzzyListenConfigChangeEvent event); - - /** - * Receive the configuration information. This method is overridden but does nothing in this abstract class. - * - * @param configInfo The configuration information - */ - @Override - public void receiveConfigInfo(String configInfo) { - // Do nothing by default - } - - /** - * Compute the hash code for this listener based on its UUID. - * - * @return The hash code value for this listener - */ - @Override - public int hashCode() { - return Objects.hashCode(uuid); - } - - /** - * Compare this listener to the specified object for equality. Two listeners are considered equal if they have the - * same UUID. - * - * @param o The object to compare to - * @return true if the specified object is equal to this listener, false otherwise - */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - AbstractFuzzyListenListener that = (AbstractFuzzyListenListener) o; - return Objects.equals(uuid, that.uuid); - } -} diff --git a/api/src/main/java/com/alibaba/nacos/api/config/listener/AbstractFuzzyWatchEventWatcher.java b/api/src/main/java/com/alibaba/nacos/api/config/listener/AbstractFuzzyWatchEventWatcher.java new file mode 100644 index 00000000000..b62cca652af --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/config/listener/AbstractFuzzyWatchEventWatcher.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2023 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.api.config.listener; + +import java.util.concurrent.Executor; + +/** + * AbstractFuzzyListenListener is an abstract class that provides basic functionality for listening to fuzzy + * configuration changes in Nacos. + * + * @author stone-98 + * @date 2024/3/4 + */ +public abstract class AbstractFuzzyWatchEventWatcher implements FuzzyWatchEventWatcher { + + /** + * Get executor for execute this receive. + * + * @return Executor + */ + public Executor getExecutor() { + return null; + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/config/listener/AbstractListener.java b/api/src/main/java/com/alibaba/nacos/api/config/listener/AbstractListener.java index e14008d01d9..3f4c968f378 100644 --- a/api/src/main/java/com/alibaba/nacos/api/config/listener/AbstractListener.java +++ b/api/src/main/java/com/alibaba/nacos/api/config/listener/AbstractListener.java @@ -23,7 +23,6 @@ * * @author water.lyl */ -@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") public abstract class AbstractListener implements Listener { /** diff --git a/api/src/main/java/com/alibaba/nacos/api/config/listener/ConfigFuzzyWatchChangeEvent.java b/api/src/main/java/com/alibaba/nacos/api/config/listener/ConfigFuzzyWatchChangeEvent.java new file mode 100644 index 00000000000..ac90ec4caaf --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/config/listener/ConfigFuzzyWatchChangeEvent.java @@ -0,0 +1,113 @@ +/* + * Copyright 1999-2023 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.api.config.listener; + +import com.alibaba.nacos.api.common.Constants; + +/** + * Represents a fuzzy listening configuration change event. + * + *

This event indicates that a change has occurred in a configuration that matches a fuzzy listening pattern. + * + * @author stone-98 + * @date 2024/3/12 + */ +public class ConfigFuzzyWatchChangeEvent { + + /** + * The group of the configuration that has changed. + */ + private String group; + + /** + * The data ID of the configuration that has changed. + */ + private String dataId; + + /** + * The namespace of the configuration that has changed. + */ + private String namespace; + + /** + * The change type of local watcher , contains {"ADD_CONFIG", "DELETE_CONFIG"}. + * {@link Constants.ConfigChangedType}. + */ + private String changedType; + + /** + * the sync type that trigger this changed,contains {"FUZZY_WATCH_INIT_NOTIFY","FUZZY_WATCH_RESOURCE_CHANGED", + * "FUZZY_WATCH_DIFF_SYNC_NOTIFY"}. + */ + private String syncType; + + /** + * Constructs a FuzzyListenConfigChangeEvent with the specified parameters. + * + * @param group The group of the configuration that has changed + * @param dataId The data ID of the configuration that has changed + * @param changedType The type of change that has occurred + */ + private ConfigFuzzyWatchChangeEvent(String namespace, String group, String dataId, String changedType, + String syncType) { + this.group = group; + this.dataId = dataId; + this.namespace = namespace; + this.changedType = changedType; + this.syncType = syncType; + } + + /** + * Constructs and returns a new FuzzyListenConfigChangeEvent with the specified parameters. + * + * @param group The group of the configuration that has changed + * @param dataId The data ID of the configuration that has changed + * @param changedType The type of change that has occurred + * @return A new FuzzyListenConfigChangeEvent instance + */ + public static ConfigFuzzyWatchChangeEvent build(String namespace, String group, String dataId, String changedType, + String syncType) { + return new ConfigFuzzyWatchChangeEvent(namespace, group, dataId, changedType, syncType); + } + + public String getNamespace() { + return namespace; + } + + public String getGroup() { + return group; + } + + public String getDataId() { + return dataId; + } + + public String getChangedType() { + return changedType; + } + + public String getSyncType() { + return syncType; + } + + @Override + public String toString() { + return "ConfigFuzzyWatchChangeEvent{" + "group='" + group + '\'' + ", dataId='" + dataId + '\'' + + ", namespace='" + namespace + '\'' + ", changedType='" + changedType + '\'' + ", syncType='" + + syncType + '\'' + '}'; + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/config/listener/FuzzyListenConfigChangeEvent.java b/api/src/main/java/com/alibaba/nacos/api/config/listener/FuzzyListenConfigChangeEvent.java deleted file mode 100644 index 6e2c3e319ca..00000000000 --- a/api/src/main/java/com/alibaba/nacos/api/config/listener/FuzzyListenConfigChangeEvent.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 1999-2023 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.api.config.listener; - -/** - * Represents a fuzzy listening configuration change event. - * - *

This event indicates that a change has occurred in a configuration that matches a fuzzy listening pattern. - * - * @author stone-98 - * @date 2024/3/12 - */ -public class FuzzyListenConfigChangeEvent { - - /** - * The group of the configuration that has changed. - */ - private String group; - - /** - * The data ID of the configuration that has changed. - */ - private String dataId; - - /** - * The type of change that has occurred (e.g., "ADD_CONFIG", "DELETE_CONFIG"). - */ - private String type; - - /** - * Constructs an empty FuzzyListenConfigChangeEvent. - */ - public FuzzyListenConfigChangeEvent() { - } - - /** - * Constructs a FuzzyListenConfigChangeEvent with the specified parameters. - * - * @param group The group of the configuration that has changed - * @param dataId The data ID of the configuration that has changed - * @param type The type of change that has occurred - */ - public FuzzyListenConfigChangeEvent(String group, String dataId, String type) { - this.group = group; - this.dataId = dataId; - this.type = type; - } - - /** - * Constructs and returns a new FuzzyListenConfigChangeEvent with the specified parameters. - * - * @param group The group of the configuration that has changed - * @param dataId The data ID of the configuration that has changed - * @param type The type of change that has occurred - * @return A new FuzzyListenConfigChangeEvent instance - */ - public static FuzzyListenConfigChangeEvent build(String group, String dataId, String type) { - return new FuzzyListenConfigChangeEvent(group, dataId, type); - } - - public String getGroup() { - return group; - } - - public void setGroup(String group) { - this.group = group; - } - - public String getDataId() { - return dataId; - } - - public void setDataId(String dataId) { - this.dataId = dataId; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - /** - * Returns a string representation of the FuzzyListenConfigChangeEvent. - * - * @return A string representation of the event - */ - @Override - public String toString() { - return "FuzzyListenConfigChangeEvent{" + "group='" + group + '\'' + ", dataId='" + dataId + '\'' + ", type='" - + type + '\'' + '}'; - } -} diff --git a/api/src/main/java/com/alibaba/nacos/api/config/listener/FuzzyWatchEventWatcher.java b/api/src/main/java/com/alibaba/nacos/api/config/listener/FuzzyWatchEventWatcher.java new file mode 100644 index 00000000000..56c1bbe5ed9 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/config/listener/FuzzyWatchEventWatcher.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2023 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.api.config.listener; + +import java.util.concurrent.Executor; + +/** + * fuzzy watch config changes. + * + * @author stone-98 + * @date 2024/3/4 + */ +public interface FuzzyWatchEventWatcher { + + /** + * Callback method invoked when a fuzzy configuration change event occurs. + * + * @param event The fuzzy configuration change event + */ + void onEvent(ConfigFuzzyWatchChangeEvent event); + + /** + * Get executor for execute this receive. + * + * @return Executor + */ + Executor getExecutor(); + +} diff --git a/api/src/main/java/com/alibaba/nacos/api/config/remote/request/AbstractFuzzyListenNotifyRequest.java b/api/src/main/java/com/alibaba/nacos/api/config/remote/request/AbstractFuzzyWatchNotifyRequest.java similarity index 65% rename from api/src/main/java/com/alibaba/nacos/api/config/remote/request/AbstractFuzzyListenNotifyRequest.java rename to api/src/main/java/com/alibaba/nacos/api/config/remote/request/AbstractFuzzyWatchNotifyRequest.java index 1ba2b7bda14..67a22264e70 100644 --- a/api/src/main/java/com/alibaba/nacos/api/config/remote/request/AbstractFuzzyListenNotifyRequest.java +++ b/api/src/main/java/com/alibaba/nacos/api/config/remote/request/AbstractFuzzyWatchNotifyRequest.java @@ -26,23 +26,9 @@ * @author stone-98 * @date 2024/3/14 */ -public abstract class AbstractFuzzyListenNotifyRequest extends ServerRequest { +public abstract class AbstractFuzzyWatchNotifyRequest extends ServerRequest { - private String serviceChangedType; - - public AbstractFuzzyListenNotifyRequest() { - } - - public AbstractFuzzyListenNotifyRequest(String serviceChangedType) { - this.serviceChangedType = serviceChangedType; - } - - public String getServiceChangedType() { - return serviceChangedType; - } - - public void setServiceChangedType(String serviceChangedType) { - this.serviceChangedType = serviceChangedType; + public AbstractFuzzyWatchNotifyRequest() { } @Override diff --git a/api/src/main/java/com/alibaba/nacos/api/config/remote/request/ConfigBatchFuzzyListenRequest.java b/api/src/main/java/com/alibaba/nacos/api/config/remote/request/ConfigBatchFuzzyListenRequest.java deleted file mode 100644 index fd5ce81e1cb..00000000000 --- a/api/src/main/java/com/alibaba/nacos/api/config/remote/request/ConfigBatchFuzzyListenRequest.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright 1999-2023 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.api.config.remote.request; - -import com.alibaba.nacos.api.common.Constants; -import com.alibaba.nacos.api.remote.request.Request; -import com.alibaba.nacos.api.utils.StringUtils; - -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -/** - * Represents a request for batch fuzzy listening configurations. - * - *

This request is used to request batch fuzzy listening configurations from the server. It contains a set of - * contexts, each representing a fuzzy listening context. - * - * @author stone-98 - * @date 2024/3/4 - */ -public class ConfigBatchFuzzyListenRequest extends Request { - - /** - * Set of fuzzy listening contexts. - */ - private Set contexts = new HashSet<>(); - - /** - * Constructs an empty ConfigBatchFuzzyListenRequest. - */ - public ConfigBatchFuzzyListenRequest() { - } - - /** - * Adds a new context to the request. - * - * @param tenant The namespace or tenant associated with the configurations - * @param group The group associated with the configurations - * @param dataIdPattern The pattern for matching data IDs - * @param dataIds Set of data IDs - * @param listen Flag indicating whether to listen for changes - * @param isInitializing Flag indicating whether the client is initializing - */ - public void addContext(String tenant, String group, String dataIdPattern, Set dataIds, boolean listen, - boolean isInitializing) { - contexts.add( - new Context(StringUtils.isEmpty(tenant) ? Constants.DEFAULT_NAMESPACE_ID : tenant, group, dataIdPattern, - dataIds, listen, isInitializing)); - } - - /** - * Get the set of fuzzy listening contexts. - * - * @return The set of contexts - */ - public Set getContexts() { - return contexts; - } - - /** - * Set the set of fuzzy listening contexts. - * - * @param contexts The set of contexts to be set - */ - public void setContexts(Set contexts) { - this.contexts = contexts; - } - - /** - * Get the module name for this request. - * - * @return The module name - */ - @Override - public String getModule() { - return Constants.Config.CONFIG_MODULE; - } - - /** - * Represents a fuzzy listening context. - */ - public static class Context { - - /** - * The namespace or tenant associated with the configurations. - */ - private String tenant; - - /** - * The group associated with the configurations. - */ - private String group; - - /** - * The pattern for matching data IDs. - */ - private String dataIdPattern; - - /** - * Set of data IDs. - */ - private Set dataIds; - - /** - * Flag indicating whether to listen for changes. - */ - private boolean listen; - - /** - * Flag indicating whether the client is initializing. - */ - private boolean isInitializing; - - /** - * Constructs an empty Context. - */ - public Context() { - } - - /** - * Constructs a Context with the specified parameters. - * - * @param tenant The namespace or tenant associated with the configurations - * @param group The group associated with the configurations - * @param dataIdPattern The pattern for matching data IDs - * @param dataIds Set of data IDs - * @param listen Flag indicating whether to listen for changes - * @param isInitializing Flag indicating whether the client is initializing - */ - public Context(String tenant, String group, String dataIdPattern, Set dataIds, boolean listen, - boolean isInitializing) { - this.tenant = tenant; - this.group = group; - this.dataIdPattern = dataIdPattern; - this.dataIds = dataIds; - this.listen = listen; - this.isInitializing = isInitializing; - } - - public String getTenant() { - return tenant; - } - - public void setTenant(String tenant) { - this.tenant = tenant; - } - - public String getGroup() { - return group; - } - - public void setGroup(String group) { - this.group = group; - } - - public String getDataIdPattern() { - return dataIdPattern; - } - - public void setDataIdPattern(String dataIdPattern) { - this.dataIdPattern = dataIdPattern; - } - - public Set getDataIds() { - return dataIds; - } - - public void setDataIds(Set dataIds) { - this.dataIds = dataIds; - } - - public boolean isListen() { - return listen; - } - - public void setListen(boolean listen) { - this.listen = listen; - } - - public boolean isInitializing() { - return isInitializing; - } - - public void setInitializing(boolean initializing) { - isInitializing = initializing; - } - - /** - * Indicates whether some other object is "equal to" this one. - * - * @param o The reference object with which to compare - * @return True if this object is the same as the obj argument, false otherwise - */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Context that = (Context) o; - return Objects.equals(tenant, that.tenant) && Objects.equals(group, that.group) && Objects.equals( - dataIdPattern, that.dataIdPattern) && Objects.equals(dataIds, that.dataIds) && Objects.equals( - listen, that.listen) && Objects.equals(isInitializing, that.isInitializing); - } - - /** - * Returns a hash code value for the object. - * - * @return A hash code value for this object - */ - @Override - public int hashCode() { - return Objects.hash(tenant, group, dataIdPattern, dataIds, listen, isInitializing); - } - } -} diff --git a/api/src/main/java/com/alibaba/nacos/api/config/remote/request/ConfigFuzzyWatchChangeNotifyRequest.java b/api/src/main/java/com/alibaba/nacos/api/config/remote/request/ConfigFuzzyWatchChangeNotifyRequest.java new file mode 100644 index 00000000000..aaa64a705db --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/config/remote/request/ConfigFuzzyWatchChangeNotifyRequest.java @@ -0,0 +1,83 @@ +/* + * Copyright 1999-2023 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.api.config.remote.request; + +/** + * Represents a request to notify changes when a fuzzy watched configuration changed. + * + *

This request is used to notify clients about changes in configurations that match fuzzy listening patterns. + * + * @author stone-98 + * @date 2024/3/13 + */ +public class ConfigFuzzyWatchChangeNotifyRequest extends AbstractFuzzyWatchNotifyRequest { + + /** + * The groupKey of the configuration that has changed. + */ + private String groupKey; + + /** + * Indicates whether the configuration exists or not. + */ + private String changeType; + + /** + * Constructs an empty FuzzyListenNotifyChangeRequest. + */ + public ConfigFuzzyWatchChangeNotifyRequest() { + } + + /** + * Constructs a FuzzyListenNotifyChangeRequest with the specified parameters. + * + * @param groupKey The group of the configuration that has changed + * @param changeType Indicates whether the configuration exists or not + */ + public ConfigFuzzyWatchChangeNotifyRequest(String groupKey, String changeType) { + this.groupKey = groupKey; + this.changeType = changeType; + } + + public String getGroupKey() { + return groupKey; + } + + public void setGroupKey(String groupKey) { + this.groupKey = groupKey; + } + + public String getChangeType() { + return changeType; + } + + public void setChangeType(String changeType) { + this.changeType = changeType; + } + + /** + * Returns a string representation of the FuzzyListenNotifyChangeRequest. + * + * @return A string representation of the request + */ + @Override + public String toString() { + return "FuzzyListenNotifyChangeRequest{" + '\'' + ", groupKey='" + groupKey + '\'' + ", changeType=" + + changeType + '}'; + } + +} diff --git a/api/src/main/java/com/alibaba/nacos/api/config/remote/request/ConfigFuzzyWatchRequest.java b/api/src/main/java/com/alibaba/nacos/api/config/remote/request/ConfigFuzzyWatchRequest.java new file mode 100644 index 00000000000..f61e36baac2 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/config/remote/request/ConfigFuzzyWatchRequest.java @@ -0,0 +1,100 @@ +/* + * Copyright 1999-2023 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.api.config.remote.request; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.remote.request.Request; + +import java.util.Set; + +/** + * Represents a request for batch fuzzy listening configurations. + * + *

This request is used to request batch fuzzy listening configurations from the server. It contains a set of + * contexts, each representing a fuzzy listening context. + * + * @author stone-98 + * @date 2024/3/4 + */ +public class ConfigFuzzyWatchRequest extends Request { + + /** + * The namespace or tenant associated with the configurations. + */ + private String groupKeyPattern; + + private Set receivedGroupKeys; + + /** + * Flag indicating whether to listen for changes. + */ + private String watchType; + + /** + * Flag indicating whether the client is initializing. + */ + private boolean isInitializing; + + /** + * Constructs an empty ConfigBatchFuzzyListenRequest. + */ + public ConfigFuzzyWatchRequest() { + } + + public String getGroupKeyPattern() { + return groupKeyPattern; + } + + public void setGroupKeyPattern(String groupKeyPattern) { + this.groupKeyPattern = groupKeyPattern; + } + + public Set getReceivedGroupKeys() { + return receivedGroupKeys; + } + + public void setReceivedGroupKeys(Set receivedGroupKeys) { + this.receivedGroupKeys = receivedGroupKeys; + } + + public String getWatchType() { + return watchType; + } + + public void setWatchType(String watchType) { + this.watchType = watchType; + } + + public boolean isInitializing() { + return isInitializing; + } + + public void setInitializing(boolean initializing) { + isInitializing = initializing; + } + + /** + * Get the module name for this request. + * + * @return The module name + */ + @Override + public String getModule() { + return Constants.Config.CONFIG_MODULE; + } + +} diff --git a/api/src/main/java/com/alibaba/nacos/api/config/remote/request/FuzzyListenNotifyDiffRequest.java b/api/src/main/java/com/alibaba/nacos/api/config/remote/request/ConfigFuzzyWatchSyncRequest.java similarity index 50% rename from api/src/main/java/com/alibaba/nacos/api/config/remote/request/FuzzyListenNotifyDiffRequest.java rename to api/src/main/java/com/alibaba/nacos/api/config/remote/request/ConfigFuzzyWatchSyncRequest.java index 0c925a3dad9..e6ead123c78 100644 --- a/api/src/main/java/com/alibaba/nacos/api/config/remote/request/FuzzyListenNotifyDiffRequest.java +++ b/api/src/main/java/com/alibaba/nacos/api/config/remote/request/ConfigFuzzyWatchSyncRequest.java @@ -18,12 +18,10 @@ import com.alibaba.nacos.api.common.Constants; -import java.util.HashSet; -import java.util.Objects; import java.util.Set; /** - * Represents a request to notify the difference in configurations for fuzzy listening. + * Represents a request to notify the difference between client and server side. * *

This request is used to notify clients about the difference in configurations that match fuzzy listening * patterns. @@ -31,7 +29,7 @@ * @author stone-98 * @date 2024/3/6 */ -public class FuzzyListenNotifyDiffRequest extends AbstractFuzzyListenNotifyRequest { +public class ConfigFuzzyWatchSyncRequest extends AbstractFuzzyWatchNotifyRequest { /** * The pattern used to match group keys for the configurations. @@ -43,23 +41,59 @@ public class FuzzyListenNotifyDiffRequest extends AbstractFuzzyListenNotifyReque */ private Set contexts; + /** + * see FUZZY_WATCH_INIT_NOTIFY,FINISH_FUZZY_WATCH_INIT_NOTIFY,FUZZY_WATCH_DIFF_SYNC_NOTIFY. + */ + private String syncType; + + private int totalBatch; + + private int currentBatch; + + public String getSyncType() { + return syncType; + } + + public void setSyncType(String syncType) { + this.syncType = syncType; + } + + public int getTotalBatch() { + return totalBatch; + } + + public void setTotalBatch(int totalBatch) { + this.totalBatch = totalBatch; + } + + public int getCurrentBatch() { + return currentBatch; + } + + public void setCurrentBatch(int currentBatch) { + this.currentBatch = currentBatch; + } + /** * Constructs an empty FuzzyListenNotifyDiffRequest. */ - public FuzzyListenNotifyDiffRequest() { + public ConfigFuzzyWatchSyncRequest() { } /** * Constructs a FuzzyListenNotifyDiffRequest with the specified parameters. * - * @param serviceChangedType The type of service change - * @param groupKeyPattern The pattern used to match group keys for the configurations - * @param contexts The set of contexts containing information about the configurations + * @param groupKeyPattern The pattern used to match group keys for the configurations + * @param contexts The set of contexts containing information about the configurations */ - public FuzzyListenNotifyDiffRequest(String serviceChangedType, String groupKeyPattern, Set contexts) { - super(serviceChangedType); + private ConfigFuzzyWatchSyncRequest(String syncType, String groupKeyPattern, Set contexts, int totalBatch, + int currentBatch) { this.groupKeyPattern = groupKeyPattern; this.contexts = contexts; + this.syncType = syncType; + this.currentBatch = currentBatch; + this.totalBatch = totalBatch; + } /** @@ -69,8 +103,9 @@ public FuzzyListenNotifyDiffRequest(String serviceChangedType, String groupKeyPa * @param groupKeyPattern The pattern used to match group keys for the configurations * @return An initial FuzzyListenNotifyDiffRequest */ - public static FuzzyListenNotifyDiffRequest buildInitRequest(Set contexts, String groupKeyPattern) { - return new FuzzyListenNotifyDiffRequest(Constants.ConfigChangeType.LISTEN_INIT, groupKeyPattern, contexts); + public static ConfigFuzzyWatchSyncRequest buildSyncRequest(String syncType, Set contexts, + String groupKeyPattern, int totalBatch, int currentBatch) { + return new ConfigFuzzyWatchSyncRequest(syncType, groupKeyPattern, contexts, totalBatch, currentBatch); } /** @@ -79,9 +114,8 @@ public static FuzzyListenNotifyDiffRequest buildInitRequest(Set context * @param groupKeyPattern The pattern used to match group keys for the configurations * @return A final FuzzyListenNotifyDiffRequest */ - public static FuzzyListenNotifyDiffRequest buildInitFinishRequest(String groupKeyPattern) { - return new FuzzyListenNotifyDiffRequest(Constants.ConfigChangeType.FINISH_LISTEN_INIT, groupKeyPattern, - new HashSet<>()); + public static ConfigFuzzyWatchSyncRequest buildInitFinishRequest(String groupKeyPattern) { + return new ConfigFuzzyWatchSyncRequest(Constants.FINISH_FUZZY_WATCH_INIT_NOTIFY, groupKeyPattern, null, 0, 0); } public String getGroupKeyPattern() { @@ -105,13 +139,13 @@ public void setContexts(Set contexts) { */ public static class Context { - private String tenant; - - private String group; + String groupKey; - private String dataId; - - private String type; + /** + * see {@link com.alibaba.nacos.api.common.Constants.ConfigChangedType ADD_CONFIG&} ADD_CONFIG: a new config + * should be added for clientside . DELETE_CONFIG: a config should be removed for clientside . + */ + private String changedType; /** * Constructs an empty Context object. @@ -122,69 +156,31 @@ public Context() { /** * Builds a new context object with the provided parameters. * - * @param tenant The tenant associated with the configuration. - * @param group The group associated with the configuration. - * @param dataId The data ID of the configuration. - * @param type The type of the configuration change event. + * @param groupKey The groupKey associated of the configuration. + * @param changedType The type of the configuration change event. * @return A new context object initialized with the provided parameters. */ - public static Context build(String tenant, String group, String dataId, String type) { + public static Context build(String groupKey, String changedType) { Context context = new Context(); - context.setTenant(tenant); - context.setGroup(group); - context.setDataId(dataId); - context.setType(type); + context.setGroupKey(groupKey); + context.setChangedType(changedType); return context; } - @Override - public int hashCode() { - return Objects.hash(tenant, group, dataId, tenant); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Context that = (Context) o; - return Objects.equals(tenant, that.tenant) && Objects.equals(group, that.group) && Objects.equals(dataId, - that.dataId) && Objects.equals(type, that.type); - } - - public String getTenant() { - return tenant; - } - - public void setTenant(String tenant) { - this.tenant = tenant; - } - - public String getGroup() { - return group; - } - - public void setGroup(String group) { - this.group = group; - } - - public String getDataId() { - return dataId; + public String getGroupKey() { + return groupKey; } - public void setDataId(String dataId) { - this.dataId = dataId; + public void setGroupKey(String groupKey) { + this.groupKey = groupKey; } - public String getType() { - return type; + public String getChangedType() { + return changedType; } - public void setType(String type) { - this.type = type; + public void setChangedType(String changedType) { + this.changedType = changedType; } } diff --git a/api/src/main/java/com/alibaba/nacos/api/config/remote/request/FuzzyListenNotifyChangeRequest.java b/api/src/main/java/com/alibaba/nacos/api/config/remote/request/FuzzyListenNotifyChangeRequest.java deleted file mode 100644 index b149d70689e..00000000000 --- a/api/src/main/java/com/alibaba/nacos/api/config/remote/request/FuzzyListenNotifyChangeRequest.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 1999-2023 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.api.config.remote.request; - -/** - * Represents a request to notify changes in a fuzzy listening configuration. - * - *

This request is used to notify clients about changes in configurations that match fuzzy listening patterns. - * - * @author stone-98 - * @date 2024/3/13 - */ -public class FuzzyListenNotifyChangeRequest extends AbstractFuzzyListenNotifyRequest { - - /** - * The tenant of the configuration that has changed. - */ - private String tenant; - - /** - * The group of the configuration that has changed. - */ - private String group; - - /** - * The data ID of the configuration that has changed. - */ - private String dataId; - - /** - * Indicates whether the configuration exists or not. - */ - private boolean isExist; - - /** - * Constructs an empty FuzzyListenNotifyChangeRequest. - */ - public FuzzyListenNotifyChangeRequest() { - } - - /** - * Constructs a FuzzyListenNotifyChangeRequest with the specified parameters. - * - * @param tenant The tenant of the configuration that has changed - * @param group The group of the configuration that has changed - * @param dataId The data ID of the configuration that has changed - * @param isExist Indicates whether the configuration exists or not - */ - public FuzzyListenNotifyChangeRequest(String tenant, String group, String dataId, boolean isExist) { - this.tenant = tenant; - this.group = group; - this.dataId = dataId; - this.isExist = isExist; - } - - public String getTenant() { - return tenant; - } - - public void setTenant(String tenant) { - this.tenant = tenant; - } - - public String getGroup() { - return group; - } - - public void setGroup(String group) { - this.group = group; - } - - public String getDataId() { - return dataId; - } - - public void setDataId(String dataId) { - this.dataId = dataId; - } - - public boolean isExist() { - return isExist; - } - - public void setExist(boolean exist) { - isExist = exist; - } - - /** - * Returns a string representation of the FuzzyListenNotifyChangeRequest. - * - * @return A string representation of the request - */ - @Override - public String toString() { - return "FuzzyListenNotifyChangeRequest{" + "tenant='" + tenant + '\'' + ", group='" + group + '\'' - + ", dataId='" + dataId + '\'' + ", isExist=" + isExist + '}'; - } - -} diff --git a/api/src/main/java/com/alibaba/nacos/api/config/remote/response/FuzzyListenNotifyDiffResponse.java b/api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigFuzzyWatchChangeNotifyResponse.java similarity index 88% rename from api/src/main/java/com/alibaba/nacos/api/config/remote/response/FuzzyListenNotifyDiffResponse.java rename to api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigFuzzyWatchChangeNotifyResponse.java index 672d8dfc800..abb8e8449d7 100644 --- a/api/src/main/java/com/alibaba/nacos/api/config/remote/response/FuzzyListenNotifyDiffResponse.java +++ b/api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigFuzzyWatchChangeNotifyResponse.java @@ -19,11 +19,11 @@ import com.alibaba.nacos.api.remote.response.Response; /** - * FuzzyListenNotifyDiffResponse. + * FuzzyListenNotifyChangeResponse. * * @author stone-98 * @date 2024/3/18 */ -public class FuzzyListenNotifyDiffResponse extends Response { +public class ConfigFuzzyWatchChangeNotifyResponse extends Response { } diff --git a/api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigBatchFuzzyListenResponse.java b/api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigFuzzyWatchResponse.java similarity index 92% rename from api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigBatchFuzzyListenResponse.java rename to api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigFuzzyWatchResponse.java index f1eca4df7c5..bfcf065ed81 100644 --- a/api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigBatchFuzzyListenResponse.java +++ b/api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigFuzzyWatchResponse.java @@ -24,6 +24,6 @@ * @author stone-98 * @date 2024/3/4 */ -public class ConfigBatchFuzzyListenResponse extends Response { +public class ConfigFuzzyWatchResponse extends Response { } diff --git a/api/src/main/java/com/alibaba/nacos/api/config/remote/response/FuzzyListenNotifyChangeResponse.java b/api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigFuzzyWatchSyncResponse.java similarity index 92% rename from api/src/main/java/com/alibaba/nacos/api/config/remote/response/FuzzyListenNotifyChangeResponse.java rename to api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigFuzzyWatchSyncResponse.java index 1e58ec3f7dd..9b91e010e2b 100644 --- a/api/src/main/java/com/alibaba/nacos/api/config/remote/response/FuzzyListenNotifyChangeResponse.java +++ b/api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigFuzzyWatchSyncResponse.java @@ -24,6 +24,6 @@ * @author stone-98 * @date 2024/3/18 */ -public class FuzzyListenNotifyChangeResponse extends Response { +public class ConfigFuzzyWatchSyncResponse extends Response { } diff --git a/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java b/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java index 4ee3ee2e188..c810a75440c 100644 --- a/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java +++ b/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java @@ -26,67 +26,67 @@ public enum ErrorCode { /** - * success. + * success. */ SUCCESS(0, "success"), /** - * parameter missing. + * parameter missing. */ PARAMETER_MISSING(10000, "parameter missing"), /** - * access denied. + * access denied. */ ACCESS_DENIED(10001, "access denied"), /** - * data access error. + * data access error. */ DATA_ACCESS_ERROR(10002, "data access error"), /** - * 'tenant' parameter error. + * 'tenant' parameter error. */ TENANT_PARAM_ERROR(20001, "'tenant' parameter error"), /** - * parameter validate error. + * parameter validate error. */ PARAMETER_VALIDATE_ERROR(20002, "parameter validate error"), /** - * MediaType Error. + * MediaType Error. */ MEDIA_TYPE_ERROR(20003, "MediaType Error"), /** - * resource not found. + * resource not found. */ RESOURCE_NOT_FOUND(20004, "resource not found"), /** - * resource conflict. + * resource conflict. */ RESOURCE_CONFLICT(20005, "resource conflict"), /** - * config listener is null. + * config listener is null. */ CONFIG_LISTENER_IS_NULL(20006, "config listener is null"), /** - * config listener error. + * config listener error. */ CONFIG_LISTENER_ERROR(20007, "config listener error"), /** - * invalid dataId. + * invalid dataId. */ INVALID_DATA_ID(20008, "invalid dataId"), /** - * parameter mismatch. + * parameter mismatch. */ PARAMETER_MISMATCH(20009, "parameter mismatch"), @@ -111,97 +111,97 @@ public enum ErrorCode { CONFIG_GRAY_NAME_UNRECOGNIZED_ERROR(20013, "config gray name not recognized"), /** - * service name error. + * service name error. */ SERVICE_NAME_ERROR(21000, "service name error"), /** - * weight error. + * weight error. */ WEIGHT_ERROR(21001, "weight error"), /** - * instance metadata error. + * instance metadata error. */ INSTANCE_METADATA_ERROR(21002, "instance metadata error"), /** - * instance not found. + * instance not found. */ INSTANCE_NOT_FOUND(21003, "instance not found"), /** - * instance error. + * instance error. */ INSTANCE_ERROR(21004, "instance error"), /** - * service metadata error. + * service metadata error. */ SERVICE_METADATA_ERROR(21005, "service metadata error"), /** - * selector error. + * selector error. */ SELECTOR_ERROR(21006, "selector error"), /** - * service already exist. + * service already exist. */ SERVICE_ALREADY_EXIST(21007, "service already exist"), /** - * service not exist. + * service not exist. */ SERVICE_NOT_EXIST(21008, "service not exist"), /** - * service delete failure. + * service delete failure. */ SERVICE_DELETE_FAILURE(21009, "service delete failure"), /** - * healthy param miss. + * healthy param miss. */ HEALTHY_PARAM_MISS(21010, "healthy param miss"), /** - * health check still running. + * health check still running. */ HEALTH_CHECK_STILL_RUNNING(21011, "health check still running"), /** - * illegal namespace. + * illegal namespace. */ ILLEGAL_NAMESPACE(22000, "illegal namespace"), /** - * namespace not exist. + * namespace not exist. */ NAMESPACE_NOT_EXIST(22001, "namespace not exist"), /** - * namespace already exist. + * namespace already exist. */ NAMESPACE_ALREADY_EXIST(22002, "namespace already exist"), /** - * illegal state. + * illegal state. */ ILLEGAL_STATE(23000, "illegal state"), /** - * node info error. + * node info error. */ NODE_INFO_ERROR(23001, "node info error"), /** - * node down failure. + * node down failure. */ NODE_DOWN_FAILURE(23002, "node down failure"), /** - * server error. + * server error. */ SERVER_ERROR(30000, "server error"), @@ -221,7 +221,11 @@ public enum ErrorCode { DATA_EMPTY(100005, "导入的文件数据为空"), - NO_SELECTED_CONFIG(100006, "没有选择任何配置"); + NO_SELECTED_CONFIG(100006, "没有选择任何配置"), + + FUZZY_WATCH_PATTERN_OVER_LIMIT(50310, "fuzzy watch pattern over limit"), + + FUZZY_WATCH_PATTERN_MATCH_GROUP_KEY_OVER_LIMIT(50311, "fuzzy watch pattern matched group key over limit"); private final Integer code; diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/NamingService.java b/api/src/main/java/com/alibaba/nacos/api/naming/NamingService.java index f10c25d3029..20034bae97e 100644 --- a/api/src/main/java/com/alibaba/nacos/api/naming/NamingService.java +++ b/api/src/main/java/com/alibaba/nacos/api/naming/NamingService.java @@ -17,6 +17,7 @@ package com.alibaba.nacos.api.naming; import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.listener.FuzzyWatchEventWatcher; import com.alibaba.nacos.api.naming.listener.EventListener; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ListView; @@ -25,6 +26,7 @@ import com.alibaba.nacos.api.selector.AbstractSelector; import java.util.List; +import java.util.concurrent.Future; /** * Naming Service. @@ -554,7 +556,7 @@ void subscribe(String serviceName, String groupName, NamingSelector selector, Ev */ void unsubscribe(String serviceName, String groupName, List clusters, EventListener listener) throws NacosException; - + /** * Unsubscribe event listener of service. * @@ -577,6 +579,78 @@ void unsubscribe(String serviceName, String groupName, List clusters, Ev void unsubscribe(String serviceName, String groupName, NamingSelector selector, EventListener listener) throws NacosException; + + /** + * According to matching rules, watch services within a specific scope, and receive notifications when + * changes occur in the services within the scope. + * When given a fixed group name, watch changes in all services under this group. + * + * @param groupNamePattern group name pattern for fuzzy watch + * @param listener event listener + * @throws NacosException nacos exception + */ + void fuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException; + + /** + * According to matching rules, watch services within a specific scope, and receive notifications when + * changes occur in the services within the scope. + * When provided with a fixed group name and pattern of service name, watch changes in services under + * this group that match the specified pattern. + * + * @param serviceNamePattern service name pattern for fuzzy watch + * @param groupNamePattern group name pattern for fuzzy watch + * @param listener event listener + * @throws NacosException nacos exception + */ + void fuzzyWatch(String serviceNamePattern, String groupNamePattern, + FuzzyWatchEventWatcher listener) throws NacosException; + + /** + * According to matching rules, watch services within a specific scope, and receive notifications when + * changes occur in the services within the scope. + * When given a fixed group name, watch changes in all services under this group. + * + * @param groupNamePattern group name pattern for fuzzy watch + * @param listener event listener + * @return matched service keys. + * @throws NacosException nacos exception + */ + Future> fuzzyWatchWithServiceKeys(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException; + + /** + * According to matching rules, watch services within a specific scope, and receive notifications when + * changes occur in the services within the scope. + * When provided with a fixed group name and pattern of service name, watch changes in services under + * this group that match the specified pattern. + * + * @param serviceNamePattern service name pattern for fuzzy watch + * @param groupNamePattern group name pattern for fuzzy watch + * @param listener event listener + * @return matched service keys. + * @throws NacosException nacos exception + */ + Future> fuzzyWatchWithServiceKeys(String serviceNamePattern, String groupNamePattern, + FuzzyWatchEventWatcher listener) throws NacosException; + + /** + * Cancel fuzzy watch, and remove event listener of a pattern. + * + * @param groupNamePattern group name for fuzzy watch + * @param listener event listener + * @throws NacosException nacos exception + */ + void cancelFuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException; + + /** + * Cancel fuzzy watch, and remove event listener of a pattern. + * + * @param serviceNamePattern service name pattern for fuzzy watch + * @param groupNamePattern fixed group name for fuzzy watch + * @param listener event listener + * @throws NacosException nacos exception + */ + void cancelFuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException; + /** * Get all service names from server. * diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/listener/AbstractFuzzyWatchEventWatcher.java b/api/src/main/java/com/alibaba/nacos/api/naming/listener/AbstractFuzzyWatchEventWatcher.java new file mode 100644 index 00000000000..fa10d05f1ea --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/listener/AbstractFuzzyWatchEventWatcher.java @@ -0,0 +1,32 @@ +/* + * Copyright 1999-2023 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.api.naming.listener; + +import java.util.concurrent.Executor; + +/** + * Abstract fuzzy watch event listener, to support handle event by user custom executor. + * + * @author tanyongquan + */ +public abstract class AbstractFuzzyWatchEventWatcher implements FuzzyWatchEventWatcher { + + @Override + public Executor getExecutor() { + return null; + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/listener/FuzzyWatchChangeEvent.java b/api/src/main/java/com/alibaba/nacos/api/naming/listener/FuzzyWatchChangeEvent.java new file mode 100644 index 00000000000..c76e20d2ae9 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/listener/FuzzyWatchChangeEvent.java @@ -0,0 +1,83 @@ +/* + * Copyright 1999-2023 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.api.naming.listener; + +/** + * Fuzzy Watch Notify Event. + * + * @author tanyongquan + */ +public class FuzzyWatchChangeEvent implements Event { + + private String serviceName; + + private String groupName; + + private String namespace; + + private String changeType; + + private String syncType; + + public FuzzyWatchChangeEvent() { + } + + public FuzzyWatchChangeEvent(String serviceName, String groupName, String namespace, String changeType, + String syncType) { + this.changeType = changeType; + this.serviceName = serviceName; + this.groupName = groupName; + this.namespace = namespace; + this.syncType = syncType; + } + + public String getServiceName() { + return serviceName; + } + + public String getGroupName() { + return groupName; + } + + public String getNamespace() { + return namespace; + } + + /** + * The change type of local watcher , contains {"ADD_SERVICE", "DELETE_SERVICE"}. see Constants.ServiceChangedType + */ + public String getChangeType() { + return changeType; + } + + /** + * the sync type that trigger this changed,contains {"FUZZY_WATCH_INIT_NOTIFY","FUZZY_WATCH_RESOURCE_CHANGED", + * "FUZZY_WATCH_DIFF_SYNC_NOTIFY"}. + * + * @return + */ + public String getSyncType() { + return syncType; + } + + @Override + public String toString() { + return "FuzzyWatchChangeEvent{" + "serviceName='" + serviceName + '\'' + ", groupName='" + groupName + '\'' + + ", namespace='" + namespace + '\'' + ", changeType='" + changeType + '\'' + ", syncType='" + syncType + + '\'' + '}'; + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/listener/FuzzyWatchEventWatcher.java b/api/src/main/java/com/alibaba/nacos/api/naming/listener/FuzzyWatchEventWatcher.java new file mode 100644 index 00000000000..edd6c2a3603 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/listener/FuzzyWatchEventWatcher.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2023 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.api.naming.listener; + +import java.util.concurrent.Executor; + +/** + * Abstract fuzzy watch event listener, to support handle event by user custom executor. + * + * @author tanyongquan + */ +public interface FuzzyWatchEventWatcher { + + /** + * executor to notify event, using nacos internal notifier if null . + * @return + */ + Executor getExecutor(); + + /** + * handle FuzzyWatchChangeEvent. + * @param event fuzzy watch change event. + */ + void onEvent(FuzzyWatchChangeEvent event); + +} diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/AbstractFuzzyWatchNotifyRequest.java b/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/AbstractFuzzyWatchNotifyRequest.java new file mode 100644 index 00000000000..c0a776d54bb --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/AbstractFuzzyWatchNotifyRequest.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2023 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.api.naming.remote.request; + +import com.alibaba.nacos.api.remote.request.ServerRequest; + +import static com.alibaba.nacos.api.common.Constants.Naming.NAMING_MODULE; + +/** + * Abstract fuzzy watch notify request, including basic fuzzy watch notify information. + * + * @author tanyongquan + */ +public abstract class AbstractFuzzyWatchNotifyRequest extends ServerRequest { + + private String syncType; + + public AbstractFuzzyWatchNotifyRequest() { + } + + public AbstractFuzzyWatchNotifyRequest(String syncType) { + this.syncType = syncType; + } + + public String getSyncType() { + return syncType; + } + + public void setSyncType(String syncType) { + this.syncType = syncType; + } + + @Override + public String getModule() { + return NAMING_MODULE; + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/NamingFuzzyWatchChangeNotifyRequest.java b/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/NamingFuzzyWatchChangeNotifyRequest.java new file mode 100644 index 00000000000..3abade19bc2 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/NamingFuzzyWatchChangeNotifyRequest.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2023 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.api.naming.remote.request; + +/** + * Nacos fuzzy watch notify service change request, use it when one of the services changes. + * + * @author tanyongquan + */ +public class NamingFuzzyWatchChangeNotifyRequest extends AbstractFuzzyWatchNotifyRequest { + + private String serviceKey; + + private String changedType; + + public NamingFuzzyWatchChangeNotifyRequest() { + + } + + public NamingFuzzyWatchChangeNotifyRequest(String serviceKey, String changedType, String syncType) { + super(syncType); + this.serviceKey = serviceKey; + this.changedType = changedType; + } + + public String getServiceKey() { + return serviceKey; + } + + public void setServiceKey(String serviceKey) { + this.serviceKey = serviceKey; + } + + public String getChangedType() { + return changedType; + } + + public void setChangedType(String changedType) { + this.changedType = changedType; + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/NamingFuzzyWatchRequest.java b/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/NamingFuzzyWatchRequest.java new file mode 100644 index 00000000000..695348f8b00 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/NamingFuzzyWatchRequest.java @@ -0,0 +1,98 @@ +/* + * Copyright 1999-2023 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.api.naming.remote.request; + +import com.alibaba.nacos.api.remote.request.Request; + +import java.util.Set; + +import static com.alibaba.nacos.api.common.Constants.Naming.NAMING_MODULE; + +/** + * Nacos naming fuzzy watch service request. + * + * @author tanyongquan + */ +public class NamingFuzzyWatchRequest extends Request { + + private boolean isInitializing; + + private String namespace; + + /** + * The namespace or tenant associated with the configurations. + */ + private String groupKeyPattern; + + private Set receivedGroupKeys; + + private String watchType; + + public NamingFuzzyWatchRequest() { + } + + public NamingFuzzyWatchRequest(String groupKeyPattern, String watchType) { + this.watchType = watchType; + this.groupKeyPattern = groupKeyPattern; + } + + public String getGroupKeyPattern() { + return groupKeyPattern; + } + + public void setGroupKeyPattern(String groupKeyPattern) { + this.groupKeyPattern = groupKeyPattern; + } + + public String getWatchType() { + return watchType; + } + + public void setWatchType(String watchType) { + this.watchType = watchType; + } + + public Set getReceivedGroupKeys() { + return receivedGroupKeys; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public boolean isInitializing() { + return isInitializing; + } + + public void setInitializing(boolean initializing) { + isInitializing = initializing; + } + + public void setReceivedGroupKeys(Set receivedGroupKeys) { + this.receivedGroupKeys = receivedGroupKeys; + } + + @Override + public String getModule() { + return NAMING_MODULE; + } + +} diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/NamingFuzzyWatchSyncRequest.java b/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/NamingFuzzyWatchSyncRequest.java new file mode 100644 index 00000000000..c7ac6ca52a4 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/NamingFuzzyWatchSyncRequest.java @@ -0,0 +1,167 @@ +/* + * Copyright 1999-2023 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.api.naming.remote.request; + +import com.alibaba.nacos.api.common.Constants; + +import java.util.HashSet; +import java.util.Set; + +import static com.alibaba.nacos.api.common.Constants.Naming.NAMING_MODULE; + +/** + * fuzzy watch sync request from Nacos server. + * @author shiyiyue + */ +public class NamingFuzzyWatchSyncRequest extends AbstractFuzzyWatchNotifyRequest { + + /** + * The pattern used to match service keys for the services. + */ + private String groupKeyPattern; + + /** + * The set of contexts containing information about the service. + */ + private Set contexts; + + private int totalBatch; + + private int currentBatch; + + public NamingFuzzyWatchSyncRequest() { + + } + + public NamingFuzzyWatchSyncRequest(String pattern, String syncType, Set contexts) { + super(syncType); + this.groupKeyPattern = pattern; + this.contexts = contexts; + } + + public int getTotalBatch() { + return totalBatch; + } + + public void setTotalBatch(int totalBatch) { + this.totalBatch = totalBatch; + } + + public int getCurrentBatch() { + return currentBatch; + } + + public void setCurrentBatch(int currentBatch) { + this.currentBatch = currentBatch; + } + + public static NamingFuzzyWatchSyncRequest buildInitNotifyFinishRequest(String pattern) { + return new NamingFuzzyWatchSyncRequest(pattern, Constants.FINISH_FUZZY_WATCH_INIT_NOTIFY, new HashSet<>(1)); + } + + /** + * byuld SyncNotifyRequest. + * + * @param pattern pattern + * @param syncType syncType + * @param contexts contexts + * @param totalBatch totalBatch + * @param currentBatch currentBatch + * @return + */ + public static NamingFuzzyWatchSyncRequest buildSyncNotifyRequest(String pattern, String syncType, + Set contexts, int totalBatch, int currentBatch) { + NamingFuzzyWatchSyncRequest namingFuzzyWatchSyncRequest = new NamingFuzzyWatchSyncRequest(pattern, syncType, + contexts); + namingFuzzyWatchSyncRequest.currentBatch = currentBatch; + namingFuzzyWatchSyncRequest.totalBatch = totalBatch; + return namingFuzzyWatchSyncRequest; + } + + public String getGroupKeyPattern() { + return groupKeyPattern; + } + + public void setGroupKeyPattern(String groupKeyPattern) { + this.groupKeyPattern = groupKeyPattern; + } + + public Set getContexts() { + return contexts; + } + + public void setContexts(Set contexts) { + this.contexts = contexts; + } + + @Override + public String getModule() { + return NAMING_MODULE; + } + + /** + * fuzzy watch sync context. + */ + public static class Context { + + /** + * service key. + */ + String serviceKey; + + /** + * changed type. + */ + private String changedType; + + /** + * Constructs an empty Context object. + */ + public Context() { + } + + /** + * Builds a new context object with the provided parameters. + * + * @param serviceKey The groupKey associated of the configuration. + * @param changedType The type of the configuration change event. + * @return A new context object initialized with the provided parameters. + */ + public static NamingFuzzyWatchSyncRequest.Context build(String serviceKey, String changedType) { + NamingFuzzyWatchSyncRequest.Context context = new NamingFuzzyWatchSyncRequest.Context(); + context.setServiceKey(serviceKey); + context.setChangedType(changedType); + return context; + } + + public String getServiceKey() { + return serviceKey; + } + + public void setServiceKey(String serviceKey) { + this.serviceKey = serviceKey; + } + + public String getChangedType() { + return changedType; + } + + public void setChangedType(String changedType) { + this.changedType = changedType; + } + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/remote/response/NamingFuzzyWatchChangeNotifyResponse.java b/api/src/main/java/com/alibaba/nacos/api/naming/remote/response/NamingFuzzyWatchChangeNotifyResponse.java new file mode 100644 index 00000000000..3fdeefe0971 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/remote/response/NamingFuzzyWatchChangeNotifyResponse.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2023 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.api.naming.remote.response; + +import com.alibaba.nacos.api.remote.response.Response; + +/** + * Response for notify fuzzy watcher. + * + * @author tanyongquan + */ +public class NamingFuzzyWatchChangeNotifyResponse extends Response { + +} diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/remote/response/NamingFuzzyWatchResponse.java b/api/src/main/java/com/alibaba/nacos/api/naming/remote/response/NamingFuzzyWatchResponse.java new file mode 100644 index 00000000000..a18f81b66ec --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/remote/response/NamingFuzzyWatchResponse.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2023 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.api.naming.remote.response; + +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.api.remote.response.ResponseCode; + +/** + * Nacos naming fuzzy watch service response. + * + * @author tanyongquan + */ +public class NamingFuzzyWatchResponse extends Response { + + public NamingFuzzyWatchResponse() { + } + + public static NamingFuzzyWatchResponse buildSuccessResponse() { + return new NamingFuzzyWatchResponse(); + } + + /** + * Build fail response. + * + * @param message error message + * @return fail response + */ + public static NamingFuzzyWatchResponse buildFailResponse(String message) { + NamingFuzzyWatchResponse result = new NamingFuzzyWatchResponse(); + result.setErrorInfo(ResponseCode.FAIL.getCode(), message); + return result; + } + +} diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/remote/response/NamingFuzzyWatchSyncResponse.java b/api/src/main/java/com/alibaba/nacos/api/naming/remote/response/NamingFuzzyWatchSyncResponse.java new file mode 100644 index 00000000000..90180a3b74d --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/remote/response/NamingFuzzyWatchSyncResponse.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2023 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.api.naming.remote.response; + +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.api.remote.response.ResponseCode; + +/** + * Nacos naming fuzzy watch service response. + * + * @author tanyongquan + */ +public class NamingFuzzyWatchSyncResponse extends Response { + + public NamingFuzzyWatchSyncResponse() { + } + + public static NamingFuzzyWatchSyncResponse buildSuccessResponse() { + return new NamingFuzzyWatchSyncResponse(); + } + + /** + * Build fail response. + * + * @param message error message + * @return fail response + */ + public static NamingFuzzyWatchSyncResponse buildFailResponse(String message) { + NamingFuzzyWatchSyncResponse result = new NamingFuzzyWatchSyncResponse(); + result.setErrorInfo(ResponseCode.FAIL.getCode(), message); + return result; + } + +} diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/utils/NamingUtils.java b/api/src/main/java/com/alibaba/nacos/api/naming/utils/NamingUtils.java index 074c41749e2..a74e94fb6e5 100644 --- a/api/src/main/java/com/alibaba/nacos/api/naming/utils/NamingUtils.java +++ b/api/src/main/java/com/alibaba/nacos/api/naming/utils/NamingUtils.java @@ -29,6 +29,7 @@ import java.util.regex.Pattern; import static com.alibaba.nacos.api.common.Constants.CLUSTER_NAME_PATTERN_STRING; +import static com.alibaba.nacos.api.common.Constants.DEFAULT_NAMESPACE_ID; import static com.alibaba.nacos.api.common.Constants.NUMBER_PATTERN_STRING; /** @@ -67,6 +68,23 @@ public static String getGroupedName(final String serviceName, final String group return resultGroupedName.intern(); } + public static String getServiceKey(String namespace, String group, String serviceName) { + if (StringUtils.isBlank(namespace)) { + namespace = DEFAULT_NAMESPACE_ID; + } + return namespace + Constants.SERVICE_INFO_SPLITER + group + Constants.SERVICE_INFO_SPLITER + serviceName; + } + + /** + * parse service key items for serviceKey. item[0] for namespace item[1] for group item[2] for service name + * + * @param serviceKey serviceKey. + * @return + */ + public static String[] parseServiceKey(String serviceKey) { + return serviceKey.split(Constants.SERVICE_INFO_SPLITER); + } + public static String getServiceName(final String serviceNameWithGroup) { if (StringUtils.isBlank(serviceNameWithGroup)) { return StringUtils.EMPTY; @@ -157,7 +175,8 @@ public static void checkInstanceIsLegal(Instance instance) throws NacosException throw new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.INSTANCE_ERROR, "Instance 'heart beat interval' must less than 'heart beat timeout' and 'ip delete timeout'."); } - if (!StringUtils.isEmpty(instance.getClusterName()) && !CLUSTER_NAME_PATTERN.matcher(instance.getClusterName()).matches()) { + if (!StringUtils.isEmpty(instance.getClusterName()) && !CLUSTER_NAME_PATTERN.matcher(instance.getClusterName()) + .matches()) { throw new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.INSTANCE_ERROR, String.format("Instance 'clusterName' should be characters with only 0-9a-zA-Z-. (current: %s)", instance.getClusterName())); @@ -166,18 +185,21 @@ public static void checkInstanceIsLegal(Instance instance) throws NacosException /** * check batch register is Ephemeral. + * * @param instance instance * @throws NacosException NacosException */ public static void checkInstanceIsEphemeral(Instance instance) throws NacosException { if (!instance.isEphemeral()) { throw new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.INSTANCE_ERROR, - String.format("Batch registration does not allow persistent instance registration , Instance:%s", instance)); + String.format("Batch registration does not allow persistent instance registration , Instance:%s", + instance)); } } /** * Batch verify the validity of instances. + * * @param instances List of instances to be registered * @throws NacosException Nacos */ diff --git a/api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload b/api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload index eba07c73d27..e82f9815f25 100644 --- a/api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload +++ b/api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload @@ -45,6 +45,13 @@ com.alibaba.nacos.api.config.remote.response.ConfigQueryResponse com.alibaba.nacos.api.config.remote.response.ConfigRemoveResponse com.alibaba.nacos.api.config.remote.request.cluster.ConfigChangeClusterSyncRequest com.alibaba.nacos.api.config.remote.response.cluster.ConfigChangeClusterSyncResponse +com.alibaba.nacos.api.config.remote.request.ConfigFuzzyWatchRequest +com.alibaba.nacos.api.config.remote.response.ConfigFuzzyWatchResponse +com.alibaba.nacos.api.config.remote.request.ConfigFuzzyWatchChangeNotifyRequest +com.alibaba.nacos.api.config.remote.response.ConfigFuzzyWatchChangeNotifyResponse +com.alibaba.nacos.api.config.remote.response.ConfigFuzzyWatchSyncResponse +com.alibaba.nacos.api.config.remote.request.ConfigFuzzyWatchSyncRequest + com.alibaba.nacos.api.naming.remote.request.BatchInstanceRequest com.alibaba.nacos.api.naming.remote.request.InstanceRequest com.alibaba.nacos.api.naming.remote.request.PersistentInstanceRequest @@ -58,9 +65,9 @@ com.alibaba.nacos.api.naming.remote.response.NotifySubscriberResponse com.alibaba.nacos.api.naming.remote.response.QueryServiceResponse com.alibaba.nacos.api.naming.remote.response.ServiceListResponse com.alibaba.nacos.api.naming.remote.response.SubscribeServiceResponse -com.alibaba.nacos.api.config.remote.request.ConfigBatchFuzzyListenRequest -com.alibaba.nacos.api.config.remote.response.ConfigBatchFuzzyListenResponse -com.alibaba.nacos.api.config.remote.request.FuzzyListenNotifyChangeRequest -com.alibaba.nacos.api.config.remote.response.FuzzyListenNotifyChangeResponse -com.alibaba.nacos.api.config.remote.request.FuzzyListenNotifyDiffRequest -com.alibaba.nacos.api.config.remote.response.FuzzyListenNotifyDiffResponse +com.alibaba.nacos.api.naming.remote.request.NamingFuzzyWatchRequest +com.alibaba.nacos.api.naming.remote.response.NamingFuzzyWatchResponse +com.alibaba.nacos.api.naming.remote.request.NamingFuzzyWatchChangeNotifyRequest +com.alibaba.nacos.api.naming.remote.response.NamingFuzzyWatchChangeNotifyResponse +com.alibaba.nacos.api.naming.remote.request.NamingFuzzyWatchSyncRequest +com.alibaba.nacos.api.naming.remote.response.NamingFuzzyWatchSyncResponse diff --git a/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java b/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java index 7e921e45ed7..23997b1e613 100644 --- a/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java +++ b/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java @@ -21,15 +21,14 @@ import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.config.ConfigType; import com.alibaba.nacos.api.config.filter.IConfigFilter; -import com.alibaba.nacos.api.config.listener.AbstractFuzzyListenListener; +import com.alibaba.nacos.api.config.listener.FuzzyWatchEventWatcher; import com.alibaba.nacos.api.config.listener.Listener; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager; import com.alibaba.nacos.client.config.filter.impl.ConfigRequest; import com.alibaba.nacos.client.config.filter.impl.ConfigResponse; -import com.alibaba.nacos.client.config.http.ServerHttpAgent; import com.alibaba.nacos.client.config.impl.ClientWorker; -import com.alibaba.nacos.client.config.impl.FuzzyListenContext; +import com.alibaba.nacos.client.config.impl.ConfigFuzzyWatchContext; import com.alibaba.nacos.client.config.impl.ConfigServerListManager; import com.alibaba.nacos.client.config.impl.LocalConfigInfoProcessor; import com.alibaba.nacos.client.config.impl.LocalEncryptedDataKeyProcessor; @@ -42,13 +41,12 @@ import com.alibaba.nacos.common.utils.StringUtils; import org.slf4j.Logger; -import java.util.Collection; import java.util.Collections; import java.util.Properties; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; +import java.util.Set; +import java.util.concurrent.Future; -import static com.alibaba.nacos.api.common.Constants.FUZZY_LISTEN_PATTERN_WILDCARD; +import static com.alibaba.nacos.api.common.Constants.ANY_PATTERN; /** * Config Impl. @@ -64,12 +62,6 @@ public class NacosConfigService implements ConfigService { private static final String DOWN = "DOWN"; - /** - * will be deleted in 2.0 later versions - */ - @Deprecated - ServerHttpAgent agent = null; - /** * long polling. */ @@ -91,8 +83,6 @@ public NacosConfigService(Properties properties) throws NacosException { serverListManager.start(); this.worker = new ClientWorker(this.configFilterChainManager, serverListManager, clientProperties); - // will be deleted in 2.0 later versions - agent = new ServerHttpAgent(serverListManager); } @@ -133,84 +123,52 @@ public void addListener(String dataId, String group, Listener listener) throws N } @Override - public void addFuzzyListener(String fixedGroupName, AbstractFuzzyListenListener listener) throws NacosException { - doFuzzyListen(FUZZY_LISTEN_PATTERN_WILDCARD, fixedGroupName, listener); + public void fuzzyWatch(String fixedGroupName, FuzzyWatchEventWatcher watcher) throws NacosException { + doAddFuzzyWatch(ANY_PATTERN, fixedGroupName, watcher); } @Override - public void addFuzzyListener(String dataIdPattern, String fixedGroupName, AbstractFuzzyListenListener listener) + public void fuzzyWatch(String dataIdPattern, String fixedGroupName, FuzzyWatchEventWatcher watcher) throws NacosException { - // only support prefix match right now - if (!dataIdPattern.endsWith(FUZZY_LISTEN_PATTERN_WILDCARD)) { - if (dataIdPattern.startsWith(FUZZY_LISTEN_PATTERN_WILDCARD)) { - throw new UnsupportedOperationException("Suffix matching for dataId is not supported yet." - + " It will be supported in future updates if needed."); - } else { - throw new UnsupportedOperationException( - "Illegal dataId pattern, please read the documentation and pass a valid pattern."); - } - } - doFuzzyListen(dataIdPattern, fixedGroupName, listener); + doAddFuzzyWatch(dataIdPattern, fixedGroupName, watcher); } @Override - public CompletableFuture> addFuzzyListenerAndGetConfigs(String fixedGroupName, - AbstractFuzzyListenListener listener) throws NacosException { - return doAddFuzzyListenerAndGetConfigs(FUZZY_LISTEN_PATTERN_WILDCARD, fixedGroupName, listener); + public Future> fuzzyWatchWithGroupKeys(String fixedGroupName, FuzzyWatchEventWatcher watcher) + throws NacosException { + return doAddFuzzyWatch(ANY_PATTERN, fixedGroupName, watcher); } @Override - public CompletableFuture> addFuzzyListenerAndGetConfigs(String dataIdPattern, - String fixedGroupName, AbstractFuzzyListenListener listener) throws NacosException { - return doAddFuzzyListenerAndGetConfigs(dataIdPattern, fixedGroupName, listener); - } - - private CompletableFuture> doAddFuzzyListenerAndGetConfigs(String dataIdPattern, - String fixedGroupName, AbstractFuzzyListenListener listener) throws NacosException { - CompletableFuture> future = new CompletableFuture<>(); - if (listener == null) { - future.completeExceptionally(new IllegalArgumentException("Listener cannot be null")); - return future; - } - addFuzzyListener(dataIdPattern, fixedGroupName, listener); - FuzzyListenContext context = worker.getFuzzyListenContext(dataIdPattern, fixedGroupName); - if (context == null) { - future.complete(Collections.emptyList()); - return future; - } - return context.waitForInitializationComplete(future); + public Future> fuzzyWatchWithGroupKeys(String dataIdPattern, String fixedGroupName, + FuzzyWatchEventWatcher watcher) throws NacosException { + return doAddFuzzyWatch(dataIdPattern, fixedGroupName, watcher); } - private void doFuzzyListen(String dataIdPattern, String fixedGroupName, AbstractFuzzyListenListener listener) - throws NacosException { - if (listener == null) { - return; - } - listener.setUuid(UUID.randomUUID().toString()); - if (!worker.containsPatternMatchCache(dataIdPattern, fixedGroupName)) { - worker.addTenantFuzzyListenListens(dataIdPattern, fixedGroupName, Collections.singletonList(listener)); - } else { - worker.duplicateFuzzyListenInit(dataIdPattern, fixedGroupName, listener); - } + private Future> doAddFuzzyWatch(String dataIdPattern, String fixedGroupName, + FuzzyWatchEventWatcher watcher) throws NacosException { + ConfigFuzzyWatchContext configFuzzyWatchContext = worker.addTenantFuzzyWatcher(dataIdPattern, fixedGroupName, + watcher); + return configFuzzyWatchContext.createNewFuture(); } @Override - public void cancelFuzzyListen(String fixedGroupName, AbstractFuzzyListenListener listener) throws NacosException { - cancelFuzzyListen(FUZZY_LISTEN_PATTERN_WILDCARD, fixedGroupName, listener); + public void cancelFuzzyWatch(String fixedGroupName, FuzzyWatchEventWatcher watcher) throws NacosException { + cancelFuzzyWatch(ANY_PATTERN, fixedGroupName, watcher); } @Override - public void cancelFuzzyListen(String dataIdPattern, String fixedGroupName, AbstractFuzzyListenListener listener) + public void cancelFuzzyWatch(String dataIdPattern, String fixedGroupName, FuzzyWatchEventWatcher watcher) throws NacosException { - doCancelFuzzyListen(dataIdPattern, fixedGroupName, listener); + doCancelFuzzyListen(dataIdPattern, fixedGroupName, watcher); } - private void doCancelFuzzyListen(String dataIdPattern, String groupNamePattern, - AbstractFuzzyListenListener listener) throws NacosException { - if (null == listener) { + private void doCancelFuzzyListen(String dataIdPattern, String groupNamePattern, FuzzyWatchEventWatcher watcher) + throws NacosException { + if (null == watcher) { return; } - worker.removeFuzzyListenListener(dataIdPattern, groupNamePattern, listener); + worker.removeFuzzyListenListener(dataIdPattern, groupNamePattern, watcher); } @Override @@ -261,11 +219,11 @@ private String getConfigInner(String tenant, String dataId, String group, long t // changing config needed in the same time, while nacos server is down. String content = LocalConfigInfoProcessor.getFailover(worker.getAgentName(), dataId, group, tenant); if (content != null) { - LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}", - worker.getAgentName(), dataId, group, tenant); + LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}", worker.getAgentName(), + dataId, group, tenant); cr.setContent(content); - String encryptedDataKey = LocalEncryptedDataKeyProcessor.getEncryptDataKeyFailover(agent.getName(), dataId, - group, tenant); + String encryptedDataKey = LocalEncryptedDataKeyProcessor.getEncryptDataKeyFailover(worker.getAgentName(), + dataId, group, tenant); cr.setEncryptedDataKey(encryptedDataKey); configFilterChainManager.doFilter(null, cr); content = cr.getContent(); @@ -290,12 +248,12 @@ private String getConfigInner(String tenant, String dataId, String group, long t content = LocalConfigInfoProcessor.getSnapshot(worker.getAgentName(), dataId, group, tenant); if (content != null) { - LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}", - worker.getAgentName(), dataId, group, tenant); + LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}", worker.getAgentName(), + dataId, group, tenant); } cr.setContent(content); - String encryptedDataKey = LocalEncryptedDataKeyProcessor.getEncryptDataKeySnapshot(agent.getName(), dataId, - group, tenant); + String encryptedDataKey = LocalEncryptedDataKeyProcessor.getEncryptDataKeySnapshot(worker.getAgentName(), + dataId, group, tenant); cr.setEncryptedDataKey(encryptedDataKey); configFilterChainManager.doFilter(null, cr); content = cr.getContent(); diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientFuzzyWatchNotifyRequestHandler.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientFuzzyWatchNotifyRequestHandler.java new file mode 100644 index 00000000000..f6b9a934afd --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientFuzzyWatchNotifyRequestHandler.java @@ -0,0 +1,53 @@ +/* + * 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.client.config.impl; + +import com.alibaba.nacos.api.config.remote.request.ConfigFuzzyWatchChangeNotifyRequest; +import com.alibaba.nacos.api.config.remote.request.ConfigFuzzyWatchSyncRequest; +import com.alibaba.nacos.api.remote.request.Request; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.common.remote.client.Connection; +import com.alibaba.nacos.common.remote.client.ServerRequestHandler; + +/** + * fuzzy watch request from server . + * @author shiyiyue + */ +public class ClientFuzzyWatchNotifyRequestHandler implements ServerRequestHandler { + + ConfigFuzzyWatchGroupKeyHolder configFuzzyWatchGroupKeyHolder; + + public ClientFuzzyWatchNotifyRequestHandler(ConfigFuzzyWatchGroupKeyHolder configFuzzyWatchGroupKeyHolder) { + + this.configFuzzyWatchGroupKeyHolder = configFuzzyWatchGroupKeyHolder; + } + + @Override + public Response requestReply(Request request, Connection connection) { + //fuzzy watch diff reconciliation sync + if (request instanceof ConfigFuzzyWatchSyncRequest) { + return configFuzzyWatchGroupKeyHolder.handleFuzzyWatchNotifyDiffRequest( + (ConfigFuzzyWatchSyncRequest) request); + } + //fuzzy watch changed notify for a single config. include config changed or config delete. + if (request instanceof ConfigFuzzyWatchChangeNotifyRequest) { + return configFuzzyWatchGroupKeyHolder.handlerFuzzyWatchChangeNotifyRequest( + (ConfigFuzzyWatchChangeNotifyRequest) request); + } + return null; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java index b2ab88f7774..fe04c17623e 100644 --- a/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java @@ -19,26 +19,20 @@ import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.config.ConfigType; -import com.alibaba.nacos.api.config.listener.AbstractFuzzyListenListener; +import com.alibaba.nacos.api.config.listener.FuzzyWatchEventWatcher; import com.alibaba.nacos.api.config.listener.Listener; import com.alibaba.nacos.api.config.remote.request.ClientConfigMetricRequest; -import com.alibaba.nacos.api.config.remote.request.ConfigBatchFuzzyListenRequest; import com.alibaba.nacos.api.config.remote.request.ConfigBatchListenRequest; import com.alibaba.nacos.api.config.remote.request.ConfigChangeNotifyRequest; import com.alibaba.nacos.api.config.remote.request.ConfigPublishRequest; import com.alibaba.nacos.api.config.remote.request.ConfigQueryRequest; import com.alibaba.nacos.api.config.remote.request.ConfigRemoveRequest; -import com.alibaba.nacos.api.config.remote.request.FuzzyListenNotifyChangeRequest; -import com.alibaba.nacos.api.config.remote.request.FuzzyListenNotifyDiffRequest; import com.alibaba.nacos.api.config.remote.response.ClientConfigMetricResponse; -import com.alibaba.nacos.api.config.remote.response.ConfigBatchFuzzyListenResponse; import com.alibaba.nacos.api.config.remote.response.ConfigChangeBatchListenResponse; import com.alibaba.nacos.api.config.remote.response.ConfigChangeNotifyResponse; import com.alibaba.nacos.api.config.remote.response.ConfigPublishResponse; import com.alibaba.nacos.api.config.remote.response.ConfigQueryResponse; import com.alibaba.nacos.api.config.remote.response.ConfigRemoveResponse; -import com.alibaba.nacos.api.config.remote.response.FuzzyListenNotifyChangeResponse; -import com.alibaba.nacos.api.config.remote.response.FuzzyListenNotifyDiffResponse; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.remote.RemoteConstants; import com.alibaba.nacos.api.remote.request.Request; @@ -72,7 +66,6 @@ import com.alibaba.nacos.common.remote.client.ServerListFactory; import com.alibaba.nacos.common.utils.ConnLabelsUtils; import com.alibaba.nacos.common.utils.ConvertUtils; -import com.alibaba.nacos.common.utils.GroupKeyPattern; import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.MD5Utils; import com.alibaba.nacos.common.utils.StringUtils; @@ -92,7 +85,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.UUID; @@ -138,13 +130,10 @@ public class ClientWorker implements Closeable { */ private final AtomicReference> cacheMap = new AtomicReference<>(new HashMap<>()); - /** - * fuzzyListenGroupKey -> fuzzyListenContext. - */ - private final AtomicReference> fuzzyListenContextMap = new AtomicReference<>( - new HashMap<>()); private final DefaultLabelsCollectorManager defaultLabelsCollectorManager = new DefaultLabelsCollectorManager(); + private ConfigFuzzyWatchGroupKeyHolder configFuzzyWatchGroupKeyHolder; + private Map appLables = new HashMap<>(); private final ConfigFilterChainManager configFilterChainManager; @@ -170,49 +159,6 @@ public class ClientWorker implements Closeable { */ private final List taskIdCacheCountList = new ArrayList<>(); - /** - * index(taskId)-> total context count for this taskId. - */ - private final List taskIdContextCountList = new ArrayList<>(); - - @SuppressWarnings("PMD.ThreadPoolCreationRule") - public ClientWorker(final ConfigFilterChainManager configFilterChainManager, ServerListManager serverListManager, - final NacosClientProperties properties) throws NacosException { - this.configFilterChainManager = configFilterChainManager; - - init(properties); - - agent = new ConfigRpcTransportClient(properties, serverListManager); - ScheduledExecutorService executorService = Executors.newScheduledThreadPool(initWorkerThreadCount(properties), - new NameThreadFactory("com.alibaba.nacos.client.Worker")); - agent.setExecutor(executorService); - agent.start(); - - } - - /** - * Adds a list of fuzzy listen listeners for the specified data ID pattern and group. - * - * @param dataIdPattern The pattern of the data ID to listen for. - * @param group The group of the configuration. - * @param listeners The list of listeners to add. - * @throws NacosException If an error occurs while adding the listeners. - */ - public void addTenantFuzzyListenListens(String dataIdPattern, String group, - List listeners) throws NacosException { - group = blank2defaultGroup(group); - FuzzyListenContext context = addFuzzyListenContextIfAbsent(dataIdPattern, group); - synchronized (context) { - for (AbstractFuzzyListenListener listener : listeners) { - context.addListener(listener); - } - context.setInitializing(true); - context.setDiscard(false); - context.getIsConsistentWithServer().set(false); - agent.notifyFuzzyListenConfig(); - } - } - /** * Add listeners for data. * @@ -344,27 +290,16 @@ public void removeTenantListener(String dataId, String group, Listener listener) } /** - * Initializes a duplicate fuzzy listen for the specified data ID pattern, group, and listener. + * Adds a list of fuzzy listen listeners for the specified data ID pattern and group. * - * @param dataIdPattern The pattern of the data ID to listen for. - * @param group The group of the configuration. - * @param listener The listener to add. + * @param dataIdPattern The pattern of the data ID to listen for. + * @param groupPattern The group of the configuration. + * @param fuzzyWatchEventWatcher The configFuzzyWatcher to add. + * @throws NacosException If an error occurs while adding the listeners. */ - public void duplicateFuzzyListenInit(String dataIdPattern, String group, AbstractFuzzyListenListener listener) { - String groupKeyPattern = GroupKeyPattern.generateFuzzyListenGroupKeyPattern(dataIdPattern, group); - Map contextMap = fuzzyListenContextMap.get(); - FuzzyListenContext context = contextMap.get(groupKeyPattern); - if (Objects.isNull(context)) { - return; - } - synchronized (context) { - context.addListener(listener); - - for (String dataId : context.getDataIds()) { - NotifyCenter.publishEvent(FuzzyListenNotifyEvent.buildNotifyPatternSpecificListenerEvent(group, dataId, - Constants.ConfigChangeType.ADD_CONFIG, groupKeyPattern, listener.getUuid())); - } - } + public ConfigFuzzyWatchContext addTenantFuzzyWatcher(String dataIdPattern, String groupPattern, + FuzzyWatchEventWatcher fuzzyWatchEventWatcher) { + return configFuzzyWatchGroupKeyHolder.registerFuzzyWatcher(dataIdPattern, groupPattern, fuzzyWatchEventWatcher); } /** @@ -372,43 +307,26 @@ public void duplicateFuzzyListenInit(String dataIdPattern, String group, Abstrac * * @param dataIdPattern The pattern of the data ID. * @param group The group of the configuration. - * @param listener The listener to remove. + * @param watcher The listener to remove. * @throws NacosException If an error occurs while removing the listener. */ - public void removeFuzzyListenListener(String dataIdPattern, String group, AbstractFuzzyListenListener listener) - throws NacosException { - group = blank2defaultGroup(group); - FuzzyListenContext fuzzyListenContext = getFuzzyListenContext(dataIdPattern, group); - if (fuzzyListenContext != null) { - synchronized (fuzzyListenContext) { - fuzzyListenContext.removeListener(listener); - if (fuzzyListenContext.getListeners().isEmpty()) { - fuzzyListenContext.setDiscard(true); - fuzzyListenContext.getIsConsistentWithServer().set(false); - agent.removeFuzzyListenContext(dataIdPattern, group); - } - } - } + public void removeFuzzyListenListener(String dataIdPattern, String group, FuzzyWatchEventWatcher watcher) { + configFuzzyWatchGroupKeyHolder.removeFuzzyWatcher(dataIdPattern, group, watcher); } - /** - * Removes the fuzzy listen context for the specified data ID pattern and group. - * - * @param dataIdPattern The pattern of the data ID. - * @param group The group of the configuration. - */ - public void removeFuzzyListenContext(String dataIdPattern, String group) { - String groupKeyPattern = GroupKeyPattern.generateFuzzyListenGroupKeyPattern(dataIdPattern, group); - synchronized (fuzzyListenContextMap) { - Map copy = new HashMap<>(fuzzyListenContextMap.get()); - FuzzyListenContext removedContext = copy.remove(groupKeyPattern); - if (removedContext != null) { - decreaseContextTaskIdCount(removedContext.getTaskId()); + void removeCache(String dataId, String group, String tenant) { + String groupKey = GroupKey.getKeyTenant(dataId, group, tenant); + synchronized (cacheMap) { + Map copy = new HashMap<>(cacheMap.get()); + CacheData remove = copy.remove(groupKey); + if (remove != null) { + decreaseTaskIdCount(remove.getTaskId()); } - fuzzyListenContextMap.set(copy); + cacheMap.set(copy); } - LOGGER.info("[{}] [fuzzy-listen-unsubscribe] {}", agent.getName(), groupKeyPattern); - // TODO: Record metric for fuzzy listen unsubscribe. + LOGGER.info("[{}] [unsubscribe] {}", agent.getName(), groupKey); + + MetricsMonitor.getListenConfigCountMonitor().set(cacheMap.get().size()); } /** @@ -535,28 +453,6 @@ public CacheData addCacheDataIfAbsent(String dataId, String group, String tenant return cache; } - /** - * Removes the cache entry associated with the given data ID, group, and tenant. - * - * @param dataId The data ID. - * @param group The group name. - * @param tenant The tenant. - */ - public void removeCache(String dataId, String group, String tenant) { - String groupKey = GroupKey.getKeyTenant(dataId, group, tenant); - synchronized (cacheMap) { - Map copy = new HashMap<>(cacheMap.get()); - CacheData remove = copy.remove(groupKey); - if (remove != null) { - decreaseTaskIdCount(remove.getTaskId()); - } - cacheMap.set(copy); - } - LOGGER.info("[{}] [unsubscribe] {}", agent.getName(), groupKey); - - MetricsMonitor.getListenConfigCountMonitor().set(cacheMap.get().size()); - } - /** * Put cache. * @@ -571,114 +467,23 @@ private void putCache(String key, CacheData cache) { } } - /** - * Adds a fuzzy listen context if it doesn't already exist for the specified data ID pattern and group. If the - * context already exists, returns the existing context. - * - * @param dataIdPattern The pattern of the data ID. - * @param group The group of the configuration. - * @return The fuzzy listen context for the specified data ID pattern and group. - */ - public FuzzyListenContext addFuzzyListenContextIfAbsent(String dataIdPattern, String group) { - FuzzyListenContext context = getFuzzyListenContext(dataIdPattern, group); - if (context != null) { - return context; - } - synchronized (fuzzyListenContextMap) { - FuzzyListenContext contextFromMap = getFuzzyListenContext(dataIdPattern, group); - if (contextFromMap != null) { - context = contextFromMap; - context.getIsConsistentWithServer().set(false); - } else { - context = new FuzzyListenContext(agent.getName(), dataIdPattern, group); - int taskId = calculateContextTaskId(); - increaseContextTaskIdCount(taskId); - context.setTaskId(taskId); - } - } - - Map copy = new HashMap<>(fuzzyListenContextMap.get()); - String groupKeyPattern = GroupKeyPattern.generateFuzzyListenGroupKeyPattern(dataIdPattern, group); - copy.put(groupKeyPattern, context); - fuzzyListenContextMap.set(copy); - - // TODO: Record metrics - - return context; - } - - /** - * Increases the count for the specified task ID in the given count list. - * - * @param taskId The ID of the task for which the count needs to be increased. - */ private void increaseTaskIdCount(int taskId) { - increaseCount(taskId, taskIdCacheCountList); + taskIdCacheCountList.get(taskId).incrementAndGet(); } - /** - * Decreases the count for the specified task ID in the given count list. - * - * @param taskId The ID of the task for which the count needs to be decreased. - */ private void decreaseTaskIdCount(int taskId) { - decreaseCount(taskId, taskIdCacheCountList); + taskIdCacheCountList.get(taskId).decrementAndGet(); } - /** - * Increases the context task ID count in the corresponding list. - * - * @param taskId The ID of the context task for which the count needs to be increased. - */ - private void increaseContextTaskIdCount(int taskId) { - increaseCount(taskId, taskIdContextCountList); - } - - /** - * Decreases the context task ID count in the corresponding list. - * - * @param taskId The ID of the context task for which the count needs to be decreased. - */ - private void decreaseContextTaskIdCount(int taskId) { - decreaseCount(taskId, taskIdContextCountList); - } - - /** - * Calculates the task ID based on the configuration size. - * - * @return The calculated task ID. - */ private int calculateTaskId() { - return calculateId(taskIdCacheCountList, (long) ParamUtil.getPerTaskConfigSize()); - } - - /** - * Calculates the context task ID based on the configuration size. - * - * @return The calculated context task ID. - */ - private int calculateContextTaskId() { - return calculateId(taskIdContextCountList, (long) ParamUtil.getPerTaskContextSize()); - } - - /** - * Increases the count for the specified task ID in the given count list. - * - * @param taskId The ID of the task for which the count needs to be increased. - * @param countList The list containing the counts for different task IDs. - */ - private void increaseCount(int taskId, List countList) { - countList.get(taskId).incrementAndGet(); - } - - /** - * Decreases the count for the specified task ID in the given count list. - * - * @param taskId The ID of the task for which the count needs to be decreased. - * @param countList The list containing the counts for different task IDs. - */ - private void decreaseCount(int taskId, List countList) { - countList.get(taskId).decrementAndGet(); + int perTaskSize = (int) ParamUtil.getPerTaskConfigSize(); + for (int index = 0; index < taskIdCacheCountList.size(); index++) { + if (taskIdCacheCountList.get(index).get() < perTaskSize) { + return index; + } + } + taskIdCacheCountList.add(new AtomicInteger(0)); + return taskIdCacheCountList.size() - 1; } public CacheData getCache(String dataId, String group) { @@ -692,35 +497,6 @@ public CacheData getCache(String dataId, String group, String tenant) { return cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant)); } - /** - * Calculates the task ID based on the provided count list and per-task size. - * - * @param countList The list containing the counts for different task IDs. - * @param perTaskSize The size of each task. - * @return The calculated task ID. - */ - private int calculateId(List countList, long perTaskSize) { - for (int index = 0; index < countList.size(); index++) { - if (countList.get(index).get() < perTaskSize) { - return index; - } - } - countList.add(new AtomicInteger(0)); - return countList.size() - 1; - } - - /** - * Retrieves the FuzzyListenContext for the given data ID pattern and group. - * - * @param dataIdPattern The data ID pattern. - * @param group The group name. - * @return The corresponding FuzzyListenContext, or null if not found. - */ - public FuzzyListenContext getFuzzyListenContext(String dataIdPattern, String group) { - return fuzzyListenContextMap.get() - .get(GroupKeyPattern.generateFuzzyListenGroupKeyPattern(dataIdPattern, group)); - } - public ConfigResponse getServerConfig(String dataId, String group, String tenant, long readTimeout, boolean notify) throws NacosException { if (StringUtils.isBlank(group)) { @@ -733,31 +509,21 @@ private String blank2defaultGroup(String group) { return StringUtils.isBlank(group) ? Constants.DEFAULT_GROUP : group.trim(); } - /** - * Checks if the pattern match cache contains an entry for the specified data ID pattern and group. - * - * @param dataIdPattern The data ID pattern. - * @param group The group name. - * @return True if the cache contains an entry, false otherwise. - */ - public boolean containsPatternMatchCache(String dataIdPattern, String group) { - Map contextMap = fuzzyListenContextMap.get(); - String groupKeyPattern = GroupKeyPattern.generateFuzzyListenGroupKeyPattern(dataIdPattern, group); - return contextMap.containsKey(groupKeyPattern); - } - @SuppressWarnings("PMD.ThreadPoolCreationRule") - public ClientWorker(final ConfigFilterChainManager configFilterChainManager, ConfigServerListManager serverListManager, - final NacosClientProperties properties) throws NacosException { + public ClientWorker(final ConfigFilterChainManager configFilterChainManager, + ConfigServerListManager serverListManager, final NacosClientProperties properties) throws NacosException { this.configFilterChainManager = configFilterChainManager; init(properties); agent = new ConfigRpcTransportClient(properties, serverListManager); + + configFuzzyWatchGroupKeyHolder = new ConfigFuzzyWatchGroupKeyHolder(agent, uuid); ScheduledExecutorService executorService = Executors.newScheduledThreadPool(initWorkerThreadCount(properties), new NameThreadFactory("com.alibaba.nacos.client.Worker")); agent.setExecutor(executorService); agent.start(); + configFuzzyWatchGroupKeyHolder.start(); } @@ -852,28 +618,14 @@ public boolean isHealthServer() { public class ConfigRpcTransportClient extends ConfigTransportClient { - /** - * 5 minutes to check all fuzzy listen context. - */ - private static final long FUZZY_LISTEN_ALL_SYNC_INTERNAL = 5 * 60 * 1000L; - - private final String configListenerTaskPrefix = "nacos.client.config.listener.task"; - - private final String fuzzyListenerTaskPrefix = "nacos.client.config.fuzzyListener.task"; + Map multiTaskExecutor = new HashMap<>(); private final BlockingQueue listenExecutebell = new ArrayBlockingQueue<>(1); - private final Map multiTaskExecutor = new HashMap<>(); - private final Object bellItem = new Object(); private long lastAllSyncTime = System.currentTimeMillis(); - /** - * fuzzyListenExecuteBell. - */ - private final BlockingQueue fuzzyListenExecuteBell = new ArrayBlockingQueue<>(1); - Subscriber subscriber = null; /** @@ -881,11 +633,6 @@ public class ConfigRpcTransportClient extends ConfigTransportClient { */ private static final long ALL_SYNC_INTERNAL = 3 * 60 * 1000L; - /** - * fuzzyListenLastAllSyncTime. - */ - private long fuzzyListenLastAllSyncTime = System.currentTimeMillis(); - public ConfigRpcTransportClient(NacosClientProperties properties, ConfigServerListManager serverListManager) { super(properties, serverListManager); } @@ -949,85 +696,6 @@ private Map getLabels() { return labels; } - /** - * Handles a fuzzy listen init notify request. - * - *

This method processes the incoming fuzzy listen init notify request from a client. It updates the fuzzy - * listen context based on the request's information, and publishes events if necessary. - * - * @param request The fuzzy listen init notify request to handle. - * @param clientName The name of the client sending the request. - * @return A {@link FuzzyListenNotifyDiffResponse} indicating the result of handling the request. - */ - private FuzzyListenNotifyDiffResponse handleFuzzyListenNotifyDiffRequest(FuzzyListenNotifyDiffRequest request, - String clientName) { - LOGGER.info("[{}] [fuzzy-listen-config-push] config init.", clientName); - String groupKeyPattern = request.getGroupKeyPattern(); - FuzzyListenContext context = fuzzyListenContextMap.get().get(groupKeyPattern); - if (Constants.ConfigChangeType.FINISH_LISTEN_INIT.equals(request.getServiceChangedType())) { - context.markInitializationComplete(); - return new FuzzyListenNotifyDiffResponse(); - } - for (FuzzyListenNotifyDiffRequest.Context requestContext : request.getContexts()) { - Set existsDataIds = context.getDataIds(); - switch (requestContext.getType()) { - case Constants.ConfigChangeType.LISTEN_INIT: - case Constants.ConfigChangeType.ADD_CONFIG: - if (existsDataIds.add(requestContext.getDataId())) { - NotifyCenter.publishEvent(FuzzyListenNotifyEvent.buildNotifyPatternAllListenersEvent( - requestContext.getGroup(), requestContext.getDataId(), request.getGroupKeyPattern(), - Constants.ConfigChangeType.ADD_CONFIG)); - } - break; - case Constants.ConfigChangeType.DELETE_CONFIG: - if (existsDataIds.remove(requestContext.getDataId())) { - NotifyCenter.publishEvent(FuzzyListenNotifyEvent.buildNotifyPatternAllListenersEvent( - requestContext.getGroup(), requestContext.getDataId(), request.getGroupKeyPattern(), - Constants.ConfigChangeType.DELETE_CONFIG)); - } - break; - default: - LOGGER.error("Invalid config change type: {}", requestContext.getType()); - break; - } - } - return new FuzzyListenNotifyDiffResponse(); - } - - /** - * Handles a fuzzy listen notify change request. - * - *

This method processes the incoming fuzzy listen notify change request from a client. It updates the fuzzy - * listen context based on the request's information, and publishes events if necessary. - * - * @param request The fuzzy listen notify change request to handle. - * @param clientName The name of the client sending the request. - */ - private FuzzyListenNotifyChangeResponse handlerFuzzyListenNotifyChangeRequest( - FuzzyListenNotifyChangeRequest request, String clientName) { - LOGGER.info("[{}] [fuzzy-listen-config-push] config changed.", clientName); - Map listenContextMap = fuzzyListenContextMap.get(); - Set matchedPatterns = GroupKeyPattern.getConfigMatchedPatternsWithoutNamespace(request.getDataId(), - request.getGroup(), listenContextMap.keySet()); - for (String matchedPattern : matchedPatterns) { - FuzzyListenContext context = listenContextMap.get(matchedPattern); - if (request.isExist()) { - if (context.getDataIds().add(request.getDataId())) { - NotifyCenter.publishEvent( - FuzzyListenNotifyEvent.buildNotifyPatternAllListenersEvent(request.getGroup(), - request.getDataId(), matchedPattern, Constants.ConfigChangeType.ADD_CONFIG)); - } - } else { - if (context.getDataIds().remove(request.getDataId())) { - NotifyCenter.publishEvent( - FuzzyListenNotifyEvent.buildNotifyPatternAllListenersEvent(request.getGroup(), - request.getDataId(), matchedPattern, Constants.ConfigChangeType.DELETE_CONFIG)); - } - } - } - return new FuzzyListenNotifyChangeResponse(); - } - ConfigChangeNotifyResponse handleConfigChangeNotifyRequest(ConfigChangeNotifyRequest configChangeNotifyRequest, String clientName) { LOGGER.info("[{}] [server-push] config changed. dataId={}, group={},tenant={}", clientName, @@ -1059,18 +727,11 @@ private void initRpcClientHandler(final RpcClient rpcClientInner) { * Register Config Change /Config ReSync Handler */ rpcClientInner.registerServerRequestHandler((request, connection) -> { + //config change notify if (request instanceof ConfigChangeNotifyRequest) { return handleConfigChangeNotifyRequest((ConfigChangeNotifyRequest) request, rpcClientInner.getName()); } - if (request instanceof FuzzyListenNotifyDiffRequest) { - return handleFuzzyListenNotifyDiffRequest((FuzzyListenNotifyDiffRequest) request, - rpcClientInner.getName()); - } - if (request instanceof FuzzyListenNotifyChangeRequest) { - return handlerFuzzyListenNotifyChangeRequest((FuzzyListenNotifyChangeRequest) request, - rpcClientInner.getName()); - } return null; }); @@ -1081,6 +742,9 @@ private void initRpcClientHandler(final RpcClient rpcClientInner) { return null; }); + rpcClientInner.registerServerRequestHandler( + new ClientFuzzyWatchNotifyRequestHandler(configFuzzyWatchGroupKeyHolder)); + rpcClientInner.registerConnectionListener(new ConnectionEventListener() { @Override @@ -1089,13 +753,13 @@ public void onConnected(Connection connection) { notifyListenConfig(); LOGGER.info("[{}] Connected,notify fuzzy listen context...", rpcClientInner.getName()); - notifyFuzzyListenConfig(); + configFuzzyWatchGroupKeyHolder.notifyFuzzyWatchSync(); } @Override public void onDisConnect(Connection connection) { String taskId = rpcClientInner.getLabels().get("taskId"); - LOGGER.info("[{}] DisConnected,clear listen context...", rpcClientInner.getName()); + LOGGER.info("[{}] DisConnected,reset listen context", rpcClientInner.getName()); Collection values = cacheMap.get().values(); for (CacheData cacheData : values) { @@ -1108,17 +772,8 @@ public void onDisConnect(Connection connection) { } } - Collection fuzzyListenContexts = fuzzyListenContextMap.get().values(); - - for (FuzzyListenContext context : fuzzyListenContexts) { - if (StringUtils.isNotBlank(taskId)) { - if (Integer.valueOf(taskId).equals(context.getTaskId())) { - context.getIsConsistentWithServer().set(false); - } - } else { - context.getIsConsistentWithServer().set(false); - } - } + LOGGER.info("[{}] DisConnected,reset fuzzy watch consistence status", rpcClientInner.getName()); + configFuzzyWatchGroupKeyHolder.resetConsistenceStatus(); } }); @@ -1155,25 +810,6 @@ public Class subscribeType() { } }; NotifyCenter.registerSubscriber(subscriber); - - NotifyCenter.registerSubscriber(new Subscriber() { - @Override - public void onEvent(Event event) { - FuzzyListenNotifyEvent fuzzyListenNotifyEvent = (FuzzyListenNotifyEvent) event; - FuzzyListenContext context = fuzzyListenContextMap.get() - .get(fuzzyListenNotifyEvent.getGroupKeyPattern()); - if (context == null) { - return; - } - context.notifyListener(fuzzyListenNotifyEvent.getDataId(), fuzzyListenNotifyEvent.getType(), - fuzzyListenNotifyEvent.getUuid()); - } - - @Override - public Class subscribeType() { - return FuzzyListenNotifyEvent.class; - } - }); } @Override @@ -1198,26 +834,6 @@ public void startInternal() { } }, 0L, TimeUnit.MILLISECONDS); - executor.schedule(() -> { - while (!executor.isShutdown() && !executor.isTerminated()) { - try { - fuzzyListenExecuteBell.poll(5L, TimeUnit.SECONDS); - if (executor.isShutdown() || executor.isTerminated()) { - continue; - } - executeConfigFuzzyListen(); - } catch (Throwable e) { - LOGGER.error("[rpc-fuzzy-listen-execute] rpc fuzzy listen exception", e); - try { - Thread.sleep(50L); - } catch (InterruptedException interruptedException) { - //ignore - } - notifyFuzzyListenConfig(); - } - } - }, 0L, TimeUnit.MILLISECONDS); - } @Override @@ -1230,11 +846,6 @@ public void notifyListenConfig() { listenExecutebell.offer(bellItem); } - @Override - public void notifyFuzzyListenConfig() { - fuzzyListenExecuteBell.offer(bellItem); - } - @Override public void executeConfigListen() throws NacosException { @@ -1290,118 +901,6 @@ public void executeConfigListen() throws NacosException { } - /** - * Execute fuzzy listen configuration changes. - * - *

This method iterates through all fuzzy listen contexts and determines whether they need to be added or - * removed based on their consistency with the server and discard status. It then calls the appropriate method - * to execute the fuzzy listen operation. - * - * @throws NacosException If an error occurs during the execution of fuzzy listen configuration changes. - */ - @Override - public void executeConfigFuzzyListen() throws NacosException { - Map> needSyncContextMap = new HashMap<>(16); - - // Obtain the current timestamp - long now = System.currentTimeMillis(); - - // Determine whether a full synchronization is needed - boolean needAllSync = now - fuzzyListenLastAllSyncTime >= FUZZY_LISTEN_ALL_SYNC_INTERNAL; - - // Iterate through all fuzzy listen contexts - for (FuzzyListenContext context : fuzzyListenContextMap.get().values()) { - // Check if the context is consistent with the server - if (context.getIsConsistentWithServer().get()) { - // Skip if a full synchronization is not needed - if (!needAllSync) { - continue; - } - } - - List needSyncContexts = needSyncContextMap.computeIfAbsent( - String.valueOf(context.getTaskId()), k -> new LinkedList<>()); - needSyncContexts.add(context); - } - - // Execute fuzzy listen operation for addition - doExecuteConfigFuzzyListen(needSyncContextMap); - - // Update last all sync time if a full synchronization was performed - if (needAllSync) { - fuzzyListenLastAllSyncTime = now; - } - } - - /** - * Execute fuzzy listen configuration changes for a specific map of contexts. - * - *

This method submits tasks to execute fuzzy listen operations asynchronously for the provided contexts. It - * waits for all tasks to complete and logs any errors that occur. - * - * @param contextMap The map of contexts to execute fuzzy listen operations for. - * @throws NacosException If an error occurs during the execution of fuzzy listen configuration changes. - */ - private void doExecuteConfigFuzzyListen(Map> contextMap) - throws NacosException { - // Return if the context map is null or empty - if (contextMap == null || contextMap.isEmpty()) { - return; - } - - // List to hold futures for asynchronous tasks - List> listenFutures = new ArrayList<>(); - - // Iterate through the context map and submit tasks for execution - for (Map.Entry> entry : contextMap.entrySet()) { - String taskId = entry.getKey(); - List contexts = entry.getValue(); - RpcClient rpcClient = ensureRpcClient(taskId); - ExecutorService executorService = ensureSyncExecutor(fuzzyListenerTaskPrefix, taskId); - // Submit task for execution - Future future = executorService.submit(() -> { - ConfigBatchFuzzyListenRequest configBatchFuzzyListenRequest = buildFuzzyListenConfigRequest( - contexts); - try { - // Execute the fuzzy listen operation - ConfigBatchFuzzyListenResponse listenResponse = (ConfigBatchFuzzyListenResponse) requestProxy( - rpcClient, configBatchFuzzyListenRequest); - if (listenResponse != null && listenResponse.isSuccess()) { - for (FuzzyListenContext context : contexts) { - if (context.isDiscard()) { - ClientWorker.this.removeFuzzyListenContext(context.getDataIdPattern(), - context.getGroup()); - } else { - context.getIsConsistentWithServer().set(true); - } - } - } - } catch (NacosException e) { - // Log error and retry after a short delay - LOGGER.error("Execute batch fuzzy listen config change error.", e); - try { - Thread.sleep(50L); - } catch (InterruptedException interruptedException) { - // Ignore interruption - } - // Retry notification - notifyFuzzyListenConfig(); - } - }); - listenFutures.add(future); - } - - // Wait for all tasks to complete - for (Future future : listenFutures) { - try { - future.get(); - } catch (Throwable throwable) { - // Log async listen error - LOGGER.error("Async fuzzy listen config change error.", throwable); - } - } - } - /** * Checks and handles local configuration for a given CacheData object. This method evaluates the use of * failover files for local configuration storage and updates the CacheData accordingly. @@ -1450,41 +949,16 @@ public void checkLocalConfig(CacheData cacheData) { } } - /** - * Ensure to create a synchronous executor for the given task prefix and task ID. If an executor for the given - * task doesn't exist yet, a new executor will be created. - * - * @param taskPrefix The prefix of the task identifier - * @param taskId The ID of the task - * @return The created or existing executor - */ - private ExecutorService ensureSyncExecutor(String taskPrefix, String taskId) { - // Generate the unique task identifier - String taskIdentifier = generateTaskIdentifier(taskPrefix, taskId); - - // If the task identifier doesn't exist in the existing executors, create a new executor and add it to the multiTaskExecutor map - if (!multiTaskExecutor.containsKey(taskIdentifier)) { - multiTaskExecutor.put(taskIdentifier, + private ExecutorService ensureSyncExecutor(String taskId) { + if (!multiTaskExecutor.containsKey(taskId)) { + multiTaskExecutor.put(taskId, new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), r -> { - Thread thread = new Thread(r, taskIdentifier); + Thread thread = new Thread(r, "nacos.client.config.listener.task-" + taskId); thread.setDaemon(true); return thread; })); } - - // Return the created or existing executor - return multiTaskExecutor.get(taskIdentifier); - } - - /** - * Generate a task identifier based on the task prefix and task ID. - * - * @param taskPrefix The prefix of the task identifier - * @param taskId The ID of the task - * @return The generated task identifier - */ - private String generateTaskIdentifier(String taskPrefix, String taskId) { - return taskPrefix + "-" + taskId; + return multiTaskExecutor.get(taskId); } private void refreshContentAndCheck(RpcClient rpcClient, String groupKey, boolean notify) { @@ -1524,7 +998,7 @@ private void checkRemoveListenCache(Map> removeListenCac String taskId = entry.getKey(); RpcClient rpcClient = ensureRpcClient(taskId); - ExecutorService executorService = ensureSyncExecutor(configListenerTaskPrefix, taskId); + ExecutorService executorService = ensureSyncExecutor(taskId); Future future = executorService.submit(() -> { List removeListenCaches = entry.getValue(); ConfigBatchListenRequest configChangeListenRequest = buildConfigRequest(removeListenCaches); @@ -1574,7 +1048,7 @@ private boolean checkListenCache(Map> listenCachesMap) t String taskId = entry.getKey(); RpcClient rpcClient = ensureRpcClient(taskId); - ExecutorService executorService = ensureSyncExecutor(configListenerTaskPrefix, taskId); + ExecutorService executorService = ensureSyncExecutor(taskId); Future future = executorService.submit(() -> { List listenCaches = entry.getValue(); //reset notify change flag. @@ -1655,7 +1129,7 @@ private boolean checkListenCache(Map> listenCachesMap) t return hasChangedKeys.get(); } - private RpcClient ensureRpcClient(String taskId) throws NacosException { + RpcClient ensureRpcClient(String taskId) throws NacosException { synchronized (ClientWorker.this) { Map labels = getLabels(); Map newLabels = new HashMap<>(labels); @@ -1669,7 +1143,7 @@ private RpcClient ensureRpcClient(String taskId) throws NacosException { rpcClient.setTenant(getTenant()); rpcClient.start(); } - + return rpcClient; } @@ -1691,33 +1165,12 @@ private ConfigBatchListenRequest buildConfigRequest(List caches) { return configChangeListenRequest; } - /** - * Builds a request for fuzzy listen configuration. - * - * @param contexts The list of fuzzy listen contexts. - * @return A {@code ConfigBatchFuzzyListenRequest} object representing the request. - */ - private ConfigBatchFuzzyListenRequest buildFuzzyListenConfigRequest(List contexts) { - ConfigBatchFuzzyListenRequest request = new ConfigBatchFuzzyListenRequest(); - for (FuzzyListenContext context : contexts) { - request.addContext(getTenant(), context.getGroup(), context.getDataIdPattern(), context.getDataIds(), - !context.isDiscard(), context.isInitializing()); - } - return request; - } - @Override public void removeCache(String dataId, String group) { // Notify to rpc un listen ,and remove cache if success. notifyListenConfig(); } - @Override - public void removeFuzzyListenContext(String dataIdPattern, String group) throws NacosException { - // Notify to rpc un fuzzy listen, and remove cache if success. - notifyFuzzyListenConfig(); - } - /** * send cancel listen config change request . * @@ -1789,7 +1242,7 @@ ConfigResponse queryConfigInner(RpcClient rpcClient, String dataId, String group } } - private Response requestProxy(RpcClient rpcClientInner, Request request) throws NacosException { + Response requestProxy(RpcClient rpcClientInner, Request request) throws NacosException { return requestProxy(rpcClientInner, request, requestTimeout); } @@ -1867,8 +1320,8 @@ public boolean publishConfig(String dataId, String group, String tenant, String this.getName(), dataId, group, tenant, response.getErrorCode(), response.getMessage()); return false; } else { - LOGGER.info("[{}] [publish-single] ok, dataId={}, group={}, tenant={}", getName(), - dataId, group, tenant); + LOGGER.info("[{}] [publish-single] ok, dataId={}, group={}, tenant={}", getName(), dataId, group, + tenant); return true; } } catch (Exception e) { diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigFuzzyWatchContext.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigFuzzyWatchContext.java new file mode 100644 index 00000000000..e9a14ee1cf4 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigFuzzyWatchContext.java @@ -0,0 +1,447 @@ +/* + * Copyright 1999-2023 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.client.config.impl; + +import com.alibaba.nacos.api.config.listener.ConfigFuzzyWatchChangeEvent; +import com.alibaba.nacos.api.config.listener.FuzzyWatchEventWatcher; +import com.alibaba.nacos.client.config.common.GroupKey; +import com.alibaba.nacos.client.utils.LogUtils; +import com.alibaba.nacos.common.utils.ConcurrentHashSet; +import com.alibaba.nacos.common.utils.FuzzyGroupKeyPattern; +import com.alibaba.nacos.common.utils.StringUtils; +import org.slf4j.Logger; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.alibaba.nacos.api.common.Constants.ConfigChangedType.ADD_CONFIG; +import static com.alibaba.nacos.api.common.Constants.ConfigChangedType.DELETE_CONFIG; +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_DIFF_SYNC_NOTIFY; +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_INIT_NOTIFY; + +/** + * fuzzy wather context for a single group key pattern. + * + *

This class manages the context information for fuzzy listening, including environment name, task ID, data ID + * pattern, group, tenant, listener set, and other related information. + *

+ * + * @author stone-98 + * @date 2024/3/4 + */ +public class ConfigFuzzyWatchContext { + + /** + * Logger for FuzzyListenContext. + */ + private static final Logger LOGGER = LogUtils.logger(ConfigFuzzyWatchContext.class); + + /** + * Environment name. + */ + private String envName; + + /** + * Task ID. + */ + private int taskId; + + private String groupKeyPattern; + + /** + * Set of data IDs associated with the context. + */ + private Set receivedGroupKeys = new ConcurrentHashSet<>(); + + /** + * Flag indicating whether the context is consistent with the server. + */ + private final AtomicBoolean isConsistentWithServer = new AtomicBoolean(); + + /** + * Condition object for waiting initialization completion. + */ + final AtomicBoolean initializationCompleted = new AtomicBoolean(false); + + /** + * Flag indicating whether the context is discarded. + */ + private volatile boolean isDiscard = false; + + /** + * Set of listeners associated with the context. + */ + private Set configFuzzyWatcherWrappers = new HashSet<>(); + + /** + * Constructor with environment name, data ID pattern, and group. + * + * @param envName Environment name + * @param groupKeyPattern groupKeyPattern + */ + public ConfigFuzzyWatchContext(String envName, String groupKeyPattern) { + this.envName = envName; + this.groupKeyPattern = groupKeyPattern; + } + + /** + * Calculate the listeners to notify based on the given UUID. + * + * @param uuid UUID to filter listeners + * @return Set of listeners to notify + */ + public Set calculateListenersToNotify(String uuid) { + Set listenersToNotify = new HashSet<>(); + if (StringUtils.isEmpty(uuid)) { + listenersToNotify = configFuzzyWatcherWrappers; + } else { + for (ConfigFuzzyWatcherWrapper listener : configFuzzyWatcherWrappers) { + if (uuid.equals(listener.getUuid())) { + listenersToNotify.add(listener); + } + } + } + return listenersToNotify; + } + + /** + * Notify the listener with the specified data ID, type, and UUID. + * + * @param groupKey groupKey + * @param uuid UUID to filter listeners + */ + public void notifyWatcher(final String groupKey, final String changedType, final String syncType, + final String uuid) { + Set listenersToNotify = calculateListenersToNotify(uuid); + doNotifyWatchers(groupKey, changedType, syncType, listenersToNotify); + } + + /** + * Perform the notification for the specified data ID, type, and listeners. + * + * @param groupKey groupKey + * @param listenersToNotify Set of listeners to notify + */ + private void doNotifyWatchers(final String groupKey, final String changedType, final String syncType, + Set listenersToNotify) { + for (ConfigFuzzyWatcherWrapper watcher : listenersToNotify) { + doNotifyWatcher(groupKey, changedType, syncType, watcher); + } + } + + private void doNotifyWatcher(final String groupKey, final String changedType, final String syncType, + ConfigFuzzyWatcherWrapper configFuzzyWatcher) { + + if (ADD_CONFIG.equals(changedType) && configFuzzyWatcher.getSyncGroupKeys().contains(groupKey)) { + return; + } + + if (DELETE_CONFIG.equals(changedType) && !configFuzzyWatcher.getSyncGroupKeys().contains(groupKey)) { + return; + } + + String[] parseKey = GroupKey.parseKey(groupKey); + String dataId = parseKey[0]; + String group = parseKey[1]; + + String tenant = parseKey[2]; + + final String resetSyncType = initializationCompleted.get() ? syncType : FUZZY_WATCH_INIT_NOTIFY; + AbstractFuzzyNotifyTask job = new AbstractFuzzyNotifyTask() { + @Override + public void run() { + long start = System.currentTimeMillis(); + ConfigFuzzyWatchChangeEvent event = ConfigFuzzyWatchChangeEvent.build(tenant, group, dataId, + changedType, resetSyncType); + if (configFuzzyWatcher != null) { + configFuzzyWatcher.fuzzyWatchEventWatcher.onEvent(event); + } + LOGGER.info( + "[{}] [notify-fuzzy-watcher-ok] dataId={}, group={}, tenant={}, watcher={}, job run cost={} millis.", + envName, dataId, group, tenant, configFuzzyWatcher, (System.currentTimeMillis() - start)); + if (changedType.equals(DELETE_CONFIG)) { + configFuzzyWatcher.getSyncGroupKeys().remove(GroupKey.getKey(dataId, group, tenant)); + } else if (changedType.equals(ADD_CONFIG)) { + configFuzzyWatcher.getSyncGroupKeys().add(GroupKey.getKey(dataId, group, tenant)); + + } + } + }; + + try { + if (null != configFuzzyWatcher.fuzzyWatchEventWatcher.getExecutor()) { + LOGGER.info( + "[{}] [notify-fuzzy-watcher] task submitted to user executor, dataId={}, group={}, tenant={}, listener={}.", + envName, dataId, group, tenant, configFuzzyWatcher); + job.async = true; + configFuzzyWatcher.fuzzyWatchEventWatcher.getExecutor().execute(job); + } else { + LOGGER.info( + "[{}] [notify-fuzzy-watcher] task execute in nacos thread, dataId={}, group={}, tenant={}, listener={}.", + envName, dataId, group, tenant, configFuzzyWatcher); + job.run(); + } + } catch (Throwable t) { + LOGGER.error("[{}] [notify-fuzzy-watcher-error] dataId={}, group={}, tenant={}, listener={}, throwable={}.", + envName, dataId, group, tenant, configFuzzyWatcher, t.getCause()); + } + } + + /** + * Mark initialization as complete and notify waiting threads. + */ + public void markInitializationComplete() { + initializationCompleted.set(true); + synchronized (this) { + this.notifyAll(); + } + } + + /** + * Remove a watcher from the context. + * + * @param watcher watcher to be removed + */ + public void removeWatcher(FuzzyWatchEventWatcher watcher) { + + Iterator iterator = configFuzzyWatcherWrappers.iterator(); + while (iterator.hasNext()) { + ConfigFuzzyWatcherWrapper next = iterator.next(); + if (next.fuzzyWatchEventWatcher.equals(watcher)) { + iterator.remove(); + LOGGER.info("[{}] [remove-fuzzy-watcher-ok] groupKeyPattern={}, watcher={},uuid={} ", getEnvName(), + this.groupKeyPattern, watcher, next.getUuid()); + } + } + + } + + /** + * Add a watcher to the context. + * + * @param configFuzzyWatcherWrapper watcher to be added + */ + public boolean addWatcher(ConfigFuzzyWatcherWrapper configFuzzyWatcherWrapper) { + boolean added = configFuzzyWatcherWrappers.add(configFuzzyWatcherWrapper); + if (added) { + LOGGER.info("[{}] [add-fuzzy-watcher-ok] groupKeyPattern={}, watcher={},uuid={} ", getEnvName(), + this.groupKeyPattern, configFuzzyWatcherWrapper.fuzzyWatchEventWatcher, + configFuzzyWatcherWrapper.getUuid()); + } + return added; + } + + /** + * Get the environment name. + * + * @return Environment name + */ + public String getEnvName() { + return envName; + } + + /** + * Set the environment name. + * + * @param envName Environment name to be set + */ + public void setEnvName(String envName) { + this.envName = envName; + } + + /** + * Get the task ID. + * + * @return Task ID + */ + public int getTaskId() { + return taskId; + } + + /** + * Set the task ID. + * + * @param taskId Task ID to be set + */ + public void setTaskId(int taskId) { + this.taskId = taskId; + } + + public String getGroupKeyPattern() { + return groupKeyPattern; + } + + /** + * Get the flag indicating whether the context is consistent with the server. + * + * @return AtomicBoolean indicating whether the context is consistent with the server + */ + public boolean isConsistentWithServer() { + return isConsistentWithServer.get(); + } + + public void setConsistentWithServer(boolean isConsistentWithServer) { + this.isConsistentWithServer.set(isConsistentWithServer); + } + + /** + * Check if the context is discarded. + * + * @return True if the context is discarded, otherwise false + */ + public boolean isDiscard() { + return isDiscard; + } + + /** + * Set the flag indicating whether the context is discarded. + * + * @param discard True to mark the context as discarded, otherwise false + */ + public void setDiscard(boolean discard) { + isDiscard = discard; + } + + /** + * Check if the context is initializing. + * + * @return True if the context is initializing, otherwise false + */ + public boolean isInitializing() { + return !initializationCompleted.get(); + } + + /** + * Get the set of data IDs associated with the context. zw + * + * @return Set of data IDs + */ + public Set getReceivedGroupKeys() { + return Collections.unmodifiableSet(receivedGroupKeys); + } + + public boolean addReceivedGroupKey(String groupKey) { + return receivedGroupKeys.add(groupKey); + } + + public boolean removeReceivedGroupKey(String groupKey) { + return receivedGroupKeys.remove(groupKey); + } + + /** + * Get the set of listeners associated with the context. + * + * @return Set of listeners + */ + public Set getConfigFuzzyWatcherWrappers() { + return configFuzzyWatcherWrappers; + } + + /** + * Abstract task for fuzzy notification. + */ + abstract static class AbstractFuzzyNotifyTask implements Runnable { + + /** + * Flag indicating whether the task is asynchronous. + */ + boolean async = false; + + /** + * Check if the task is asynchronous. + * + * @return True if the task is asynchronous, otherwise false + */ + public boolean isAsync() { + return async; + } + } + + void syncFuzzyWatchers() { + for (ConfigFuzzyWatcherWrapper configFuzzyWatcher : configFuzzyWatcherWrappers) { + Set receivedGroupKeysContext = receivedGroupKeys; + Set syncGroupKeys = configFuzzyWatcher.getSyncGroupKeys(); + List groupKeyStates = FuzzyGroupKeyPattern.diffGroupKeys( + receivedGroupKeysContext, syncGroupKeys); + for (FuzzyGroupKeyPattern.GroupKeyState groupKeyState : groupKeyStates) { + String changedType = groupKeyState.isExist() ? ADD_CONFIG : DELETE_CONFIG; + doNotifyWatcher(groupKeyState.getGroupKey(), changedType, FUZZY_WATCH_DIFF_SYNC_NOTIFY, + configFuzzyWatcher); + } + + } + } + + /** + * creat a new future of this context. + * @return + */ + public Future> createNewFuture() { + Future> completableFuture = new Future>() { + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + throw new UnsupportedOperationException("not support to cancel fuzzy watch"); + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return ConfigFuzzyWatchContext.this.initializationCompleted.get(); + } + + @Override + public Set get() throws InterruptedException, ExecutionException { + + if (!ConfigFuzzyWatchContext.this.initializationCompleted.get()) { + synchronized (ConfigFuzzyWatchContext.this) { + ConfigFuzzyWatchContext.this.wait(); + } + } + return new HashSet<>(ConfigFuzzyWatchContext.this.getReceivedGroupKeys()); + } + + public Set get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { + if (!ConfigFuzzyWatchContext.this.initializationCompleted.get()) { + synchronized (ConfigFuzzyWatchContext.this) { + ConfigFuzzyWatchContext.this.wait(unit.toMillis(timeout)); + } + } + + if (!ConfigFuzzyWatchContext.this.initializationCompleted.get()) { + throw new TimeoutException( + "fuzzy watch result future timeout for " + unit.toMillis(timeout) + " millis"); + } + return new HashSet<>(ConfigFuzzyWatchContext.this.getReceivedGroupKeys()); + } + }; + + return completableFuture; + } +} + diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigFuzzyWatchGroupKeyHolder.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigFuzzyWatchGroupKeyHolder.java new file mode 100644 index 00000000000..f64a8ccbff0 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigFuzzyWatchGroupKeyHolder.java @@ -0,0 +1,485 @@ +/* + * 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.client.config.impl; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.config.listener.FuzzyWatchEventWatcher; +import com.alibaba.nacos.api.config.remote.request.ConfigFuzzyWatchChangeNotifyRequest; +import com.alibaba.nacos.api.config.remote.request.ConfigFuzzyWatchRequest; +import com.alibaba.nacos.api.config.remote.request.ConfigFuzzyWatchSyncRequest; +import com.alibaba.nacos.api.config.remote.response.ConfigFuzzyWatchChangeNotifyResponse; +import com.alibaba.nacos.api.config.remote.response.ConfigFuzzyWatchResponse; +import com.alibaba.nacos.api.config.remote.response.ConfigFuzzyWatchSyncResponse; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.client.config.common.GroupKey; +import com.alibaba.nacos.client.utils.LogUtils; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.Subscriber; +import com.alibaba.nacos.common.remote.client.RpcClient; +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.common.utils.FuzzyGroupKeyPattern; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import static com.alibaba.nacos.api.common.Constants.ConfigChangedType.ADD_CONFIG; +import static com.alibaba.nacos.api.common.Constants.ConfigChangedType.CONFIG_CHANGED; +import static com.alibaba.nacos.api.common.Constants.ConfigChangedType.DELETE_CONFIG; +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_INIT_NOTIFY; +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_RESOURCE_CHANGED; +import static com.alibaba.nacos.api.common.Constants.WATCH_TYPE_CANCEL_WATCH; +import static com.alibaba.nacos.api.common.Constants.WATCH_TYPE_WATCH; + +/** + * config fuzzy watch context holder. + * @author shiyiyue + */ +public class ConfigFuzzyWatchGroupKeyHolder { + + private static final Logger LOGGER = LogUtils.logger(ClientWorker.class); + + private final ClientWorker.ConfigRpcTransportClient agent; + + private final String clientUuid; + + /** + * fuzzyListenExecuteBell. + */ + private final BlockingQueue fuzzyListenExecuteBell = new ArrayBlockingQueue<>(1); + + private final Object bellItem = new Object(); + + private final AtomicLong fuzzyListenLastAllSyncTime = new AtomicLong(System.currentTimeMillis()); + + private static final long FUZZY_LISTEN_ALL_SYNC_INTERNAL = 3 * 60 * 1000; + + private String taskId = "0"; + + /** + * fuzzyListenGroupKey -> fuzzyListenContext. + */ + private final AtomicReference> fuzzyListenContextMap = new AtomicReference<>( + new HashMap<>()); + + public ConfigFuzzyWatchGroupKeyHolder(ClientWorker.ConfigRpcTransportClient agent, String clientUuid) { + this.clientUuid = clientUuid; + this.agent = agent; + NotifyCenter.registerSubscriber(new Subscriber() { + @Override + public void onEvent(Event event) { + ConfigFuzzyWatchNotifyEvent configFuzzyWatchNotifyEvent = (ConfigFuzzyWatchNotifyEvent) event; + + //instance check + if (!configFuzzyWatchNotifyEvent.getClientUuid().equals(clientUuid)) { + return; + } + + ConfigFuzzyWatchContext context = fuzzyListenContextMap.get() + .get(configFuzzyWatchNotifyEvent.getGroupKeyPattern()); + if (context == null) { + return; + } + + context.notifyWatcher(configFuzzyWatchNotifyEvent.getGroupKey(), + configFuzzyWatchNotifyEvent.getChangedType(), configFuzzyWatchNotifyEvent.getSyncType(), + configFuzzyWatchNotifyEvent.getWatcherUuid()); + } + + @Override + public Class subscribeType() { + return ConfigFuzzyWatchNotifyEvent.class; + } + }); + } + + /** + * start. + */ + public void start() { + agent.executor.schedule(() -> { + while (!agent.executor.isShutdown() && !agent.executor.isTerminated()) { + try { + fuzzyListenExecuteBell.poll(5L, TimeUnit.SECONDS); + if (agent.executor.isShutdown() || agent.executor.isTerminated()) { + continue; + } + executeConfigFuzzyListen(); + } catch (Throwable e) { + LOGGER.error("[rpc-fuzzy-listen-execute] rpc fuzzy listen exception", e); + try { + Thread.sleep(50L); + } catch (InterruptedException interruptedException) { + //ignore + } + notifyFuzzyWatchSync(); + } + } + }, 0L, TimeUnit.MILLISECONDS); + } + + /** + * Removes the fuzzy listen context for the specified data ID pattern and group. + * + * @param groupKeyPattern The pattern of the data ID. + */ + public void removeFuzzyListenContext(String groupKeyPattern) { + synchronized (fuzzyListenContextMap) { + Map copy = new HashMap<>(fuzzyListenContextMap.get()); + copy.remove(groupKeyPattern); + fuzzyListenContextMap.set(copy); + } + LOGGER.info("[{}] [fuzzy-watch-unsubscribe] {}", agent.getName(), groupKeyPattern); + } + + /** + * register fuzzy watcher. + * + * @param dataIdPattern dataIdPattern. + * @param groupPattern groupPattern. + * @param fuzzyWatchEventWatcher fuzzyWatchEventWatcher. + * @return + */ + public ConfigFuzzyWatchContext registerFuzzyWatcher(String dataIdPattern, String groupPattern, + FuzzyWatchEventWatcher fuzzyWatchEventWatcher) { + ConfigFuzzyWatchContext configFuzzyWatchContext = initFuzzyWatchContextIfAbsent(dataIdPattern, groupPattern); + ConfigFuzzyWatcherWrapper configFuzzyWatcherWrapper = new ConfigFuzzyWatcherWrapper(fuzzyWatchEventWatcher); + if (configFuzzyWatchContext.addWatcher(configFuzzyWatcherWrapper)) { + if (configFuzzyWatchContext.getReceivedGroupKeys() != null) { + for (String groupKey : configFuzzyWatchContext.getReceivedGroupKeys()) { + ConfigFuzzyWatchNotifyEvent configFuzzyWatchNotifyEvent = ConfigFuzzyWatchNotifyEvent.buildNotifyPatternAllListenersEvent( + groupKey, configFuzzyWatchContext.getGroupKeyPattern(), ADD_CONFIG, FUZZY_WATCH_INIT_NOTIFY, + configFuzzyWatcherWrapper.getUuid()); + NotifyCenter.publishEvent(configFuzzyWatchNotifyEvent); + } + } + } + return configFuzzyWatchContext; + } + + /** + * Retrieves the FuzzyListenContext for the given data ID pattern and group. + * + * @param dataIdPattern The data ID pattern. + * @param groupPattern The group name pattern. + * @return The corresponding FuzzyListenContext, or null if not found. + */ + public ConfigFuzzyWatchContext getFuzzyListenContext(String dataIdPattern, String groupPattern) { + return fuzzyListenContextMap.get() + .get(FuzzyGroupKeyPattern.generatePattern(dataIdPattern, groupPattern, agent.getTenant())); + } + + /** + * Handles a fuzzy listen init notify request. + * + *

This method processes the incoming fuzzy listen init notify request from a client. It updates the fuzzy + * listen context based on the request's information, and publishes events if necessary. + * + * @param request The fuzzy listen init notify request to handle. + * @return A {@link ConfigFuzzyWatchSyncResponse} indicating the result of handling the request. + */ + ConfigFuzzyWatchSyncResponse handleFuzzyWatchNotifyDiffRequest(ConfigFuzzyWatchSyncRequest request) { + String groupKeyPattern = request.getGroupKeyPattern(); + ConfigFuzzyWatchContext context = fuzzyListenContextMap.get().get(groupKeyPattern); + if (Constants.FINISH_FUZZY_WATCH_INIT_NOTIFY.equals(request.getSyncType())) { + LOGGER.info("[{}] [fuzzy-watch] init-notify-finished, pattern ->{}, match group keys count {}", + agent.getName(), request.getGroupKeyPattern(), context.getReceivedGroupKeys().size()); + context.markInitializationComplete(); + return new ConfigFuzzyWatchSyncResponse(); + } + + LOGGER.info( + "[{}] [fuzzy-watch-diff-sync-push] pattern ->{},syncType={},,syncCount={},totalBatch={},currentBatch={}", + agent.getName(), request.getGroupKeyPattern(), request.getSyncType(), request.getContexts().size(), + request.getTotalBatch(), request.getCurrentBatch()); + + for (ConfigFuzzyWatchSyncRequest.Context requestContext : request.getContexts()) { + switch (requestContext.getChangedType()) { + case ADD_CONFIG: + if (context.addReceivedGroupKey(requestContext.getGroupKey())) { + LOGGER.info("[{}] [fuzzy-watch-diff-sync-push] local match group key added ,pattern ->{}, " + + "group key ->{},publish fuzzy watch notify event", agent.getName(), + request.getGroupKeyPattern(), requestContext.getGroupKey()); + NotifyCenter.publishEvent(ConfigFuzzyWatchNotifyEvent.buildNotifyPatternAllListenersEvent( + requestContext.getGroupKey(), request.getGroupKeyPattern(), + requestContext.getChangedType(), request.getSyncType(), this.clientUuid)); + } + break; + case DELETE_CONFIG: + if (context.removeReceivedGroupKey(requestContext.getGroupKey())) { + LOGGER.info("[{}] [fuzzy-watch-diff-sync-push] local match group key remove ,pattern ->{}, " + + "group key ->{},publish fuzzy watch notify event", agent.getName(), + request.getGroupKeyPattern(), requestContext.getGroupKey()); + NotifyCenter.publishEvent(ConfigFuzzyWatchNotifyEvent.buildNotifyPatternAllListenersEvent( + requestContext.getGroupKey(), request.getGroupKeyPattern(), + requestContext.getChangedType(), request.getSyncType(), this.clientUuid)); + } + break; + default: + LOGGER.warn("Invalid config change type: {}", requestContext.getChangedType()); + break; + } + } + return new ConfigFuzzyWatchSyncResponse(); + } + + /** + * Removes a fuzzy listen listener for the specified data ID pattern, group, and listener. + * + * @param dataIdPattern The pattern of the data ID. + * @param groupPattern The group of the configuration. + * @param watcher The listener to remove. + * @throws NacosException If an error occurs while removing the listener. + */ + public void removeFuzzyWatcher(String dataIdPattern, String groupPattern, FuzzyWatchEventWatcher watcher) { + ConfigFuzzyWatchContext configFuzzyWatchContext = getFuzzyListenContext(dataIdPattern, groupPattern); + if (configFuzzyWatchContext != null) { + synchronized (configFuzzyWatchContext) { + configFuzzyWatchContext.removeWatcher(watcher); + if (configFuzzyWatchContext.getConfigFuzzyWatcherWrappers().isEmpty()) { + configFuzzyWatchContext.setDiscard(true); + configFuzzyWatchContext.setConsistentWithServer(false); + } + } + } + } + + /** + * Handles a fuzzy listen notify change request. + * + *

This method processes the incoming fuzzy listen notify change request from a client. It updates the fuzzy + * listen context based on the request's information, and publishes events if necessary. + * + * @param request The fuzzy listen notify change request to handle. + */ + ConfigFuzzyWatchChangeNotifyResponse handlerFuzzyWatchChangeNotifyRequest( + ConfigFuzzyWatchChangeNotifyRequest request) { + + LOGGER.info("[{}] [fuzzy-watch-change-notify-push] changeType={},groupKey={}", agent.getName(), + request.getChangeType(), request.getGroupKey()); + + Map listenContextMap = fuzzyListenContextMap.get(); + String[] groupItems = GroupKey.parseKey(request.getGroupKey()); + Set matchedPatterns = FuzzyGroupKeyPattern.filterMatchedPatterns(listenContextMap.keySet(), + groupItems[0], groupItems[1], groupItems[2]); + for (String matchedPattern : matchedPatterns) { + ConfigFuzzyWatchContext context = listenContextMap.get(matchedPattern); + if (ADD_CONFIG.equals(request.getChangeType()) || CONFIG_CHANGED.equals(request.getChangeType())) { + if (context.addReceivedGroupKey(request.getGroupKey())) { + LOGGER.info("[{}] [fuzzy-watch-change-notify-push] match group key added ,pattern={},groupKey={}", + agent.getName(), request.getChangeType(), request.getGroupKey()); + + NotifyCenter.publishEvent( + ConfigFuzzyWatchNotifyEvent.buildNotifyPatternAllListenersEvent(request.getGroupKey(), + matchedPattern, ADD_CONFIG, FUZZY_WATCH_RESOURCE_CHANGED, this.clientUuid)); + } + } else if (DELETE_CONFIG.equals(request.getChangeType()) && context.removeReceivedGroupKey( + request.getGroupKey())) { + NotifyCenter.publishEvent( + ConfigFuzzyWatchNotifyEvent.buildNotifyPatternAllListenersEvent(request.getGroupKey(), + matchedPattern, Constants.ConfigChangedType.DELETE_CONFIG, FUZZY_WATCH_RESOURCE_CHANGED, + this.clientUuid)); + + } + } + return new ConfigFuzzyWatchChangeNotifyResponse(); + } + + void notifyFuzzyWatchSync() { + fuzzyListenExecuteBell.offer(bellItem); + + } + + /** + * Execute fuzzy listen configuration changes. + * + *

This method iterates through all fuzzy listen contexts and determines whether they need to be added or + * removed based on their consistency with the server and discard status. It then calls the appropriate method to + * execute the fuzzy listen operation. + * + * @throws NacosException If an error occurs during the execution of fuzzy listen configuration changes. + */ + public void executeConfigFuzzyListen() throws NacosException { + + // Obtain the current timestamp + long now = System.currentTimeMillis(); + + // Determine whether a full synchronization is needed + boolean needAllSync = now - fuzzyListenLastAllSyncTime.get() >= FUZZY_LISTEN_ALL_SYNC_INTERNAL; + + List needSyncContexts = new ArrayList<>(); + // Iterate through all fuzzy listen contexts + for (ConfigFuzzyWatchContext context : fuzzyListenContextMap.get().values()) { + // Check if the context is consistent with the server + if (context.isConsistentWithServer()) { + // Skip if a full synchronization is not needed + if (!needAllSync) { + continue; + } else { + context.syncFuzzyWatchers(); + } + } + + needSyncContexts.add(context); + } + + // Execute fuzzy listen operation for addition + doExecuteConfigFuzzyListen(needSyncContexts); + + // Update last all sync time if a full synchronization was performed + if (needAllSync) { + fuzzyListenLastAllSyncTime.set(now); + } + } + + void resetConsistenceStatus() { + Collection configFuzzyWatchContexts = fuzzyListenContextMap.get().values(); + + for (ConfigFuzzyWatchContext context : configFuzzyWatchContexts) { + context.setConsistentWithServer(false); + } + } + + /** + * Execute fuzzy listen configuration changes for a specific map of contexts. + * + *

This method submits tasks to execute fuzzy listen operations asynchronously for the provided contexts. It + * waits for all tasks to complete and logs any errors that occur. + * + * @param contextLists The map of contexts to execute fuzzy listen operations for. + * @throws NacosException If an error occurs during the execution of fuzzy listen configuration changes. + */ + private void doExecuteConfigFuzzyListen(List contextLists) throws NacosException { + // Return if the context map is null or empty + if (CollectionUtils.isEmpty(contextLists)) { + return; + } + + // List to hold futures for asynchronous tasks + List> listenFutures = new ArrayList<>(); + + RpcClient rpcClient = agent.ensureRpcClient(taskId); + + // Iterate through the context map and submit tasks for execution + for (ConfigFuzzyWatchContext entry : contextLists) { + ExecutorService executorService = agent.executor; + // Submit task for execution + Future future = executorService.submit(() -> { + ConfigFuzzyWatchRequest configFuzzyWatchRequest = buildFuzzyListenConfigRequest(entry); + try { + // Execute the fuzzy listen operation + ConfigFuzzyWatchResponse listenResponse = (ConfigFuzzyWatchResponse) agent.requestProxy(rpcClient, + configFuzzyWatchRequest); + if (listenResponse != null && listenResponse.isSuccess()) { + + if (entry.isDiscard()) { + removeFuzzyListenContext(entry.getGroupKeyPattern()); + } else { + entry.setConsistentWithServer(true); + } + + } + } catch (NacosException e) { + // Log error and retry after a short delay + LOGGER.error("Execute batch fuzzy listen config change error.", e); + try { + Thread.sleep(50L); + } catch (InterruptedException interruptedException) { + // Ignore interruption + } + // Retry notification + notifyFuzzyWatchSync(); + } + }); + listenFutures.add(future); + } + + // Wait for all tasks to complete + for (Future future : listenFutures) { + try { + future.get(); + } catch (Throwable throwable) { + // Log async listen error + LOGGER.error("Async fuzzy listen config change error.", throwable); + } + } + } + + /** + * Builds a request for fuzzy listen configuration. + * + * @param context The list of fuzzy listen contexts. + * @return A {@code ConfigBatchFuzzyListenRequest} object representing the request. + */ + private ConfigFuzzyWatchRequest buildFuzzyListenConfigRequest(ConfigFuzzyWatchContext context) { + ConfigFuzzyWatchRequest request = new ConfigFuzzyWatchRequest(); + request.setGroupKeyPattern(context.getGroupKeyPattern()); + request.setInitializing(context.isInitializing()); + request.setWatchType((context.isDiscard() && CollectionUtils.isEmpty(context.getConfigFuzzyWatcherWrappers())) + ? WATCH_TYPE_CANCEL_WATCH : WATCH_TYPE_WATCH); + request.setReceivedGroupKeys(context.getReceivedGroupKeys()); + return request; + } + + /** + * Adds a fuzzy listen context if it doesn't already exist for the specified data ID pattern and group. If the + * context already exists, returns the existing context. + * + * @param dataIdPattern The pattern of the data ID. + * @param groupPattern The group of the configuration. + * @return The fuzzy listen context for the specified data ID pattern and group. + */ + private ConfigFuzzyWatchContext initFuzzyWatchContextIfAbsent(String dataIdPattern, String groupPattern) { + ConfigFuzzyWatchContext context = getFuzzyListenContext(dataIdPattern, groupPattern); + if (context != null) { + return context; + } + synchronized (fuzzyListenContextMap) { + ConfigFuzzyWatchContext contextFromMap = getFuzzyListenContext(dataIdPattern, groupPattern); + if (contextFromMap != null) { + context = contextFromMap; + } else { + String groupKeyPattern = FuzzyGroupKeyPattern.generatePattern(dataIdPattern, groupPattern, + agent.getTenant()); + + context = new ConfigFuzzyWatchContext(agent.getName(), groupKeyPattern); + context.setConsistentWithServer(false); + Map copy = new HashMap<>(fuzzyListenContextMap.get()); + copy.put(groupKeyPattern, context); + LOGGER.info("[{}][fuzzy-watch] init fuzzy watch context , groupKeyPattern={} ,notify fuzzy watch sync ", + agent.getName(), groupKeyPattern); + fuzzyListenContextMap.set(copy); + notifyFuzzyWatchSync(); + } + } + + return context; + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigFuzzyWatchNotifyEvent.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigFuzzyWatchNotifyEvent.java new file mode 100644 index 00000000000..3489e77ed40 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigFuzzyWatchNotifyEvent.java @@ -0,0 +1,133 @@ +/* + * Copyright 1999-2023 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.client.config.impl; + +import com.alibaba.nacos.common.notify.Event; + +/** + * Event class for fuzzy listen notifications. + * + *

This class represents an event used for notifying fuzzy listen changes. It extends {@link Event}, indicating + * that it may be processed asynchronously. The event contains information about the group, dataId, type, and UUID of + * the notification. + * + * @author stone-98 + * @date 2024/3/4 + */ +public class ConfigFuzzyWatchNotifyEvent extends Event { + + private String clientUuid; + + /** + * The uuid of this watcher for which that this notify event . + */ + private String watcherUuid; + + /** + * The groupKeyPattern of configuration. + */ + private String groupKeyPattern; + + private String groupKey; + + /** + * The type of notification (e.g., ADD_CONFIG, DELETE_CONFIG). + */ + private String changedType; + + private String syncType; + + /** + * Constructs a new FuzzyListenNotifyEvent. + */ + public ConfigFuzzyWatchNotifyEvent() { + } + + /** + * Constructs a new FuzzyListenNotifyEvent with the specified group, dataId, and type. + * + * @param groupKey The groupKey of the configuration. + * @param changedType The type of notification. + */ + private ConfigFuzzyWatchNotifyEvent(String groupKey, String changedType, String syncType, String groupKeyPattern, + String clientUuid, String watcherUuid) { + this.groupKey = groupKey; + this.syncType = syncType; + this.changedType = changedType; + this.groupKeyPattern = groupKeyPattern; + this.clientUuid = clientUuid; + this.watcherUuid = watcherUuid; + } + + /** + * Builds a new FuzzyListenNotifyEvent with the specified group, dataId, and type. + * + * @param groupKey The groupKey of the configuration. + * @return A new FuzzyListenNotifyEvent instance. + */ + public static ConfigFuzzyWatchNotifyEvent buildNotifyPatternAllListenersEvent(String groupKey, + String groupKeyPattern, String changedType, String syncType, String clientUuid) { + return buildNotifyPatternAllListenersEvent(groupKey, groupKeyPattern, changedType, syncType, clientUuid, null); + } + + /** + * Builds a new FuzzyListenNotifyEvent with the specified group, dataId, and type. + * + * @param groupKey The groupKey of the configuration. + * @return A new FuzzyListenNotifyEvent instance. + */ + public static ConfigFuzzyWatchNotifyEvent buildNotifyPatternAllListenersEvent(String groupKey, + String groupKeyPattern, String changedType, String syncType, String clientUuid, String watcherUuid) { + ConfigFuzzyWatchNotifyEvent configFuzzyWatchNotifyEvent = new ConfigFuzzyWatchNotifyEvent(groupKey, changedType, + syncType, groupKeyPattern, clientUuid, watcherUuid); + return configFuzzyWatchNotifyEvent; + } + + /** + * Gets the UUID (Unique Identifier) of the listener. + * + * @return The UUID of the listener. + */ + public String getWatcherUuid() { + return watcherUuid; + } + + public String getClientUuid() { + return clientUuid; + } + + public String getGroupKeyPattern() { + return groupKeyPattern; + } + + public String getGroupKey() { + return groupKey; + } + + public String getSyncType() { + return syncType; + } + + /** + * Gets the type of notification. + * + * @return The type of notification. + */ + public String getChangedType() { + return changedType; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigFuzzyWatcherWrapper.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigFuzzyWatcherWrapper.java new file mode 100644 index 00000000000..2d7ade95b71 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigFuzzyWatcherWrapper.java @@ -0,0 +1,75 @@ +/* + * 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.client.config.impl; + +import com.alibaba.nacos.api.config.listener.FuzzyWatchEventWatcher; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * ConfigFuzzyWatcherWrapper. + * @author shiyiyue + */ +public class ConfigFuzzyWatcherWrapper { + + FuzzyWatchEventWatcher fuzzyWatchEventWatcher; + + public ConfigFuzzyWatcherWrapper(FuzzyWatchEventWatcher fuzzyWatchEventWatcher) { + this.fuzzyWatchEventWatcher = fuzzyWatchEventWatcher; + } + + /** + * Unique identifier for the listener. + */ + String uuid = UUID.randomUUID().toString(); + + private Set syncGroupKeys = new HashSet<>(); + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ConfigFuzzyWatcherWrapper that = (ConfigFuzzyWatcherWrapper) o; + return Objects.equals(fuzzyWatchEventWatcher, that.fuzzyWatchEventWatcher) && Objects.equals(uuid, that.uuid); + } + + @Override + public int hashCode() { + return Objects.hash(fuzzyWatchEventWatcher, uuid); + } + + Set getSyncGroupKeys() { + return syncGroupKeys; + } + + /** + * Get the UUID (Unique Identifier) of the listener. + * + * @return The UUID of the listener + */ + String getUuid() { + return uuid; + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigTransportClient.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigTransportClient.java index 28d49e59054..1bd5a8e10c5 100644 --- a/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigTransportClient.java +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigTransportClient.java @@ -19,14 +19,14 @@ import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.exception.NacosException; -import com.alibaba.nacos.client.config.filter.impl.ConfigResponse; import com.alibaba.nacos.client.env.NacosClientProperties; +import com.alibaba.nacos.plugin.auth.api.RequestResource; +import com.alibaba.nacos.client.config.filter.impl.ConfigResponse; import com.alibaba.nacos.client.security.SecurityProxy; -import com.alibaba.nacos.client.utils.ParamUtil; import com.alibaba.nacos.common.utils.ConvertUtils; import com.alibaba.nacos.common.utils.MD5Utils; import com.alibaba.nacos.common.utils.StringUtils; -import com.alibaba.nacos.plugin.auth.api.RequestResource; +import com.alibaba.nacos.client.utils.ParamUtil; import java.util.HashMap; import java.util.Map; @@ -177,11 +177,6 @@ public String getTenant() { **/ public abstract void notifyListenConfig(); - /** - * notify fuzzy listen config. - */ - public abstract void notifyFuzzyListenConfig(); - /** * listen change . * @@ -189,13 +184,6 @@ public String getTenant() { */ public abstract void executeConfigListen() throws NacosException; - /** - * Fuzzy listen change. - * - * @throws NacosException nacos exception throws, should retry. - */ - public abstract void executeConfigFuzzyListen() throws NacosException; - /** * remove cache implements. * @@ -204,15 +192,6 @@ public String getTenant() { */ public abstract void removeCache(String dataId, String group); - /** - * Remove fuzzy listen context. - * - * @param dataIdPattern dataIdPattern - * @param group group - * @throws NacosException if an error occurs while removing the fuzzy listen context. - */ - public abstract void removeFuzzyListenContext(String dataIdPattern, String group) throws NacosException; - /** * query config. * diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/FuzzyListenContext.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/FuzzyListenContext.java deleted file mode 100644 index 93ddbcfbb7a..00000000000 --- a/client/src/main/java/com/alibaba/nacos/client/config/impl/FuzzyListenContext.java +++ /dev/null @@ -1,447 +0,0 @@ -/* - * Copyright 1999-2023 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.client.config.impl; - -import com.alibaba.nacos.api.config.listener.AbstractFuzzyListenListener; -import com.alibaba.nacos.api.config.listener.FuzzyListenConfigChangeEvent; -import com.alibaba.nacos.client.utils.LogUtils; -import com.alibaba.nacos.common.utils.ConcurrentHashSet; -import com.alibaba.nacos.common.utils.StringUtils; -import org.slf4j.Logger; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * Context for fuzzy listening. - * - *

This class manages the context information for fuzzy listening, including environment name, task ID, data ID - * pattern, group, tenant, listener set, and other related information. - *

- * - * @author stone-98 - * @date 2024/3/4 - */ -public class FuzzyListenContext { - - /** - * Logger for FuzzyListenContext. - */ - private static final Logger LOGGER = LogUtils.logger(FuzzyListenContext.class); - - /** - * Environment name. - */ - private String envName; - - /** - * Task ID. - */ - private int taskId; - - /** - * Data ID pattern. - */ - private String dataIdPattern; - - /** - * Group name. - */ - private String group; - - /** - * Tenant name. - */ - private String tenant; - - /** - * Flag indicating whether the context is consistent with the server. - */ - private final AtomicBoolean isConsistentWithServer = new AtomicBoolean(); - - /** - * Lock object for synchronization of initialization. - */ - private final Lock initializationLock = new ReentrantLock(); - - /** - * Condition object for waiting initialization completion. - */ - private final Condition initializationCompleted = initializationLock.newCondition(); - - /** - * Flag indicating whether the context is initializing. - */ - private boolean isInitializing = false; - - /** - * Flag indicating whether the context is discarded. - */ - private volatile boolean isDiscard = false; - - /** - * Set of data IDs associated with the context. - */ - private Set dataIds = new ConcurrentHashSet<>(); - - /** - * Set of listeners associated with the context. - */ - private Set listeners = new HashSet<>(); - - /** - * Constructor with environment name, data ID pattern, and group. - * - * @param envName Environment name - * @param dataIdPattern Data ID pattern - * @param group Group name - */ - public FuzzyListenContext(String envName, String dataIdPattern, String group) { - this.envName = envName; - this.dataIdPattern = dataIdPattern; - this.group = group; - } - - /** - * Calculate the listeners to notify based on the given UUID. - * - * @param uuid UUID to filter listeners - * @return Set of listeners to notify - */ - public Set calculateListenersToNotify(String uuid) { - Set listenersToNotify = new HashSet<>(); - if (StringUtils.isEmpty(uuid)) { - listenersToNotify = listeners; - } else { - for (AbstractFuzzyListenListener listener : listeners) { - if (uuid.equals(listener.getUuid())) { - listenersToNotify.add(listener); - } - } - } - return listenersToNotify; - } - - /** - * Notify the listener with the specified data ID, type, and UUID. - * - * @param dataId Data ID - * @param type Type of the event - * @param uuid UUID to filter listeners - */ - public void notifyListener(final String dataId, final String type, final String uuid) { - Set listenersToNotify = calculateListenersToNotify(uuid); - doNotifyListener(dataId, type, listenersToNotify); - } - - /** - * Perform the notification for the specified data ID, type, and listeners. - * - * @param dataId Data ID - * @param type Type of the event - * @param listenersToNotify Set of listeners to notify - */ - private void doNotifyListener(final String dataId, final String type, - Set listenersToNotify) { - for (AbstractFuzzyListenListener listener : listenersToNotify) { - AbstractFuzzyNotifyTask job = new AbstractFuzzyNotifyTask() { - @Override - public void run() { - long start = System.currentTimeMillis(); - FuzzyListenConfigChangeEvent event = FuzzyListenConfigChangeEvent.build(group, dataId, type); - if (listener != null) { - listener.onEvent(event); - } - LOGGER.info("[{}] [notify-ok] dataId={}, group={}, tenant={}, listener={}, job run cost={} millis.", - envName, dataId, group, tenant, listener, (System.currentTimeMillis() - start)); - } - }; - - try { - if (null != listener.getExecutor()) { - LOGGER.info( - "[{}] [notify-listener] task submitted to user executor, dataId={}, group={}, tenant={}, listener={}.", - envName, dataId, group, tenant, listener); - job.async = true; - listener.getExecutor().execute(job); - } else { - LOGGER.info( - "[{}] [notify-listener] task execute in nacos thread, dataId={}, group={}, tenant={}, listener={}.", - envName, dataId, group, tenant, listener); - job.run(); - } - } catch (Throwable t) { - LOGGER.error("[{}] [notify-listener-error] dataId={}, group={}, tenant={}, listener={}, throwable={}.", - envName, dataId, group, tenant, listener, t.getCause()); - } - } - } - - - /** - * Wait for initialization to be complete. - * - * @return CompletableFuture> Completes with the collection of data IDs if initialization is - * @return CompletableFuture> Completes with the collection of data IDs if initialization is - * complete, or completes exceptionally if an error occurs - */ - public CompletableFuture> waitForInitializationComplete( - CompletableFuture> future) { - initializationLock.lock(); - try { - while (isInitializing) { - initializationCompleted.await(); - } - future.complete(Collections.unmodifiableCollection(dataIds)); - } catch (InterruptedException e) { - future.completeExceptionally(e); - } finally { - initializationLock.unlock(); - } - return future; - } - - /** - * Mark initialization as complete and notify waiting threads. - */ - public void markInitializationComplete() { - initializationLock.lock(); - try { - isInitializing = false; - initializationCompleted.signalAll(); - } finally { - initializationLock.unlock(); - } - } - - /** - * Remove a listener from the context. - * - * @param listener Listener to be removed - */ - public void removeListener(AbstractFuzzyListenListener listener) { - listeners.remove(listener); - } - - /** - * Add a listener to the context. - * - * @param listener Listener to be added - */ - public void addListener(AbstractFuzzyListenListener listener) { - listeners.add(listener); - } - - /** - * Get the environment name. - * - * @return Environment name - */ - public String getEnvName() { - return envName; - } - - /** - * Set the environment name. - * - * @param envName Environment name to be set - */ - public void setEnvName(String envName) { - this.envName = envName; - } - - /** - * Get the task ID. - * - * @return Task ID - */ - public int getTaskId() { - return taskId; - } - - /** - * Set the task ID. - * - * @param taskId Task ID to be set - */ - public void setTaskId(int taskId) { - this.taskId = taskId; - } - - /** - * Get the data ID pattern. - * - * @return Data ID pattern - */ - public String getDataIdPattern() { - return dataIdPattern; - } - - /** - * Set the data ID pattern. - * - * @param dataIdPattern Data ID pattern to be set - */ - public void setDataIdPattern(String dataIdPattern) { - this.dataIdPattern = dataIdPattern; - } - - /** - * Get the group name. - * - * @return Group name - */ - public String getGroup() { - return group; - } - - /** - * Set the group name. - * - * @param group Group name to be set - */ - public void setGroup(String group) { - this.group = group; - } - - /** - * Get the tenant name. - * - * @return Tenant name - */ - public String getTenant() { - return tenant; - } - - /** - * Set the tenant name. - * - * @param tenant Tenant name to be set - */ - public void setTenant(String tenant) { - this.tenant = tenant; - } - - /** - * Get the flag indicating whether the context is consistent with the server. - * - * @return AtomicBoolean indicating whether the context is consistent with the server - */ - public AtomicBoolean getIsConsistentWithServer() { - return isConsistentWithServer; - } - - /** - * Check if the context is discarded. - * - * @return True if the context is discarded, otherwise false - */ - public boolean isDiscard() { - return isDiscard; - } - - /** - * Set the flag indicating whether the context is discarded. - * - * @param discard True to mark the context as discarded, otherwise false - */ - public void setDiscard(boolean discard) { - isDiscard = discard; - } - - /** - * Check if the context is initializing. - * - * @return True if the context is initializing, otherwise false - */ - public boolean isInitializing() { - return isInitializing; - } - - /** - * Set the flag indicating whether the context is initializing. - * - * @param initializing True to mark the context as initializing, otherwise false - */ - public void setInitializing(boolean initializing) { - isInitializing = initializing; - } - - /** - * Get the set of data IDs associated with the context. - * - * @return Set of data IDs - */ - public Set getDataIds() { - return Collections.unmodifiableSet(dataIds); - } - - /** - * Set the set of data IDs associated with the context. - * - * @param dataIds Set of data IDs to be set - */ - public void setDataIds(Set dataIds) { - this.dataIds = dataIds; - } - - /** - * Get the set of listeners associated with the context. - * - * @return Set of listeners - */ - public Set getListeners() { - return listeners; - } - - /** - * Set the set of listeners associated with the context. - * - * @param listeners Set of listeners to be set - */ - public void setListeners(Set listeners) { - this.listeners = listeners; - } - - /** - * Abstract task for fuzzy notification. - */ - abstract static class AbstractFuzzyNotifyTask implements Runnable { - - /** - * Flag indicating whether the task is asynchronous. - */ - boolean async = false; - - /** - * Check if the task is asynchronous. - * - * @return True if the task is asynchronous, otherwise false - */ - public boolean isAsync() { - return async; - } - } -} - diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/FuzzyListenNotifyEvent.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/FuzzyListenNotifyEvent.java deleted file mode 100644 index a710630c131..00000000000 --- a/client/src/main/java/com/alibaba/nacos/client/config/impl/FuzzyListenNotifyEvent.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 1999-2023 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.client.config.impl; - -import com.alibaba.nacos.common.notify.Event; - -/** - * Event class for fuzzy listen notifications. - * - *

This class represents an event used for notifying fuzzy listen changes. It extends {@link Event}, indicating - * that it may be processed asynchronously. The event contains information about the group, dataId, type, and UUID of - * the notification. - * - * @author stone-98 - * @date 2024/3/4 - */ -public class FuzzyListenNotifyEvent extends Event { - - /** - * The unique identifier for the listener. - */ - private String uuid; - - /** - * The groupKeyPattern of configuration. - */ - private String groupKeyPattern; - - /** - * The group of the configuration. - */ - private String group; - - /** - * The dataId of the configuration. - */ - private String dataId; - - /** - * The type of notification (e.g., ADD_CONFIG, DELETE_CONFIG). - */ - private String type; - - /** - * Constructs a new FuzzyListenNotifyEvent. - */ - public FuzzyListenNotifyEvent() { - } - - /** - * Constructs a new FuzzyListenNotifyEvent with the specified group, dataId, type, and UUID. - * - * @param group The group of the configuration. - * @param dataId The dataId of the configuration. - * @param type The type of notification. - * @param uuid The UUID (Unique Identifier) of the listener. - */ - public FuzzyListenNotifyEvent(String group, String dataId, String type, String groupKeyPattern, String uuid) { - this.group = group; - this.dataId = dataId; - this.type = type; - this.groupKeyPattern = groupKeyPattern; - this.uuid = uuid; - } - - /** - * Constructs a new FuzzyListenNotifyEvent with the specified group, dataId, and type. - * - * @param group The group of the configuration. - * @param dataId The dataId of the configuration. - * @param type The type of notification. - */ - public FuzzyListenNotifyEvent(String group, String dataId, String type, String groupKeyPattern) { - this.group = group; - this.dataId = dataId; - this.type = type; - this.groupKeyPattern = groupKeyPattern; - } - - /** - * Builds a new FuzzyListenNotifyEvent with the specified group, dataId, type, and UUID. - * - * @param group The group of the configuration. - * @param dataId The dataId of the configuration. - * @param type The type of notification. - * @param uuid The UUID (Unique Identifier) of the listener. - * @return A new FuzzyListenNotifyEvent instance. - */ - public static FuzzyListenNotifyEvent buildNotifyPatternSpecificListenerEvent(String group, String dataId, - String type, String groupKeyPattern, String uuid) { - return new FuzzyListenNotifyEvent(group, dataId, type, groupKeyPattern, uuid); - } - - /** - * Builds a new FuzzyListenNotifyEvent with the specified group, dataId, and type. - * - * @param group The group of the configuration. - * @param dataId The dataId of the configuration. - * @param type The type of notification. - * @return A new FuzzyListenNotifyEvent instance. - */ - public static FuzzyListenNotifyEvent buildNotifyPatternAllListenersEvent(String group, String dataId, - String groupKeyPattern, String type) { - return new FuzzyListenNotifyEvent(group, dataId, type, groupKeyPattern); - } - - /** - * Gets the UUID (Unique Identifier) of the listener. - * - * @return The UUID of the listener. - */ - public String getUuid() { - return uuid; - } - - /** - * Sets the UUID (Unique Identifier) of the listener. - * - * @param uuid The UUID to set. - */ - public void setUuid(String uuid) { - this.uuid = uuid; - } - - public String getGroupKeyPattern() { - return groupKeyPattern; - } - - public void setGroupKeyPattern(String groupKeyPattern) { - this.groupKeyPattern = groupKeyPattern; - } - - /** - * Gets the group of the configuration. - * - * @return The group of the configuration. - */ - public String getGroup() { - return group; - } - - /** - * Sets the group of the configuration. - * - * @param group The group to set. - */ - public void setGroup(String group) { - this.group = group; - } - - /** - * Gets the dataId of the configuration. - * - * @return The dataId of the configuration. - */ - public String getDataId() { - return dataId; - } - - /** - * Sets the dataId of the configuration. - * - * @param dataId The dataId to set. - */ - public void setDataId(String dataId) { - this.dataId = dataId; - } - - /** - * Gets the type of notification. - * - * @return The type of notification. - */ - public String getType() { - return type; - } - - /** - * Sets the type of notification. - * - * @param type The type to set. - */ - public void setType(String type) { - this.type = type; - } -} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java index bf382f02e27..1b0b24d9884 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java @@ -21,6 +21,7 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.FuzzyWatchEventWatcher; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ListView; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; @@ -30,11 +31,14 @@ import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.api.selector.AbstractSelector; import com.alibaba.nacos.client.env.NacosClientProperties; +import com.alibaba.nacos.client.naming.cache.NamingFuzzyWatchContext; +import com.alibaba.nacos.client.naming.cache.NamingFuzzyWatchServiceListHolder; import com.alibaba.nacos.client.naming.cache.ServiceInfoHolder; import com.alibaba.nacos.client.naming.core.Balancer; import com.alibaba.nacos.client.naming.event.InstancesChangeEvent; import com.alibaba.nacos.client.naming.event.InstancesChangeNotifier; import com.alibaba.nacos.client.naming.event.InstancesDiff; +import com.alibaba.nacos.client.naming.event.NamingFuzzyWatchNotifyEvent; import com.alibaba.nacos.client.naming.remote.NamingClientProxy; import com.alibaba.nacos.client.naming.remote.NamingClientProxyDelegate; import com.alibaba.nacos.client.naming.selector.NamingSelectorFactory; @@ -47,6 +51,7 @@ import com.alibaba.nacos.client.utils.PreInitUtils; import com.alibaba.nacos.client.utils.ValidatorUtils; import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.utils.FuzzyGroupKeyPattern; import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.StringUtils; @@ -55,7 +60,9 @@ import java.util.List; import java.util.Properties; import java.util.UUID; +import java.util.concurrent.Future; +import static com.alibaba.nacos.api.common.Constants.ANY_PATTERN; import static com.alibaba.nacos.client.naming.selector.NamingSelectorFactory.getUniqueClusterString; import static com.alibaba.nacos.client.utils.LogUtils.NAMING_LOGGER; @@ -83,6 +90,8 @@ public class NacosNamingService implements NamingService { private ServiceInfoHolder serviceInfoHolder; + private NamingFuzzyWatchServiceListHolder namingFuzzyWatchServiceListHolder; + private InstancesChangeNotifier changeNotifier; private NamingClientProxy clientProxy; @@ -114,8 +123,12 @@ private void init(Properties properties) throws NacosException { NotifyCenter.registerToPublisher(InstancesChangeEvent.class, 16384); NotifyCenter.registerSubscriber(changeNotifier); this.serviceInfoHolder = new ServiceInfoHolder(namespace, this.notifierEventScope, nacosClientProperties); + + NotifyCenter.registerToPublisher(NamingFuzzyWatchNotifyEvent.class, 16384); + this.namingFuzzyWatchServiceListHolder = new NamingFuzzyWatchServiceListHolder(this.notifierEventScope); + this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, nacosClientProperties, - changeNotifier); + changeNotifier, namingFuzzyWatchServiceListHolder); } @Deprecated @@ -362,7 +375,8 @@ private ServiceInfo getServiceInfoBySubscribe(String serviceName, String groupNa return serviceInfo; } - private ServiceInfo tryToSubscribe(String serviceName, String groupName, ServiceInfo cachedServiceInfo) throws NacosException { + private ServiceInfo tryToSubscribe(String serviceName, String groupName, ServiceInfo cachedServiceInfo) + throws NacosException { // not found in cache, service never subscribed. if (null == cachedServiceInfo) { return clientProxy.subscribe(serviceName, groupName, StringUtils.EMPTY); @@ -525,6 +539,66 @@ private void doUnsubscribe(String serviceName, String groupName, NamingSelector } } + @Override + public void fuzzyWatch(String fixedGroupName, FuzzyWatchEventWatcher listener) throws NacosException { + doFuzzyWatch(ANY_PATTERN, fixedGroupName, listener); + } + + @Override + public void fuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) + throws NacosException { + doFuzzyWatch(serviceNamePattern, groupNamePattern, listener); + } + + @Override + public Future> fuzzyWatchWithServiceKeys(String fixedGroupName, FuzzyWatchEventWatcher listener) + throws NacosException { + return doFuzzyWatch(ANY_PATTERN, fixedGroupName, listener); + } + + @Override + public Future> fuzzyWatchWithServiceKeys(String serviceNamePattern, String groupNamePattern, + FuzzyWatchEventWatcher listener) throws NacosException { + return doFuzzyWatch(serviceNamePattern, groupNamePattern, listener); + } + + private Future> doFuzzyWatch(String serviceNamePattern, String groupNamePattern, + FuzzyWatchEventWatcher watcher) { + if (null == watcher) { + return null; + } + + String groupKeyPattern = FuzzyGroupKeyPattern.generatePattern(serviceNamePattern, groupNamePattern, namespace); + NamingFuzzyWatchContext namingFuzzyWatchContext = namingFuzzyWatchServiceListHolder.registerFuzzyWatcher( + groupKeyPattern, watcher); + return namingFuzzyWatchContext.createNewFuture(); + } + + @Override + public void cancelFuzzyWatch(String fixedGroupName, FuzzyWatchEventWatcher listener) throws NacosException { + doCancelFuzzyWatch(ANY_PATTERN, fixedGroupName, listener); + } + + @Override + public void cancelFuzzyWatch(String serviceNamePattern, String fixedGroupName, FuzzyWatchEventWatcher listener) + throws NacosException { + doCancelFuzzyWatch(serviceNamePattern, fixedGroupName, listener); + } + + private void doCancelFuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher watcher) + throws NacosException { + if (null == watcher) { + return; + } + String groupKeyPattern = FuzzyGroupKeyPattern.generatePattern(serviceNamePattern, groupNamePattern, namespace); + + NamingFuzzyWatchContext namingFuzzyWatchContext = namingFuzzyWatchServiceListHolder.getFuzzyWatchContext( + groupKeyPattern); + if (namingFuzzyWatchContext != null) { + namingFuzzyWatchContext.removeWatcher(watcher); + } + } + @Override public ListView getServicesOfServer(int pageNo, int pageSize) throws NacosException { return getServicesOfServer(pageNo, pageSize, Constants.DEFAULT_GROUP); @@ -561,6 +635,7 @@ public String getServerStatus() { public void shutDown() throws NacosException { serviceInfoHolder.shutdown(); clientProxy.shutdown(); + namingFuzzyWatchServiceListHolder.shutdown(); NotifyCenter.deregisterSubscriber(changeNotifier); } diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/cache/FuzzyWatchEventWatcherWrapper.java b/client/src/main/java/com/alibaba/nacos/client/naming/cache/FuzzyWatchEventWatcherWrapper.java new file mode 100644 index 00000000000..c607c8e9a44 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/cache/FuzzyWatchEventWatcherWrapper.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2023 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.client.naming.cache; + +import com.alibaba.nacos.api.naming.listener.FuzzyWatchEventWatcher; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * fuzzy watcher wrapper. + * @author shiyiyue + */ +public class FuzzyWatchEventWatcherWrapper { + + FuzzyWatchEventWatcher fuzzyWatchEventWatcher; + + public FuzzyWatchEventWatcherWrapper(FuzzyWatchEventWatcher fuzzyWatchEventWatcher) { + this.fuzzyWatchEventWatcher = fuzzyWatchEventWatcher; + } + + String uuid = UUID.randomUUID().toString(); + + private Set syncServiceKeys = new HashSet<>(); + + final String getUuid() { + return uuid; + } + + Set getSyncServiceKeys() { + return syncServiceKeys; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FuzzyWatchEventWatcherWrapper that = (FuzzyWatchEventWatcherWrapper) o; + return Objects.equals(fuzzyWatchEventWatcher, that.fuzzyWatchEventWatcher); + } + + @Override + public int hashCode() { + return Objects.hash(fuzzyWatchEventWatcher); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/cache/NamingFuzzyWatchContext.java b/client/src/main/java/com/alibaba/nacos/client/naming/cache/NamingFuzzyWatchContext.java new file mode 100644 index 00000000000..e5173614bda --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/cache/NamingFuzzyWatchContext.java @@ -0,0 +1,369 @@ +/* + * Copyright 1999-2023 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.client.naming.cache; + +import com.alibaba.nacos.api.naming.listener.FuzzyWatchChangeEvent; +import com.alibaba.nacos.api.naming.listener.FuzzyWatchEventWatcher; +import com.alibaba.nacos.api.naming.pojo.ListView; +import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.client.utils.LogUtils; +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.common.utils.ConcurrentHashSet; +import com.alibaba.nacos.common.utils.FuzzyGroupKeyPattern; +import com.alibaba.nacos.common.utils.StringUtils; +import org.slf4j.Logger; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_DIFF_SYNC_NOTIFY; +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_INIT_NOTIFY; +import static com.alibaba.nacos.api.common.Constants.ServiceChangedType.ADD_SERVICE; +import static com.alibaba.nacos.api.common.Constants.ServiceChangedType.DELETE_SERVICE; + +/** + * fuzzy wather context for a single group key pattern. + * + *

This class manages the context information for fuzzy listening, including environment name, task ID, data ID + * pattern, group, tenant, listener set, and other related information. + *

+ * + * @author stone-98 + * @date 2024/3/4 + */ +public class NamingFuzzyWatchContext { + + /** + * Logger for FuzzyListenContext. + */ + private static final Logger LOGGER = LogUtils.logger(NamingFuzzyWatchContext.class); + + /** + * Environment name. + */ + private String envName; + + private String groupKeyPattern; + + /** + * Set of service keys associated with the context. + */ + private Set receivedServiceKeys = new ConcurrentHashSet<>(); + + /** + * Flag indicating whether the context is consistent with the server. + */ + private final AtomicBoolean isConsistentWithServer = new AtomicBoolean(); + + /** + * Condition object for waiting initialization completion. + */ + final AtomicBoolean initializationCompleted = new AtomicBoolean(false); + + /** + * Flag indicating whether the context is discarded. + */ + private volatile boolean isDiscard = false; + + /** + * Set of listeners associated with the context. + */ + private final Set fuzzyWatchEventWatcherWrappers = new HashSet<>(); + + /** + * Constructor with environment name, data ID pattern, and group. + * + * @param envName Environment name + * @param groupKeyPattern groupKeyPattern + */ + public NamingFuzzyWatchContext(String envName, String groupKeyPattern) { + this.envName = envName; + this.groupKeyPattern = groupKeyPattern; + } + + private void doNotifyWatcher(final String serviceKey, final String changedType, final String syncType, + FuzzyWatchEventWatcherWrapper fuzzyWatchEventWatcherWrapper) { + + if (ADD_SERVICE.equals(changedType) && fuzzyWatchEventWatcherWrapper.getSyncServiceKeys() + .contains(serviceKey)) { + return; + } + + if (DELETE_SERVICE.equals(changedType) && !fuzzyWatchEventWatcherWrapper.getSyncServiceKeys() + .contains(serviceKey)) { + return; + } + + String[] serviceKeyItems = NamingUtils.parseServiceKey(serviceKey); + String namespace = serviceKeyItems[0]; + String groupName = serviceKeyItems[1]; + String serviceName = serviceKeyItems[2]; + + final String resetSyncType = !initializationCompleted.get() ? FUZZY_WATCH_INIT_NOTIFY : syncType; + + Runnable job = () -> { + long start = System.currentTimeMillis(); + FuzzyWatchChangeEvent event = new FuzzyWatchChangeEvent(serviceName, groupName, namespace, changedType, + resetSyncType); + if (fuzzyWatchEventWatcherWrapper != null) { + fuzzyWatchEventWatcherWrapper.fuzzyWatchEventWatcher.onEvent(event); + } + LOGGER.info( + "[{}] [notify-watcher-ok] serviceName={}, groupName={}, namespace={}, watcher={},changedType={}, job run cost={} millis.", + envName, serviceName, groupName, namespace, fuzzyWatchEventWatcherWrapper.fuzzyWatchEventWatcher, + changedType, (System.currentTimeMillis() - start)); + if (changedType.equals(DELETE_SERVICE)) { + fuzzyWatchEventWatcherWrapper.getSyncServiceKeys() + .remove(NamingUtils.getServiceKey(namespace, groupName, serviceName)); + } else if (changedType.equals(ADD_SERVICE)) { + fuzzyWatchEventWatcherWrapper.getSyncServiceKeys() + .add(NamingUtils.getServiceKey(namespace, groupName, serviceName)); + } + }; + + try { + if (null != fuzzyWatchEventWatcherWrapper.fuzzyWatchEventWatcher.getExecutor()) { + LOGGER.info( + "[{}] [notify-watcher] task submitted to user executor, serviceName={}, groupName={}, namespace={}, listener={}.", + envName, serviceName, groupName, namespace, fuzzyWatchEventWatcherWrapper); + fuzzyWatchEventWatcherWrapper.fuzzyWatchEventWatcher.getExecutor().execute(job); + } else { + LOGGER.info( + "[{}] [notify-watcher] task execute in nacos thread, serviceName={}, groupName={}, namespace={}, listener={}.", + envName, serviceName, groupName, namespace, fuzzyWatchEventWatcherWrapper); + job.run(); + } + } catch (Throwable t) { + LOGGER.error( + "[{}] [notify-watcher-error] serviceName={}, groupName={}, namespace={}, listener={}, throwable={}.", + envName, serviceName, groupName, namespace, fuzzyWatchEventWatcherWrapper, t.getCause()); + } + } + + /** + * Mark initialization as complete and notify waiting threads. + */ + public void markInitializationComplete() { + LOGGER.info("[{}] [fuzzy-watch] pattern init notify finish pattern={},match service count {}", envName, + groupKeyPattern, receivedServiceKeys.size()); + initializationCompleted.set(true); + synchronized (this) { + notifyAll(); + } + } + + /** + * Remove a watcher from the context. + * + * @param watcher watcher to be removed + */ + public void removeWatcher(FuzzyWatchEventWatcher watcher) { + Iterator iterator = fuzzyWatchEventWatcherWrappers.iterator(); + while (iterator.hasNext()) { + FuzzyWatchEventWatcherWrapper next = iterator.next(); + if (next.fuzzyWatchEventWatcher.equals(watcher)) { + iterator.remove(); + LOGGER.info("[{}] [remove-watcher-ok] groupKeyPattern={}, watcher={},uuid={} ", getEnvName(), + this.groupKeyPattern, watcher, next.getUuid()); + } + } + + } + + /** + * Get the environment name. + * + * @return Environment name + */ + public String getEnvName() { + return envName; + } + + /** + * Set the environment name. + * + * @param envName Environment name to be set + */ + public void setEnvName(String envName) { + this.envName = envName; + } + + public String getGroupKeyPattern() { + return groupKeyPattern; + } + + /** + * Get the flag indicating whether the context is consistent with the server. + * + * @return AtomicBoolean indicating whether the context is consistent with the server + */ + public boolean isConsistentWithServer() { + return isConsistentWithServer.get(); + } + + public void setConsistentWithServer(boolean isConsistentWithServer) { + this.isConsistentWithServer.set(isConsistentWithServer); + } + + /** + * Check if the context is discarded. + * + * @return True if the context is discarded, otherwise false + */ + public boolean isDiscard() { + return isDiscard; + } + + /** + * Set the flag indicating whether the context is discarded. + * + * @param discard True to mark the context as discarded, otherwise false + */ + public void setDiscard(boolean discard) { + isDiscard = discard; + } + + /** + * Check if the context is initializing. + * + * @return True if the context is initializing, otherwise false + */ + public boolean isInitializing() { + return !initializationCompleted.get(); + } + + /** + * Get the set of data IDs associated with the context. + * + * @return Set of data IDs + */ + public Set getReceivedServiceKeys() { + return Collections.unmodifiableSet(receivedServiceKeys); + } + + public boolean addReceivedServiceKey(String serviceKey) { + return receivedServiceKeys.add(serviceKey); + } + + public boolean removeReceivedServiceKey(String serviceKey) { + return receivedServiceKeys.remove(serviceKey); + } + + /** + * Get the set of listeners associated with the context. + * + * @return Set of listeners + */ + public Set getFuzzyWatchEventWatcherWrappers() { + return fuzzyWatchEventWatcherWrappers; + } + + void syncFuzzyWatchers() { + for (FuzzyWatchEventWatcherWrapper namingFuzzyWatcher : fuzzyWatchEventWatcherWrappers) { + Set receivedServiceKeysContext = this.getReceivedServiceKeys(); + Set syncGroupKeys = namingFuzzyWatcher.getSyncServiceKeys(); + List groupKeyStates = FuzzyGroupKeyPattern.diffGroupKeys( + receivedServiceKeysContext, syncGroupKeys); + for (FuzzyGroupKeyPattern.GroupKeyState groupKeyState : groupKeyStates) { + String changedType = groupKeyState.isExist() ? ADD_SERVICE : DELETE_SERVICE; + doNotifyWatcher(groupKeyState.getGroupKey(), changedType, FUZZY_WATCH_DIFF_SYNC_NOTIFY, + namingFuzzyWatcher); + } + } + } + + void notifyFuzzyWatchers(String serviceKey, String changedType, String syncType, String watcherUuid) { + for (FuzzyWatchEventWatcherWrapper namingFuzzyWatcher : filterWatchers(watcherUuid)) { + doNotifyWatcher(serviceKey, changedType, syncType, namingFuzzyWatcher); + } + } + + private Set filterWatchers(String uuid) { + if (StringUtils.isBlank(uuid) || CollectionUtils.isEmpty(getFuzzyWatchEventWatcherWrappers())) { + return getFuzzyWatchEventWatcherWrappers(); + } else { + return getFuzzyWatchEventWatcherWrappers().stream().filter(a -> a.getUuid().equals(uuid)) + .collect(Collectors.toSet()); + } + } + + /** + * create a new future of this context. + * @return + */ + public Future> createNewFuture() { + Future> completableFuture = new Future>() { + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + throw new UnsupportedOperationException("not support to cancel fuzzy watch"); + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return NamingFuzzyWatchContext.this.initializationCompleted.get(); + } + + @Override + public ListView get() throws InterruptedException { + synchronized (NamingFuzzyWatchContext.this) { + while (!NamingFuzzyWatchContext.this.initializationCompleted.get()) { + NamingFuzzyWatchContext.this.wait(); + } + } + + ListView result = new ListView<>(); + result.setData(Arrays.asList(NamingFuzzyWatchContext.this.receivedServiceKeys.toArray(new String[0]))); + result.setCount(result.getData().size()); + return result; + } + + @Override + public ListView get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { + + if (!NamingFuzzyWatchContext.this.initializationCompleted.get()) { + synchronized (NamingFuzzyWatchContext.this) { + NamingFuzzyWatchContext.this.wait(unit.toMillis(timeout)); + } + } + + if (!NamingFuzzyWatchContext.this.initializationCompleted.get()) { + throw new TimeoutException( + "fuzzy watch result future timeout for " + unit.toMillis(timeout) + " millis"); + } + + ListView result = new ListView<>(); + result.setData(Arrays.asList(NamingFuzzyWatchContext.this.receivedServiceKeys.toArray(new String[0]))); + result.setCount(result.getData().size()); + return result; + } + }; + return completableFuture; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/cache/NamingFuzzyWatchServiceListHolder.java b/client/src/main/java/com/alibaba/nacos/client/naming/cache/NamingFuzzyWatchServiceListHolder.java new file mode 100644 index 00000000000..64fcc984eec --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/cache/NamingFuzzyWatchServiceListHolder.java @@ -0,0 +1,340 @@ +/* + * Copyright 1999-2023 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.client.naming.cache; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.listener.FuzzyWatchEventWatcher; +import com.alibaba.nacos.api.naming.remote.request.NamingFuzzyWatchRequest; +import com.alibaba.nacos.api.naming.remote.response.NamingFuzzyWatchResponse; +import com.alibaba.nacos.client.naming.event.NamingFuzzyWatchNotifyEvent; +import com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy; +import com.alibaba.nacos.client.utils.LogUtils; +import com.alibaba.nacos.common.executor.NameThreadFactory; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.Subscriber; +import com.alibaba.nacos.common.utils.CollectionUtils; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_INIT_NOTIFY; +import static com.alibaba.nacos.api.common.Constants.ServiceChangedType.ADD_SERVICE; +import static com.alibaba.nacos.api.common.Constants.WATCH_TYPE_CANCEL_WATCH; +import static com.alibaba.nacos.api.common.Constants.WATCH_TYPE_WATCH; + +/** + * Naming client fuzzy watch service list holder. + * + * @author tanyongquan + */ +public class NamingFuzzyWatchServiceListHolder extends Subscriber { + + private static final Logger LOGGER = LogUtils.logger(NamingFuzzyWatchServiceListHolder.class); + + private String notifierEventScope; + + private NamingGrpcClientProxy namingGrpcClientProxy; + + /** + * fuzzyListenExecuteBell. + */ + private final BlockingQueue fuzzyWatchExecuteBell = new ArrayBlockingQueue<>(1); + + private final Object bellItem = new Object(); + + private final AtomicLong fuzzyWatchLastAllSyncTime = new AtomicLong(System.currentTimeMillis()); + + private static final long FUZZY_LISTEN_ALL_SYNC_INTERNAL = 3 * 60 * 1000; + + ScheduledExecutorService executorService; + + /** + * The contents of {@code patternMatchMap} are Map{pattern -> Set[matched services]}. + */ + private Map fuzzyMatchContextMap = new ConcurrentHashMap<>(); + + public NamingFuzzyWatchServiceListHolder(String notifierEventScope) { + this.notifierEventScope = notifierEventScope; + NotifyCenter.registerSubscriber(this); + } + + /** + * shut down. + */ + public void shutdown() { + if (executorService != null && !executorService.isShutdown()) { + executorService.shutdown(); + } + } + + /** + * start. + */ + @SuppressWarnings("PMD.ThreadPoolCreationRule") + public void start() { + + executorService = Executors.newSingleThreadScheduledExecutor( + new NameThreadFactory("com.alibaba.nacos.client.naming.fuzzy.watch.Worker")); + executorService.submit(() -> { + while (!executorService.isShutdown() && !executorService.isTerminated()) { + try { + fuzzyWatchExecuteBell.poll(5L, TimeUnit.SECONDS); + if (executorService.isShutdown() || executorService.isTerminated()) { + continue; + } + executeNamingFuzzyWatch(); + } catch (Throwable e) { + LOGGER.error("[rpc-fuzzy-watch-execute] rpc fuzzy watch exception", e); + try { + Thread.sleep(50L); + } catch (InterruptedException interruptedException) { + //ignore + } + notifyFuzzyWatchSync(); + } + } + }); + } + + public void registerNamingGrpcClientProxy(NamingGrpcClientProxy namingGrpcClientProxy) { + this.namingGrpcClientProxy = namingGrpcClientProxy; + } + + public NamingFuzzyWatchContext getFuzzyWatchContext(String groupKeyPattern) { + return fuzzyMatchContextMap.get(groupKeyPattern); + } + + /** + * Add a watcher to the context. + * + * @param watcher watcher to be added + */ + public NamingFuzzyWatchContext registerFuzzyWatcher(String groupKeyPattern, FuzzyWatchEventWatcher watcher) { + NamingFuzzyWatchContext namingFuzzyWatchContext = initFuzzyWatchContextIfNeed(groupKeyPattern); + + FuzzyWatchEventWatcherWrapper fuzzyWatchEventWatcherWrapper = new FuzzyWatchEventWatcherWrapper(watcher); + if (namingFuzzyWatchContext.getFuzzyWatchEventWatcherWrappers().add(fuzzyWatchEventWatcherWrapper)) { + LOGGER.info(" [add-watcher-ok] groupKeyPattern={}, watcher={},uuid={} ", groupKeyPattern, watcher, + fuzzyWatchEventWatcherWrapper.getUuid()); + if (CollectionUtils.isNotEmpty(namingFuzzyWatchContext.getReceivedServiceKeys())) { + for (String serviceKey : namingFuzzyWatchContext.getReceivedServiceKeys()) { + NamingFuzzyWatchNotifyEvent namingFuzzyWatchNotifyEvent = NamingFuzzyWatchNotifyEvent.build( + notifierEventScope, groupKeyPattern, serviceKey, ADD_SERVICE, FUZZY_WATCH_INIT_NOTIFY, + fuzzyWatchEventWatcherWrapper.getUuid()); + NotifyCenter.publishEvent(namingFuzzyWatchNotifyEvent); + } + } + } + return namingFuzzyWatchContext; + } + + /** + * init fuzzy watch context. + * @param groupKeyPattern groupKeyPattern. + * @return fuzzy context. + */ + public NamingFuzzyWatchContext initFuzzyWatchContextIfNeed(String groupKeyPattern) { + if (!fuzzyMatchContextMap.containsKey(groupKeyPattern)) { + synchronized (fuzzyMatchContextMap) { + if (fuzzyMatchContextMap.containsKey(groupKeyPattern)) { + return fuzzyMatchContextMap.get(groupKeyPattern); + } + LOGGER.info("[fuzzy-watch] init fuzzy watch context for pattern {}", groupKeyPattern); + fuzzyMatchContextMap.putIfAbsent(groupKeyPattern, + new NamingFuzzyWatchContext(notifierEventScope, groupKeyPattern)); + notifyFuzzyWatchSync(); + } + } + return fuzzyMatchContextMap.get(groupKeyPattern); + } + + /** + * remove fuzzy watch context for pattern. + * @param groupKeyPattern group key pattern. + */ + public synchronized void removePatternMatchCache(String groupKeyPattern) { + NamingFuzzyWatchContext namingFuzzyWatchContext = fuzzyMatchContextMap.get(groupKeyPattern); + if (namingFuzzyWatchContext == null) { + return; + } + if (namingFuzzyWatchContext.isDiscard() && namingFuzzyWatchContext.getFuzzyWatchEventWatcherWrappers() + .isEmpty()) { + LOGGER.info("[fuzzy-watch] remove fuzzy watch context for pattern {}", groupKeyPattern); + fuzzyMatchContextMap.remove(groupKeyPattern); + } + } + + /** + * notify sync fuzzy watch with server. + */ + void notifyFuzzyWatchSync() { + fuzzyWatchExecuteBell.offer(bellItem); + } + + /** + * Execute fuzzy listen configuration changes. + * + *

This method iterates through all fuzzy listen contexts and determines whether they need to be added or + * removed based on their consistency with the server and discard status. It then calls the appropriate method to + * execute the fuzzy listen operation. + * + * @throws NacosException If an error occurs during the execution of fuzzy listen configuration changes. + */ + public void executeNamingFuzzyWatch() throws NacosException { + + // Obtain the current timestamp + long now = System.currentTimeMillis(); + + // Determine whether a full synchronization is needed + boolean needAllSync = now - fuzzyWatchLastAllSyncTime.get() >= FUZZY_LISTEN_ALL_SYNC_INTERNAL; + + List needSyncContexts = new ArrayList<>(); + // Iterate through all fuzzy listen contexts + for (NamingFuzzyWatchContext context : fuzzyMatchContextMap.values()) { + // Check if the context is consistent with the server + if (context.isConsistentWithServer()) { + // Skip if a full synchronization is not needed + if (!needAllSync) { + continue; + } else { + context.syncFuzzyWatchers(); + } + } + + needSyncContexts.add(context); + } + + // Execute fuzzy listen operation for addition + doExecuteNamingFuzzyWatch(needSyncContexts); + + // Update last all sync time if a full synchronization was performed + if (needAllSync) { + fuzzyWatchLastAllSyncTime.set(now); + } + } + + public void resetConsistenceStatus() { + fuzzyMatchContextMap.values() + .forEach(fuzzyWatcherContext -> fuzzyWatcherContext.setConsistentWithServer(false)); + } + + /** + * Execute fuzzy listen configuration changes for a specific map of contexts. + * + *

This method submits tasks to execute fuzzy listen operations asynchronously for the provided contexts. It + * waits for all tasks to complete and logs any errors that occur. + * + * @param contextLists The map of contexts to execute fuzzy listen operations for. + * @throws NacosException If an error occurs during the execution of fuzzy listen configuration changes. + */ + private void doExecuteNamingFuzzyWatch(List contextLists) throws NacosException { + // Return if the context map is null or empty + if (CollectionUtils.isEmpty(contextLists)) { + return; + } + + // Iterate through the context map and submit tasks for execution + for (NamingFuzzyWatchContext entry : contextLists) { + // Submit task for execution + NamingFuzzyWatchRequest configFuzzyWatchRequest = buildFuzzyWatchNamingRequest(entry); + try { + + // Execute the fuzzy listen operation + NamingFuzzyWatchResponse listenResponse = namingGrpcClientProxy.fuzzyWatchRequest( + configFuzzyWatchRequest); + if (listenResponse != null && listenResponse.isSuccess()) { + + if (configFuzzyWatchRequest.getWatchType().equals(WATCH_TYPE_CANCEL_WATCH)) { + removePatternMatchCache(entry.getGroupKeyPattern()); + } else { + entry.setConsistentWithServer(true); + } + + } + } catch (NacosException e) { + // Log error and retry after a short delay + LOGGER.error(" fuzzy watch request fail.", e); + try { + Thread.sleep(500L); + } catch (InterruptedException interruptedException) { + // Ignore interruption + } + // Retry notification + notifyFuzzyWatchSync(); + } + } + + } + + private NamingFuzzyWatchRequest buildFuzzyWatchNamingRequest(NamingFuzzyWatchContext namingFuzzyWatchContext) { + NamingFuzzyWatchRequest namingFuzzyWatchRequest = new NamingFuzzyWatchRequest(); + namingFuzzyWatchRequest.setInitializing(namingFuzzyWatchContext.isInitializing()); + namingFuzzyWatchRequest.setNamespace(namingGrpcClientProxy.getNamespaceId()); + namingFuzzyWatchRequest.setReceivedGroupKeys(namingFuzzyWatchContext.getReceivedServiceKeys()); + namingFuzzyWatchRequest.setGroupKeyPattern(namingFuzzyWatchContext.getGroupKeyPattern()); + if (namingFuzzyWatchContext.isDiscard() && namingFuzzyWatchContext.getFuzzyWatchEventWatcherWrappers() + .isEmpty()) { + namingFuzzyWatchRequest.setWatchType(WATCH_TYPE_CANCEL_WATCH); + } else { + namingFuzzyWatchRequest.setWatchType(WATCH_TYPE_WATCH); + } + return namingFuzzyWatchRequest; + } + + public Map getFuzzyMatchContextMap() { + return fuzzyMatchContextMap; + } + + @Override + public void onEvent(NamingFuzzyWatchNotifyEvent event) { + if (!event.scope().equals(notifierEventScope)) { + return; + } + + String changedType = event.getChangedType(); + String syncType = event.getSyncType(); + + String serviceKey = event.getServiceKey(); + String pattern = event.getPattern(); + String watchUuid = event.getWatcherUuid(); + NamingFuzzyWatchContext namingFuzzyWatchContext = fuzzyMatchContextMap.get(pattern); + if (namingFuzzyWatchContext == null) { + return; + } + namingFuzzyWatchContext.notifyFuzzyWatchers(serviceKey, changedType, syncType, watchUuid); + + } + + @Override + public Class subscribeType() { + return NamingFuzzyWatchNotifyEvent.class; + } + + public String getNotifierEventScope() { + return notifierEventScope; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/event/NamingFuzzyWatchNotifyEvent.java b/client/src/main/java/com/alibaba/nacos/client/naming/event/NamingFuzzyWatchNotifyEvent.java new file mode 100644 index 00000000000..072215f5bce --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/event/NamingFuzzyWatchNotifyEvent.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2023 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.client.naming.event; + +import com.alibaba.nacos.common.notify.Event; + +/** + * Watch notify event, including service change/watch initial. + * + * @author tanyongquan + */ +public class NamingFuzzyWatchNotifyEvent extends Event { + + private final String scope; + + private String watcherUuid; + + private String serviceKey; + + private String pattern; + + private final String changedType; + + private final String syncType; + + private NamingFuzzyWatchNotifyEvent(String scope, String pattern, String serviceKey, String changedType, + String syncType, String watcherUuid) { + this.scope = scope; + this.pattern = pattern; + this.serviceKey = serviceKey; + this.changedType = changedType; + this.syncType = syncType; + this.watcherUuid = watcherUuid; + } + + public static NamingFuzzyWatchNotifyEvent build(String eventScope, String pattern, String serviceKey, + String changedType, String syncType) { + return new NamingFuzzyWatchNotifyEvent(eventScope, pattern, serviceKey, changedType, syncType, null); + } + + public static NamingFuzzyWatchNotifyEvent build(String eventScope, String pattern, String serviceKey, + String changedType, String syncType, String watcherUuid) { + return new NamingFuzzyWatchNotifyEvent(eventScope, pattern, serviceKey, changedType, syncType, watcherUuid); + } + + public String getPattern() { + return pattern; + } + + public String getChangedType() { + return changedType; + } + + @Override + public String scope() { + return this.scope; + } + + public String getWatcherUuid() { + return watcherUuid; + } + + public String getServiceKey() { + return serviceKey; + } + + public String getScope() { + return scope; + } + + public String getSyncType() { + return syncType; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java b/client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java index efd7bdabf46..bea1ee38ee9 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java @@ -25,6 +25,7 @@ import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.api.selector.AbstractSelector; import com.alibaba.nacos.client.env.NacosClientProperties; +import com.alibaba.nacos.client.naming.cache.NamingFuzzyWatchServiceListHolder; import com.alibaba.nacos.client.naming.cache.ServiceInfoHolder; import com.alibaba.nacos.client.naming.core.NamingServerListManager; import com.alibaba.nacos.client.naming.core.ServiceInfoUpdateService; @@ -68,7 +69,8 @@ public class NamingClientProxyDelegate implements NamingClientProxy { private ScheduledExecutorService executorService; public NamingClientProxyDelegate(String namespace, ServiceInfoHolder serviceInfoHolder, - NacosClientProperties properties, InstancesChangeNotifier changeNotifier) throws NacosException { + NacosClientProperties properties, InstancesChangeNotifier changeNotifier, + NamingFuzzyWatchServiceListHolder namingFuzzyWatchServiceListHolder) throws NacosException { this.serviceInfoUpdateService = new ServiceInfoUpdateService(properties, serviceInfoHolder, this, changeNotifier); this.serverListManager = new NamingServerListManager(properties, namespace); @@ -79,7 +81,7 @@ public NamingClientProxyDelegate(String namespace, ServiceInfoHolder serviceInfo initSecurityProxy(properties); this.httpClientProxy = new NamingHttpClientProxy(namespace, securityProxy, serverListManager, properties); this.grpcClientProxy = new NamingGrpcClientProxy(namespace, securityProxy, serverListManager, properties, - serviceInfoHolder); + serviceInfoHolder, namingFuzzyWatchServiceListHolder); } private void initSecurityProxy(NacosClientProperties properties) { @@ -193,7 +195,8 @@ public boolean serverHealthy() { } private NamingClientProxy getExecuteClientProxy(Instance instance) { - if (instance.isEphemeral() || grpcClientProxy.isAbilitySupportedByServer(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)) { + if (instance.isEphemeral() || grpcClientProxy.isAbilitySupportedByServer( + AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)) { return grpcClientProxy; } return httpClientProxy; diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingFuzzyWatchNotifyRequestHandler.java b/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingFuzzyWatchNotifyRequestHandler.java new file mode 100644 index 00000000000..aa40d048a01 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingFuzzyWatchNotifyRequestHandler.java @@ -0,0 +1,126 @@ +/* + * 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.client.naming.remote.gprc; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.naming.remote.request.NamingFuzzyWatchChangeNotifyRequest; +import com.alibaba.nacos.api.naming.remote.request.NamingFuzzyWatchSyncRequest; +import com.alibaba.nacos.api.naming.remote.response.NamingFuzzyWatchChangeNotifyResponse; +import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.api.remote.request.Request; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.client.naming.cache.NamingFuzzyWatchContext; +import com.alibaba.nacos.client.naming.cache.NamingFuzzyWatchServiceListHolder; +import com.alibaba.nacos.client.naming.event.NamingFuzzyWatchNotifyEvent; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.remote.client.Connection; +import com.alibaba.nacos.common.remote.client.ServerRequestHandler; +import com.alibaba.nacos.common.utils.FuzzyGroupKeyPattern; + +import java.util.Collection; + +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_RESOURCE_CHANGED; + +/** + * handle fuzzy watch request from server. + * @author shiyiyue + */ +public class NamingFuzzyWatchNotifyRequestHandler implements ServerRequestHandler { + + NamingFuzzyWatchServiceListHolder namingFuzzyWatchServiceListHolder; + + public NamingFuzzyWatchNotifyRequestHandler(NamingFuzzyWatchServiceListHolder namingFuzzyWatchServiceListHolder) { + this.namingFuzzyWatchServiceListHolder = namingFuzzyWatchServiceListHolder; + NotifyCenter.registerToPublisher(NamingFuzzyWatchNotifyEvent.class, 1000); + } + + @Override + public Response requestReply(Request request, Connection connection) { + + if (request instanceof NamingFuzzyWatchSyncRequest) { + NamingFuzzyWatchSyncRequest watchNotifySyncRequest = (NamingFuzzyWatchSyncRequest) request; + NamingFuzzyWatchContext namingFuzzyWatchContext = namingFuzzyWatchServiceListHolder.getFuzzyMatchContextMap() + .get(watchNotifySyncRequest.getGroupKeyPattern()); + if (namingFuzzyWatchContext != null) { + Collection serviceKeys = watchNotifySyncRequest.getContexts(); + if (watchNotifySyncRequest.getSyncType().equals(Constants.FUZZY_WATCH_INIT_NOTIFY) + || watchNotifySyncRequest.getSyncType().equals(Constants.FUZZY_WATCH_DIFF_SYNC_NOTIFY)) { + for (NamingFuzzyWatchSyncRequest.Context serviceKey : serviceKeys) { + // may have a 'change event' sent to client before 'init event' + if (namingFuzzyWatchContext.addReceivedServiceKey(serviceKey.getServiceKey())) { + NotifyCenter.publishEvent(NamingFuzzyWatchNotifyEvent.build( + namingFuzzyWatchServiceListHolder.getNotifierEventScope(), + watchNotifySyncRequest.getGroupKeyPattern(), serviceKey.getServiceKey(), + serviceKey.getChangedType(), watchNotifySyncRequest.getSyncType())); + } + } + } else if (watchNotifySyncRequest.getSyncType().equals(Constants.FINISH_FUZZY_WATCH_INIT_NOTIFY)) { + namingFuzzyWatchContext.markInitializationComplete(); + } + } + + return new NamingFuzzyWatchChangeNotifyResponse(); + + } else if (request instanceof NamingFuzzyWatchChangeNotifyRequest) { + NamingFuzzyWatchChangeNotifyRequest notifyChangeRequest = (NamingFuzzyWatchChangeNotifyRequest) request; + String[] serviceKeyItems = NamingUtils.parseServiceKey(notifyChangeRequest.getServiceKey()); + String namespace = serviceKeyItems[0]; + String groupName = serviceKeyItems[1]; + String serviceName = serviceKeyItems[2]; + + Collection matchedPattern = FuzzyGroupKeyPattern.filterMatchedPatterns( + namingFuzzyWatchServiceListHolder.getFuzzyMatchContextMap().keySet(), serviceName, groupName, + namespace); + String serviceChangeType = notifyChangeRequest.getChangedType(); + + switch (serviceChangeType) { + case Constants.ServiceChangedType.ADD_SERVICE: + case Constants.ServiceChangedType.INSTANCE_CHANGED: + for (String pattern : matchedPattern) { + NamingFuzzyWatchContext namingFuzzyWatchContext = namingFuzzyWatchServiceListHolder.getFuzzyMatchContextMap() + .get(pattern); + if (namingFuzzyWatchContext != null && namingFuzzyWatchContext.addReceivedServiceKey( + ((NamingFuzzyWatchChangeNotifyRequest) request).getServiceKey())) { + //publish local service add event + NotifyCenter.publishEvent(NamingFuzzyWatchNotifyEvent.build( + namingFuzzyWatchServiceListHolder.getNotifierEventScope(), pattern, + notifyChangeRequest.getServiceKey(), Constants.ServiceChangedType.ADD_SERVICE, + FUZZY_WATCH_RESOURCE_CHANGED)); + } + } + break; + case Constants.ServiceChangedType.DELETE_SERVICE: + for (String pattern : matchedPattern) { + NamingFuzzyWatchContext namingFuzzyWatchContext = namingFuzzyWatchServiceListHolder.getFuzzyMatchContextMap() + .get(pattern); + if (namingFuzzyWatchContext != null && namingFuzzyWatchContext.removeReceivedServiceKey( + notifyChangeRequest.getServiceKey())) { + NotifyCenter.publishEvent(NamingFuzzyWatchNotifyEvent.build( + namingFuzzyWatchServiceListHolder.getNotifierEventScope(), pattern, + notifyChangeRequest.getServiceKey(), Constants.ServiceChangedType.DELETE_SERVICE, + FUZZY_WATCH_RESOURCE_CHANGED)); + } + } + break; + default: + break; + } + return new NamingFuzzyWatchChangeNotifyResponse(); + } + return null; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java b/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java index 508b76f0ab3..29d7e480486 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java @@ -29,24 +29,28 @@ import com.alibaba.nacos.api.naming.remote.request.AbstractNamingRequest; import com.alibaba.nacos.api.naming.remote.request.BatchInstanceRequest; import com.alibaba.nacos.api.naming.remote.request.InstanceRequest; +import com.alibaba.nacos.api.naming.remote.request.NamingFuzzyWatchRequest; import com.alibaba.nacos.api.naming.remote.request.PersistentInstanceRequest; import com.alibaba.nacos.api.naming.remote.request.ServiceListRequest; import com.alibaba.nacos.api.naming.remote.request.ServiceQueryRequest; import com.alibaba.nacos.api.naming.remote.request.SubscribeServiceRequest; import com.alibaba.nacos.api.naming.remote.response.BatchInstanceResponse; +import com.alibaba.nacos.api.naming.remote.response.NamingFuzzyWatchResponse; import com.alibaba.nacos.api.naming.remote.response.QueryServiceResponse; import com.alibaba.nacos.api.naming.remote.response.ServiceListResponse; import com.alibaba.nacos.api.naming.remote.response.SubscribeServiceResponse; import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.api.remote.RemoteConstants; +import com.alibaba.nacos.api.remote.request.Request; import com.alibaba.nacos.api.remote.response.Response; import com.alibaba.nacos.api.remote.response.ResponseCode; import com.alibaba.nacos.api.selector.AbstractSelector; import com.alibaba.nacos.api.selector.SelectorType; +import com.alibaba.nacos.client.address.ServerListChangeEvent; import com.alibaba.nacos.client.env.NacosClientProperties; import com.alibaba.nacos.client.monitor.MetricsMonitor; +import com.alibaba.nacos.client.naming.cache.NamingFuzzyWatchServiceListHolder; import com.alibaba.nacos.client.naming.cache.ServiceInfoHolder; -import com.alibaba.nacos.client.address.ServerListChangeEvent; import com.alibaba.nacos.client.naming.remote.AbstractNamingClientProxy; import com.alibaba.nacos.client.naming.remote.gprc.redo.NamingGrpcRedoService; import com.alibaba.nacos.client.naming.remote.gprc.redo.data.BatchInstanceRedoData; @@ -94,7 +98,8 @@ public class NamingGrpcClientProxy extends AbstractNamingClientProxy { private final NamingGrpcRedoService redoService; public NamingGrpcClientProxy(String namespaceId, SecurityProxy securityProxy, ServerListFactory serverListFactory, - NacosClientProperties properties, ServiceInfoHolder serviceInfoHolder) throws NacosException { + NacosClientProperties properties, ServiceInfoHolder serviceInfoHolder, + NamingFuzzyWatchServiceListHolder namingFuzzyWatchServiceListHolder) throws NacosException { super(securityProxy); this.namespaceId = namespaceId; this.uuid = UUID.randomUUID().toString(); @@ -103,18 +108,23 @@ public NamingGrpcClientProxy(String namespaceId, SecurityProxy securityProxy, Se labels.put(RemoteConstants.LABEL_SOURCE, RemoteConstants.LABEL_SOURCE_SDK); labels.put(RemoteConstants.LABEL_MODULE, RemoteConstants.LABEL_MODULE_NAMING); labels.put(Constants.APPNAME, AppNameUtils.getAppName()); + namingFuzzyWatchServiceListHolder.registerNamingGrpcClientProxy(this); this.rpcClient = RpcClientFactory.createClient(uuid, ConnectionType.GRPC, labels, RpcClientTlsConfigFactory.getInstance().createSdkConfig(properties.asProperties())); - this.redoService = new NamingGrpcRedoService(this, properties); + this.redoService = new NamingGrpcRedoService(this, namingFuzzyWatchServiceListHolder, properties); NAMING_LOGGER.info("Create naming rpc client for uuid->{}", uuid); - start(serverListFactory, serviceInfoHolder); + start(serverListFactory, serviceInfoHolder, namingFuzzyWatchServiceListHolder); } - private void start(ServerListFactory serverListFactory, ServiceInfoHolder serviceInfoHolder) throws NacosException { + private void start(ServerListFactory serverListFactory, ServiceInfoHolder serviceInfoHolder, + NamingFuzzyWatchServiceListHolder namingFuzzyWatchServiceListHolder) throws NacosException { rpcClient.serverListFactory(serverListFactory); rpcClient.registerConnectionListener(redoService); rpcClient.registerServerRequestHandler(new NamingPushRequestHandler(serviceInfoHolder)); + rpcClient.registerServerRequestHandler( + new NamingFuzzyWatchNotifyRequestHandler(namingFuzzyWatchServiceListHolder)); rpcClient.start(); + namingFuzzyWatchServiceListHolder.start(); NotifyCenter.registerSubscriber(this); } @@ -268,9 +278,8 @@ public void doRegisterServiceForPersistent(String serviceName, String groupName, @Override public void deregisterService(String serviceName, String groupName, Instance instance) throws NacosException { - NAMING_LOGGER - .info("[DEREGISTER-SERVICE] {} deregistering service {} with instance: {}", namespaceId, serviceName, - instance); + NAMING_LOGGER.info("[DEREGISTER-SERVICE] {} deregistering service {} with instance: {}", namespaceId, + serviceName, instance); if (instance.isEphemeral()) { deregisterServiceForEphemeral(serviceName, groupName, instance); } else { @@ -438,12 +447,31 @@ public boolean isAbilitySupportedByServer(AbilityKey abilityKey) { return rpcClient.getConnectionAbility(abilityKey) == AbilityStatus.SUPPORTED; } - private T requestToServer(AbstractNamingRequest request, Class responseClass) + /** + * Execute unsubscribe operation. + * + * @param namingFuzzyWatchRequest namingFuzzyWatchRequest + * @throws NacosException nacos exception + */ + public NamingFuzzyWatchResponse fuzzyWatchRequest(NamingFuzzyWatchRequest namingFuzzyWatchRequest) throws NacosException { + return requestToServer(namingFuzzyWatchRequest, NamingFuzzyWatchResponse.class); + } + + private T requestToServer(Request request, Class responseClass) throws NacosException { Response response = null; try { - request.putAllHeader( - getSecurityHeaders(request.getNamespace(), request.getGroupName(), request.getServiceName())); + if (request instanceof AbstractNamingRequest) { + request.putAllHeader(getSecurityHeaders(((AbstractNamingRequest) request).getNamespace(), + ((AbstractNamingRequest) request).getGroupName(), + ((AbstractNamingRequest) request).getServiceName())); + } else if (request instanceof NamingFuzzyWatchRequest) { + request.putAllHeader( + getSecurityHeaders(((NamingFuzzyWatchRequest) request).getNamespace(), null, null)); + } else { + throw new NacosException(400, "unknown naming request type"); + } + response = requestTimeout < 0 ? rpcClient.request(request) : rpcClient.request(request, requestTimeout); if (ResponseCode.SUCCESS.getCode() != response.getResultCode()) { // If the 403 login operation is triggered, refresh the accessToken of the client @@ -475,7 +503,7 @@ private T requestToServer(AbstractNamingRequest request, Cl * successful. * @param response The response object containing registration result information, or null if registration failed. */ - private void recordRequestFailedMetrics(AbstractNamingRequest request, Exception exception, Response response) { + private void recordRequestFailedMetrics(Request request, Exception exception, Response response) { if (Objects.isNull(response)) { MetricsMonitor.getNamingRequestFailedMonitor(request.getClass().getSimpleName(), MONITOR_LABEL_NONE, MONITOR_LABEL_NONE, exception.getClass().getSimpleName()).inc(); @@ -508,4 +536,8 @@ private void shutDownAndRemove(String uuid) { public boolean isEnable() { return rpcClient.isRunning(); } + + public String getNamespaceId() { + return namespaceId; + } } diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/redo/NamingGrpcRedoService.java b/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/redo/NamingGrpcRedoService.java index aad883ddabe..7527b9d00ce 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/redo/NamingGrpcRedoService.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/redo/NamingGrpcRedoService.java @@ -22,6 +22,7 @@ import com.alibaba.nacos.api.naming.pojo.ServiceInfo; import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.client.env.NacosClientProperties; +import com.alibaba.nacos.client.naming.cache.NamingFuzzyWatchServiceListHolder; import com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy; import com.alibaba.nacos.client.naming.remote.gprc.redo.data.BatchInstanceRedoData; import com.alibaba.nacos.client.naming.remote.gprc.redo.data.InstanceRedoData; @@ -59,12 +60,16 @@ public class NamingGrpcRedoService implements ConnectionEventListener { private final ConcurrentMap subscribes = new ConcurrentHashMap<>(); + private final NamingFuzzyWatchServiceListHolder namingFuzzyWatchServiceListHolder; + private final ScheduledExecutorService redoExecutor; private volatile boolean connected = false; - public NamingGrpcRedoService(NamingGrpcClientProxy clientProxy, NacosClientProperties properties) { + public NamingGrpcRedoService(NamingGrpcClientProxy clientProxy, + NamingFuzzyWatchServiceListHolder namingFuzzyWatchServiceListHolder, NacosClientProperties properties) { setProperties(properties); + this.namingFuzzyWatchServiceListHolder = namingFuzzyWatchServiceListHolder; this.redoExecutor = new ScheduledThreadPoolExecutor(redoThreadCount, new NameThreadFactory(REDO_THREAD_NAME)); this.redoExecutor.scheduleWithFixedDelay(new RedoScheduledTask(clientProxy, this), redoDelayTime, redoDelayTime, TimeUnit.MILLISECONDS); @@ -100,6 +105,9 @@ public void onDisConnect(Connection connection) { synchronized (subscribes) { subscribes.values().forEach(subscriberRedoData -> subscriberRedoData.setRegistered(false)); } + synchronized (namingFuzzyWatchServiceListHolder) { + namingFuzzyWatchServiceListHolder.resetConsistenceStatus(); + } LogUtils.NAMING_LOGGER.warn("mark to redo completed"); } diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/redo/data/FuzzyWatcherRedoData.java b/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/redo/data/FuzzyWatcherRedoData.java new file mode 100644 index 00000000000..f0e820910dd --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/redo/data/FuzzyWatcherRedoData.java @@ -0,0 +1,33 @@ +/* + * Copyright 1999-2023 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.client.naming.remote.gprc.redo.data; + +/** + * Redo data for fuzzy watcher. + * + * @author tanyongquan + */ +public class FuzzyWatcherRedoData extends RedoData { + + private FuzzyWatcherRedoData(String serviceNamePattern, String groupNamePattern) { + super(serviceNamePattern, groupNamePattern); + } + + public static FuzzyWatcherRedoData build(String serviceNamePattern, String groupNamePattern) { + return new FuzzyWatcherRedoData(serviceNamePattern, groupNamePattern); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java b/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java index f6f8be2b4cc..4325d60adcc 100644 --- a/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java +++ b/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java @@ -62,8 +62,6 @@ public class ParamUtil { private static double perTaskConfigSize = 3000; - private static final String PER_TASK_CONTEXT_SIZE_KEY = "PER_TASK_CONTEXT_SIZE_KEY"; - private static final String NACOS_CLIENT_APP_KEY = "nacos.client.appKey"; private static final String BLANK_STR = ""; @@ -86,9 +84,6 @@ public class ParamUtil { private static final String DEFAULT_PER_TASK_CONFIG_SIZE_KEY = "3000"; - private static final String DEFAULT_PER_TASK_CONTEXT_SIZE_KEY = "3000"; - - private static double perTaskContextSize = 3000; private static final int DESENSITISE_PARAMETER_MIN_LENGTH = 2; private static final int DESENSITISE_PARAMETER_KEEP_ONE_CHAR_LENGTH = 8; @@ -115,9 +110,6 @@ public class ParamUtil { perTaskConfigSize = initPerTaskConfigSize(); LOGGER.info("PER_TASK_CONFIG_SIZE: {}", perTaskConfigSize); - - perTaskContextSize = initPerTaskContextSize(); - LOGGER.info("PER_TASK_CONTEXT_SIZE: {}", perTaskContextSize); } private static int initConnectionTimeout() { @@ -154,16 +146,6 @@ private static double initPerTaskConfigSize() { } } - private static double initPerTaskContextSize() { - try { - return Double.parseDouble(NacosClientProperties.PROTOTYPE.getProperty(PER_TASK_CONTEXT_SIZE_KEY, - DEFAULT_PER_TASK_CONTEXT_SIZE_KEY)); - } catch (NumberFormatException e) { - LOGGER.error("[PER_TASK_CONTEXT_SIZE] PER_TASK_CONTEXT_SIZE invalid", e); - throw new IllegalArgumentException("invalid PER_TASK_CONTEXT_SIZE, expected value type double", e); - } - } - public static String getAppKey() { return appKey; } @@ -220,14 +202,6 @@ public static void setPerTaskConfigSize(double perTaskConfigSize) { ParamUtil.perTaskConfigSize = perTaskConfigSize; } - public static double getPerTaskContextSize() { - return perTaskContextSize; - } - - public static void setPerTaskContextSize(double perTaskContextSize) { - ParamUtil.perTaskContextSize = perTaskContextSize; - } - public static String getDefaultServerPort() { return serverPort; } @@ -283,10 +257,10 @@ public static String parsingEndpointRule(String endpointUrl) { if (StringUtils.isNotBlank(endpointUrlSource)) { endpointUrl = endpointUrlSource; } - + return StringUtils.isNotBlank(endpointUrl) ? endpointUrl : ""; } - + endpointUrl = endpointUrl.substring(endpointUrl.indexOf("${") + 2, endpointUrl.lastIndexOf("}")); int defStartOf = endpointUrl.indexOf(":"); String defaultEndpointUrl = null; @@ -294,13 +268,12 @@ public static String parsingEndpointRule(String endpointUrl) { defaultEndpointUrl = endpointUrl.substring(defStartOf + 1); endpointUrl = endpointUrl.substring(0, defStartOf); } - String endpointUrlSource = TemplateUtils.stringBlankAndThenExecute( NacosClientProperties.PROTOTYPE.getProperty(endpointUrl), () -> NacosClientProperties.PROTOTYPE.getProperty( PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_URL)); - + if (StringUtils.isBlank(endpointUrlSource)) { if (StringUtils.isNotBlank(defaultEndpointUrl)) { endpointUrl = defaultEndpointUrl; @@ -308,7 +281,7 @@ public static String parsingEndpointRule(String endpointUrl) { } else { endpointUrl = endpointUrlSource; } - + return StringUtils.isNotBlank(endpointUrl) ? endpointUrl : ""; } diff --git a/client/src/main/resources/META-INF/native-image/com.alibaba.nacos/nacos-client/reflect-config.json b/client/src/main/resources/META-INF/native-image/com.alibaba.nacos/nacos-client/reflect-config.json index 3734fbb2843..da2ba912161 100644 --- a/client/src/main/resources/META-INF/native-image/com.alibaba.nacos/nacos-client/reflect-config.json +++ b/client/src/main/resources/META-INF/native-image/com.alibaba.nacos/nacos-client/reflect-config.json @@ -535,14 +535,23 @@ {"name":"getServiceName","parameterTypes":[] } ] }, +{ + "name":"com.alibaba.nacos.api.naming.remote.request.AbstractFuzzyWatchNotifyRequest", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[ {"name":"getInstances","parameterTypes":[] }, + {"name":"getType","parameterTypes":[] }] +}, { "name":"com.alibaba.nacos.api.naming.remote.request.BatchInstanceRequest", "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, "methods":[ - {"name":"getInstances","parameterTypes":[] }, - {"name":"getType","parameterTypes":[] } + {"name":"","parameterTypes":[] }, + {"name":"getModule","parameterTypes":[] }, + {"name":"getServiceChangedType","parameterTypes":[] } ] }, { @@ -603,6 +612,28 @@ {"name":"isHealthyOnly","parameterTypes":[] } ] }, +{ + "name":"com.alibaba.nacos.api.naming.remote.request.FuzzyWatchNotifyChangeRequest", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[ + {"name":"","parameterTypes":[] }, + {"name":"getServiceName","parameterTypes":[] }, + {"name":"getGroupName","parameterTypes":[] } + ] +}, +{ + "name":"com.alibaba.nacos.api.naming.remote.request.FuzzyWatchNotifyInitRequest", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[ + {"name":"","parameterTypes":[] }, + {"name":"getServicesName","parameterTypes":[] }, + {"name":"getPattern","parameterTypes":[] } + ] +}, { "name":"com.alibaba.nacos.api.naming.remote.request.SubscribeServiceRequest", "allDeclaredFields":true, diff --git a/client/src/test/java/com/alibaba/nacos/client/config/NacosConfigServiceTest.java b/client/src/test/java/com/alibaba/nacos/client/config/NacosConfigServiceTest.java index 4cf62936894..6dcc4e368cf 100644 --- a/client/src/test/java/com/alibaba/nacos/client/config/NacosConfigServiceTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/config/NacosConfigServiceTest.java @@ -38,6 +38,7 @@ import org.mockito.quality.Strictness; import java.lang.reflect.Field; +import java.util.Arrays; import java.util.Collections; import java.util.Properties; import java.util.concurrent.Executor; @@ -98,10 +99,12 @@ void testGetConfigFromFailOver() throws NacosException { final String group = "2"; final String tenant = "public"; - MockedStatic localConfigInfoProcessorMockedStatic = Mockito.mockStatic(LocalConfigInfoProcessor.class); + MockedStatic localConfigInfoProcessorMockedStatic = Mockito.mockStatic( + LocalConfigInfoProcessor.class); try { String contentFailOver = "failOverContent" + System.currentTimeMillis(); - localConfigInfoProcessorMockedStatic.when(() -> LocalConfigInfoProcessor.getFailover(any(), eq(dataId), eq(group), eq(tenant))) + localConfigInfoProcessorMockedStatic.when( + () -> LocalConfigInfoProcessor.getFailover(any(), eq(dataId), eq(group), eq(tenant))) .thenReturn(contentFailOver); final int timeout = 3000; @@ -118,18 +121,22 @@ void testGetConfigFromLocalCache() throws NacosException { final String group = "2"; final String tenant = "public"; - MockedStatic localConfigInfoProcessorMockedStatic = Mockito.mockStatic(LocalConfigInfoProcessor.class); + MockedStatic localConfigInfoProcessorMockedStatic = Mockito.mockStatic( + LocalConfigInfoProcessor.class); try { String contentFailOver = "localCacheContent" + System.currentTimeMillis(); //fail over null - localConfigInfoProcessorMockedStatic.when(() -> LocalConfigInfoProcessor.getFailover(any(), eq(dataId), eq(group), eq(tenant))) + localConfigInfoProcessorMockedStatic.when( + () -> LocalConfigInfoProcessor.getFailover(any(), eq(dataId), eq(group), eq(tenant))) .thenReturn(null); //snapshot content - localConfigInfoProcessorMockedStatic.when(() -> LocalConfigInfoProcessor.getSnapshot(any(), eq(dataId), eq(group), eq(tenant))) + localConfigInfoProcessorMockedStatic.when( + () -> LocalConfigInfoProcessor.getSnapshot(any(), eq(dataId), eq(group), eq(tenant))) .thenReturn(contentFailOver); //form server error. final int timeout = 3000; - Mockito.when(mockWoker.getServerConfig(dataId, group, tenant, timeout, false)).thenThrow(new NacosException()); + Mockito.when(mockWoker.getServerConfig(dataId, group, tenant, timeout, false)) + .thenThrow(new NacosException()); final String config = nacosConfigService.getConfig(dataId, group, timeout); assertEquals(contentFailOver, config); @@ -145,10 +152,12 @@ void testGetConfig403() throws NacosException { final String group = "2"; final String tenant = "public"; - MockedStatic localConfigInfoProcessorMockedStatic = Mockito.mockStatic(LocalConfigInfoProcessor.class); + MockedStatic localConfigInfoProcessorMockedStatic = Mockito.mockStatic( + LocalConfigInfoProcessor.class); try { //fail over null - localConfigInfoProcessorMockedStatic.when(() -> LocalConfigInfoProcessor.getFailover(any(), eq(dataId), eq(group), eq(tenant))) + localConfigInfoProcessorMockedStatic.when( + () -> LocalConfigInfoProcessor.getFailover(any(), eq(dataId), eq(group), eq(tenant))) .thenReturn(null); //form server error. @@ -157,7 +166,6 @@ void testGetConfig403() throws NacosException { .thenThrow(new NacosException(NacosException.NO_RIGHT, "no right")); try { nacosConfigService.getConfig(dataId, group, timeout); - Assert.fail(); assertTrue(false); } catch (NacosException e) { assertEquals(NacosException.NO_RIGHT, e.getErrCode()); @@ -196,45 +204,30 @@ public void receiveConfigInfo(String configInfo) { public void startInternal() throws NacosException { // NOOP } - + @Override public String getName() { return "TestConfigTransportClient"; } - + @Override public void notifyListenConfig() { // NOOP } - - @Override - public void notifyFuzzyListenConfig() { - // NOOP - } - + @Override public void executeConfigListen() { // NOOP } - - @Override - public void executeConfigFuzzyListen() throws NacosException { - // NOOP - } - + @Override public void removeCache(String dataId, String group) { // NOOP } - - @Override - public void removeFuzzyListenContext(String dataIdPattern, String group) { - // NOOP - } - + @Override - public ConfigResponse queryConfig(String dataId, String group, String tenant, long readTimeous, boolean notify) - throws NacosException { + public ConfigResponse queryConfig(String dataId, String group, String tenant, long readTimeous, + boolean notify) throws NacosException { ConfigResponse configResponse = new ConfigResponse(); configResponse.setContent(content); configResponse.setDataId(dataId); @@ -244,8 +237,9 @@ public ConfigResponse queryConfig(String dataId, String group, String tenant, lo } @Override - public boolean publishConfig(String dataId, String group, String tenant, String appName, String tag, String betaIps, - String content, String encryptedDataKey, String casMd5, String type) throws NacosException { + public boolean publishConfig(String dataId, String group, String tenant, String appName, String tag, + String betaIps, String content, String encryptedDataKey, String casMd5, String type) + throws NacosException { return false; } @@ -257,13 +251,14 @@ public boolean removeConfig(String dataId, String group, String tenant, String t Mockito.when(mockWoker.getAgent()).thenReturn(client); final String config = nacosConfigService.getConfigAndSignListener(dataId, group, timeout, listener); - Assert.assertEquals(content, config); - + assertEquals(content, config); + Mockito.verify(mockWoker, Mockito.times(1)) .addTenantListenersWithContent(dataId, group, content, null, Collections.singletonList(listener)); assertEquals(content, config); - Mockito.verify(mockWoker, Mockito.times(1)).addTenantListenersWithContent(dataId, group, content, null, Arrays.asList(listener)); + Mockito.verify(mockWoker, Mockito.times(1)) + .addTenantListenersWithContent(dataId, group, content, null, Arrays.asList(listener)); } @Test @@ -275,16 +270,15 @@ void testAddListener() throws NacosException { public Executor getExecutor() { return null; } - + @Override public void receiveConfigInfo(String configInfo) { - + } }; - + nacosConfigService.addListener(dataId, group, listener); - Mockito.verify(mockWoker, Mockito.times(1)) - .addTenantListeners(dataId, group, Collections.singletonList(listener)); + Mockito.verify(mockWoker, Mockito.times(1)).addTenantListeners(dataId, group, Arrays.asList(listener)); } @Test @@ -294,12 +288,14 @@ void testPublishConfig() throws NacosException { String content = "123"; String namespace = "public"; String type = ConfigType.getDefaultType().getType(); - Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", null, type)).thenReturn(true); + Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", null, type)) + .thenReturn(true); final boolean b = nacosConfigService.publishConfig(dataId, group, content); assertTrue(b); - Mockito.verify(mockWoker, Mockito.times(1)).publishConfig(dataId, group, namespace, null, null, null, content, "", null, type); + Mockito.verify(mockWoker, Mockito.times(1)) + .publishConfig(dataId, group, namespace, null, null, null, content, "", null, type); } @Test @@ -310,12 +306,14 @@ void testPublishConfig2() throws NacosException { String namespace = "public"; String type = ConfigType.PROPERTIES.getType(); - Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", null, type)).thenReturn(true); + Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", null, type)) + .thenReturn(true); final boolean b = nacosConfigService.publishConfig(dataId, group, content, type); assertTrue(b); - Mockito.verify(mockWoker, Mockito.times(1)).publishConfig(dataId, group, namespace, null, null, null, content, "", null, type); + Mockito.verify(mockWoker, Mockito.times(1)) + .publishConfig(dataId, group, namespace, null, null, null, content, "", null, type); } @Test @@ -327,12 +325,14 @@ void testPublishConfigCas() throws NacosException { String casMd5 = "96147704e3cb8be8597d55d75d244a02"; String type = ConfigType.getDefaultType().getType(); - Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", casMd5, type)).thenReturn(true); + Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", casMd5, type)) + .thenReturn(true); final boolean b = nacosConfigService.publishConfigCas(dataId, group, content, casMd5); assertTrue(b); - Mockito.verify(mockWoker, Mockito.times(1)).publishConfig(dataId, group, namespace, null, null, null, content, "", casMd5, type); + Mockito.verify(mockWoker, Mockito.times(1)) + .publishConfig(dataId, group, namespace, null, null, null, content, "", casMd5, type); } @Test @@ -344,12 +344,14 @@ void testPublishConfigCas2() throws NacosException { String casMd5 = "96147704e3cb8be8597d55d75d244a02"; String type = ConfigType.PROPERTIES.getType(); - Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", casMd5, type)).thenReturn(true); + Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", casMd5, type)) + .thenReturn(true); final boolean b = nacosConfigService.publishConfigCas(dataId, group, content, casMd5, type); assertTrue(b); - Mockito.verify(mockWoker, Mockito.times(1)).publishConfig(dataId, group, namespace, null, null, null, content, "", casMd5, type); + Mockito.verify(mockWoker, Mockito.times(1)) + .publishConfig(dataId, group, namespace, null, null, null, content, "", casMd5, type); } @Test diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/remote/AbstractNamingClientProxyTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/remote/AbstractNamingClientProxyTest.java index 7f570c2780b..cba87c980c2 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/remote/AbstractNamingClientProxyTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/remote/AbstractNamingClientProxyTest.java @@ -25,8 +25,8 @@ import com.alibaba.nacos.api.naming.pojo.Service; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; import com.alibaba.nacos.api.selector.AbstractSelector; -import com.alibaba.nacos.client.auth.ram.utils.SignUtil; import com.alibaba.nacos.client.address.ServerListChangeEvent; +import com.alibaba.nacos.client.auth.ram.utils.SignUtil; import com.alibaba.nacos.client.security.SecurityProxy; import com.alibaba.nacos.client.utils.AppNameUtils; import com.alibaba.nacos.common.notify.Event; diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegateTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegateTest.java index bdc3ac6cec1..7e1031cba34 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegateTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegateTest.java @@ -27,6 +27,7 @@ import com.alibaba.nacos.api.selector.ExpressionSelector; import com.alibaba.nacos.api.selector.NoneSelector; import com.alibaba.nacos.client.env.NacosClientProperties; +import com.alibaba.nacos.client.naming.cache.NamingFuzzyWatchServiceListHolder; import com.alibaba.nacos.client.naming.cache.ServiceInfoHolder; import com.alibaba.nacos.client.naming.event.InstancesChangeNotifier; import com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy; @@ -65,6 +66,9 @@ class NamingClientProxyDelegateTest { @Mock NamingGrpcClientProxy mockGrpcClient; + @Mock + NamingFuzzyWatchServiceListHolder namingFuzzyWatchServiceListHolder; + NamingClientProxyDelegate delegate; InstancesChangeNotifier notifier; @@ -77,7 +81,8 @@ void setUp() throws NacosException, NoSuchFieldException, IllegalAccessException props.setProperty("serverAddr", "localhost"); nacosClientProperties = NacosClientProperties.PROTOTYPE.derive(props); notifier = new InstancesChangeNotifier(); - delegate = new NamingClientProxyDelegate(TEST_NAMESPACE, holder, nacosClientProperties, notifier); + delegate = new NamingClientProxyDelegate(TEST_NAMESPACE, holder, nacosClientProperties, notifier, + namingFuzzyWatchServiceListHolder); Field grpcClientProxyField = NamingClientProxyDelegate.class.getDeclaredField("grpcClientProxy"); grpcClientProxyField.setAccessible(true); grpcClientProxyField.set(delegate, mockGrpcClient); diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxyTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxyTest.java index bdb9472cfbc..0efeba8f6db 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxyTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxyTest.java @@ -47,6 +47,7 @@ import com.alibaba.nacos.api.selector.NoneSelector; import com.alibaba.nacos.client.env.NacosClientProperties; import com.alibaba.nacos.client.naming.cache.ServiceInfoHolder; +import com.alibaba.nacos.client.naming.cache.NamingFuzzyWatchServiceListHolder; import com.alibaba.nacos.client.address.ServerListChangeEvent; import com.alibaba.nacos.client.naming.remote.gprc.redo.NamingGrpcRedoService; import com.alibaba.nacos.client.security.SecurityProxy; @@ -120,6 +121,9 @@ class NamingGrpcClientProxyTest { @Mock private ServiceInfoHolder holder; + @Mock + private NamingFuzzyWatchServiceListHolder namingFuzzyWatchServiceListHolder; + @Mock private RpcClient rpcClient; @@ -145,7 +149,8 @@ void setUp() throws NacosException, NoSuchFieldException, IllegalAccessException prop = new Properties(); final NacosClientProperties nacosClientProperties = NacosClientProperties.PROTOTYPE.derive(prop); - client = new NamingGrpcClientProxy(NAMESPACE_ID, proxy, factory, nacosClientProperties, holder); + client = new NamingGrpcClientProxy(NAMESPACE_ID, proxy, factory, nacosClientProperties, holder, + namingFuzzyWatchServiceListHolder); Field uuidField = NamingGrpcClientProxy.class.getDeclaredField("uuid"); uuidField.setAccessible(true); @@ -699,7 +704,8 @@ public void close() { @Test void testConfigAppNameLabels() throws Exception { final NacosClientProperties nacosClientProperties = NacosClientProperties.PROTOTYPE.derive(prop); - client = new NamingGrpcClientProxy(NAMESPACE_ID, proxy, factory, nacosClientProperties, holder); + client = new NamingGrpcClientProxy(NAMESPACE_ID, proxy, factory, nacosClientProperties, holder, + namingFuzzyWatchServiceListHolder); Field rpcClientField = NamingGrpcClientProxy.class.getDeclaredField("rpcClient"); rpcClientField.setAccessible(true); RpcClient rpcClient = (RpcClient) rpcClientField.get(client); diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingPushRequestHandlerTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingPushRequestHandlerTest.java index 341c26e1dcd..40855f67ae6 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingPushRequestHandlerTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingPushRequestHandlerTest.java @@ -25,6 +25,7 @@ import com.alibaba.nacos.api.remote.request.Request; import com.alibaba.nacos.api.remote.response.Response; import com.alibaba.nacos.client.naming.cache.ServiceInfoHolder; + import com.alibaba.nacos.client.naming.remote.TestConnection; import com.alibaba.nacos.common.remote.client.RpcClient; import org.junit.jupiter.api.Test; @@ -54,6 +55,7 @@ void testRequestReply() { @Test void testRequestReplyOtherType() { ServiceInfoHolder holder = mock(ServiceInfoHolder.class); + NamingPushRequestHandler handler = new NamingPushRequestHandler(holder); assertNull(handler.requestReply(new HealthCheckRequest(), new TestConnection(new RpcClient.ServerInfo()))); } diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/redo/NamingGrpcRedoServiceTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/redo/NamingGrpcRedoServiceTest.java index 61c31fc51ef..60fe8c8eab8 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/redo/NamingGrpcRedoServiceTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/redo/NamingGrpcRedoServiceTest.java @@ -19,6 +19,7 @@ import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.client.env.NacosClientProperties; +import com.alibaba.nacos.client.naming.cache.NamingFuzzyWatchServiceListHolder; import com.alibaba.nacos.client.naming.remote.TestConnection; import com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy; import com.alibaba.nacos.client.naming.remote.gprc.redo.data.BatchInstanceRedoData; @@ -56,13 +57,16 @@ class NamingGrpcRedoServiceTest { @Mock private NamingGrpcClientProxy clientProxy; + @Mock + private NamingFuzzyWatchServiceListHolder namingFuzzyWatchServiceListHolder; + private NamingGrpcRedoService redoService; @BeforeEach void setUp() throws Exception { Properties prop = new Properties(); NacosClientProperties nacosClientProperties = NacosClientProperties.PROTOTYPE.derive(prop); - redoService = new NamingGrpcRedoService(clientProxy, nacosClientProperties); + redoService = new NamingGrpcRedoService(clientProxy, namingFuzzyWatchServiceListHolder, nacosClientProperties); ScheduledExecutorService redoExecutor = (ScheduledExecutorService) ReflectUtils.getFieldValue(redoService, "redoExecutor"); redoExecutor.shutdownNow(); @@ -95,7 +99,7 @@ void testCustomProperties() throws Exception { prop.setProperty(PropertyKeyConst.REDO_DELAY_THREAD_COUNT, "2"); NacosClientProperties nacosClientProperties = NacosClientProperties.PROTOTYPE.derive(prop); - NamingGrpcRedoService redoService = new NamingGrpcRedoService(clientProxy, nacosClientProperties); + NamingGrpcRedoService redoService = new NamingGrpcRedoService(clientProxy, namingFuzzyWatchServiceListHolder, nacosClientProperties); Field redoThreadCountField = NamingGrpcRedoService.class.getDeclaredField("redoThreadCount"); redoThreadCountField.setAccessible(true); diff --git a/common/src/main/java/com/alibaba/nacos/common/task/BatchTaskCounter.java b/common/src/main/java/com/alibaba/nacos/common/task/BatchTaskCounter.java new file mode 100644 index 00000000000..1d049d14cb0 --- /dev/null +++ b/common/src/main/java/com/alibaba/nacos/common/task/BatchTaskCounter.java @@ -0,0 +1,73 @@ +/* + * 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.common.task; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * batch task counter. + * + * @author shiyiyue + */ +public class BatchTaskCounter { + + List batchCounter; + + public BatchTaskCounter(int totalBatch) { + initBatchCounter(totalBatch); + } + + /** + * init counter. + * @param totalBatch totalBatch. + */ + private void initBatchCounter(int totalBatch) { + batchCounter = new ArrayList<>(totalBatch); + for (int i = 0; i < totalBatch; i++) { + batchCounter.add(i, new AtomicBoolean(false)); + } + } + + /** + * set bath succeed. + * @param batch succeed batch. + */ + public void batchSuccess(int batch) { + if (batch <= batchCounter.size()) { + batchCounter.get(batch - 1).set(true); + } + } + + /** + * check all completed. + * @return + */ + public boolean batchCompleted() { + for (AtomicBoolean atomicBoolean : batchCounter) { + if (!atomicBoolean.get()) { + return false; + } + } + return true; + } + + public int getTotalBatch() { + return batchCounter.size(); + } +} diff --git a/common/src/main/java/com/alibaba/nacos/common/utils/FuzzyGroupKeyPattern.java b/common/src/main/java/com/alibaba/nacos/common/utils/FuzzyGroupKeyPattern.java new file mode 100644 index 00000000000..fae7accbd90 --- /dev/null +++ b/common/src/main/java/com/alibaba/nacos/common/utils/FuzzyGroupKeyPattern.java @@ -0,0 +1,240 @@ +/* + * Copyright 1999-2023 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.common.utils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.alibaba.nacos.api.common.Constants.ALL_PATTERN; +import static com.alibaba.nacos.api.common.Constants.DEFAULT_NAMESPACE_ID; +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_PATTERN_SPLITTER; + +/** + * Utility class for matching group keys against a given pattern. + * + *

This class provides methods to match group keys based on a pattern specified. It supports matching based on + * dataId, group, and namespace components of the group key. + * + * @author stone-98 + * @date 2024/3/14 + */ +public class FuzzyGroupKeyPattern { + + /** + * Generates a fuzzy listen group key pattern based on the given dataId pattern, group, and optional tenant. pattern + * result as: fixNamespace>>groupPattern>>dataIdPattern + * + * @param resourcePattern The pattern for matching dataIds or service names. + * @param groupPattern The groupPattern associated with the groups. + * @param fixNamespace (Optional) The tenant associated with the dataIds (can be null or empty). + * @return A unique group key pattern for fuzzy listen. + * @throws IllegalArgumentException If the dataId pattern or group is blank. + */ + public static String generatePattern(final String resourcePattern, final String groupPattern, String fixNamespace) { + if (StringUtils.isBlank(resourcePattern)) { + throw new IllegalArgumentException("Param 'resourcePattern' is illegal, resourcePattern is blank"); + } + if (StringUtils.isBlank(groupPattern)) { + throw new IllegalArgumentException("Param 'groupPattern' is illegal, group is blank"); + } + if (StringUtils.isBlank(fixNamespace)) { + fixNamespace = DEFAULT_NAMESPACE_ID; + } + StringBuilder sb = new StringBuilder(); + sb.append(fixNamespace); + sb.append(FUZZY_WATCH_PATTERN_SPLITTER); + sb.append(groupPattern); + sb.append(FUZZY_WATCH_PATTERN_SPLITTER); + sb.append(resourcePattern); + return sb.toString().intern(); + } + + /** + * Given a dataId, group, and a collection of completed group key patterns, returns the patterns that match. + * + * @param resourceName The dataId or service name to match. + * @param group The group to match. + * @param namespace The group to match. + * @param groupKeyPatterns The collection of completed group key patterns to match against. + * @return A set of patterns that match the dataId and group. + */ + public static Set filterMatchedPatterns(Collection groupKeyPatterns, String resourceName, + String group, String namespace) { + if (CollectionUtils.isEmpty(groupKeyPatterns)) { + return new HashSet<>(1); + } + Set matchedPatternList = new HashSet<>(); + for (String keyPattern : groupKeyPatterns) { + if (matchPattern(keyPattern, resourceName, group, namespace)) { + matchedPatternList.add(keyPattern); + } + } + return matchedPatternList; + } + + /** + * check if the resource match the groupKeyPattern. + * @param resourceName The dataId or service name to match. + * @param group The group to match. + * @param namespace The group to match. + * @param groupKeyPattern The pattern to match. + * @return matched or not. + */ + public static boolean matchPattern(String groupKeyPattern, String resourceName, String group, String namespace) { + if (StringUtils.isBlank(namespace)) { + namespace = DEFAULT_NAMESPACE_ID; + } + String[] splitPatterns = groupKeyPattern.split(FUZZY_WATCH_PATTERN_SPLITTER); + return splitPatterns[0].equals(namespace) && itemMatched(splitPatterns[1], group) && itemMatched( + splitPatterns[2], resourceName); + } + + public static String getNamespaceFromPattern(String groupKeyPattern) { + return groupKeyPattern.split(FUZZY_WATCH_PATTERN_SPLITTER)[0]; + } + + /** + * check pattern matched the resource. + * @param pattern pattern contain *. + * @param resource resource to check. + * @return + */ + private static boolean itemMatched(String pattern, String resource) { + + //accurate match without * + if (!pattern.contains(ALL_PATTERN)) { + return pattern.equals(resource); + } + + //match for '*' pattern + if (pattern.equals(ALL_PATTERN)) { + return true; + } + + //match for *{string}* + if (pattern.startsWith(ALL_PATTERN) && pattern.endsWith(ALL_PATTERN)) { + String pureString = pattern.replace(ALL_PATTERN, ""); + return resource.contains(pureString); + } + + //match for postfix match *{string} + if (pattern.startsWith(ALL_PATTERN)) { + String pureString = pattern.replace(ALL_PATTERN, ""); + return resource.endsWith(pureString); + } + + //match for prefix match {string}* + if (pattern.endsWith(ALL_PATTERN)) { + String pureString = pattern.replace(ALL_PATTERN, ""); + return resource.startsWith(pureString); + } + + return false; + } + + /** + * Calculates and merges the differences between the matched group keys and the client's existing group keys into a + * list of ConfigState objects. + * + * @param basedGroupKeys The matched group keys set + * @param followedGroupKeys The followed existing group keys set + * @return a different list of GroupKeyState objects representing the states which the followed sets should be added + * or removed GroupKeyState#exist true presents follow set should add,GroupKeyState#exist false presents follow set + * should removed. + */ + public static List diffGroupKeys(Set basedGroupKeys, Set followedGroupKeys) { + // Calculate the set of group keys to be added and removed + Set addGroupKeys = new HashSet<>(); + if (CollectionUtils.isNotEmpty(basedGroupKeys)) { + addGroupKeys.addAll(basedGroupKeys); + } + if (CollectionUtils.isNotEmpty(followedGroupKeys)) { + addGroupKeys.removeAll(followedGroupKeys); + } + + Set removeGroupKeys = new HashSet<>(); + if (CollectionUtils.isNotEmpty(followedGroupKeys)) { + removeGroupKeys.addAll(followedGroupKeys); + } + if (CollectionUtils.isNotEmpty(basedGroupKeys)) { + removeGroupKeys.removeAll(basedGroupKeys); + } + + // Convert the group keys to be added and removed into corresponding ConfigState objects and merge them into a list + return Stream.concat(addGroupKeys.stream().map(groupKey -> new GroupKeyState(groupKey, true)), + removeGroupKeys.stream().map(groupKey -> new GroupKeyState(groupKey, false))) + .collect(Collectors.toList()); + } + + public static class GroupKeyState { + + String groupKey; + + boolean exist; + + /** + * Constructs a new ConfigState instance with the given group key and existence flag. + * + * @param groupKey The group key associated with the configuration. + * @param exist {@code true} if the configuration exists, {@code false} otherwise. + */ + public GroupKeyState(String groupKey, boolean exist) { + this.groupKey = groupKey; + this.exist = exist; + } + + /** + * Retrieves the group key associated with the configuration. + * + * @return The group key. + */ + public String getGroupKey() { + return groupKey; + } + + /** + * Sets the group key associated with the configuration. + * + * @param groupKey The group key to set. + */ + public void setGroupKey(String groupKey) { + this.groupKey = groupKey; + } + + /** + * Checks whether the configuration exists or not. + * + * @return {@code true} if the configuration exists, {@code false} otherwise. + */ + public boolean isExist() { + return exist; + } + + /** + * Sets the existence flag of the configuration. + * + * @param exist {@code true} if the configuration exists, {@code false} otherwise. + */ + public void setExist(boolean exist) { + this.exist = exist; + } + } +} diff --git a/common/src/main/java/com/alibaba/nacos/common/utils/GroupKey.java b/common/src/main/java/com/alibaba/nacos/common/utils/GroupKey.java deleted file mode 100644 index c821876ed55..00000000000 --- a/common/src/main/java/com/alibaba/nacos/common/utils/GroupKey.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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.common.utils; - -/** - * Synthesize the form of dataId+groupId. Escapes reserved characters in dataId and groupId. - * - * @author Nacos - */ -public class GroupKey { - - private static final char PLUS = '+'; - - private static final char PERCENT = '%'; - - private static final char TWO = '2'; - - private static final char B = 'B'; - - private static final char FIVE = '5'; - - public static String getKey(String dataId, String group) { - return getKey(dataId, group, ""); - } - - public static String getKey(String dataId, String group, String datumStr) { - return doGetKey(dataId, group, datumStr); - } - - public static String getKeyTenant(String dataId, String group, String tenant) { - return doGetKey(dataId, group, tenant); - } - - private static String doGetKey(String dataId, String group, String datumStr) { - if (StringUtils.isBlank(dataId)) { - throw new IllegalArgumentException("invalid dataId"); - } - if (StringUtils.isBlank(group)) { - throw new IllegalArgumentException("invalid group"); - } - StringBuilder sb = new StringBuilder(); - urlEncode(dataId, sb); - sb.append(PLUS); - urlEncode(group, sb); - if (StringUtils.isNotEmpty(datumStr)) { - sb.append(PLUS); - urlEncode(datumStr, sb); - } - - return sb.toString(); - } - - /** - * Parse key. - * - * @param groupKey group key - * @return parsed key - */ - public static String[] parseKey(String groupKey) { - StringBuilder sb = new StringBuilder(); - String dataId = null; - String group = null; - String tenant = null; - - for (int i = 0; i < groupKey.length(); ++i) { - char c = groupKey.charAt(i); - if (PLUS == c) { - if (null == dataId) { - dataId = sb.toString(); - sb.setLength(0); - } else if (null == group) { - group = sb.toString(); - sb.setLength(0); - } else { - throw new IllegalArgumentException("invalid groupkey:" + groupKey); - } - } else if (PERCENT == c) { - char next = groupKey.charAt(++i); - char nextnext = groupKey.charAt(++i); - if (TWO == next && B == nextnext) { - sb.append(PLUS); - } else if (TWO == next && FIVE == nextnext) { - sb.append(PERCENT); - } else { - throw new IllegalArgumentException("invalid groupkey:" + groupKey); - } - } else { - sb.append(c); - } - } - - if (group == null) { - group = sb.toString(); - } else { - tenant = sb.toString(); - } - - if (StringUtils.isBlank(dataId)) { - throw new IllegalArgumentException("invalid dataId"); - } - if (StringUtils.isBlank(group)) { - throw new IllegalArgumentException("invalid group"); - } - if (StringUtils.isBlank(tenant)) { - return new String[] {dataId, group}; - } - return new String[] {dataId, group, tenant}; - } - - /** - * + -> %2B % -> %25. - */ - static void urlEncode(String str, StringBuilder sb) { - for (int idx = 0; idx < str.length(); ++idx) { - char c = str.charAt(idx); - if (PLUS == c) { - sb.append("%2B"); - } else if (PERCENT == c) { - sb.append("%25"); - } else { - sb.append(c); - } - } - } - -} diff --git a/common/src/main/java/com/alibaba/nacos/common/utils/GroupKeyPattern.java b/common/src/main/java/com/alibaba/nacos/common/utils/GroupKeyPattern.java deleted file mode 100644 index f00d50e7db2..00000000000 --- a/common/src/main/java/com/alibaba/nacos/common/utils/GroupKeyPattern.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright 1999-2023 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.common.utils; - -import com.alibaba.nacos.api.common.Constants; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import static com.alibaba.nacos.api.common.Constants.DATA_ID_SPLITTER; -import static com.alibaba.nacos.api.common.Constants.FUZZY_LISTEN_PATTERN_WILDCARD; -import static com.alibaba.nacos.api.common.Constants.NAMESPACE_ID_SPLITTER; - -/** - * Utility class for matching group keys against a given pattern. - * - *

This class provides methods to match group keys based on a pattern specified. It supports matching based on - * dataId, group, and namespace components of the group key. - * - * @author stone-98 - * @date 2024/3/14 - */ -public class GroupKeyPattern { - - /** - * Generates a fuzzy listen group key pattern based on the given dataId pattern, group, and optional tenant. - * - *

This method generates a unique group key pattern for fuzzy listen based on the specified dataId pattern, - * group, and optional tenant. It concatenates the dataId pattern, group, and tenant (if provided) with a delimiter - * and returns the resulting string. The resulting string is interned to improve memory efficiency. - * - * @param dataIdPattern The pattern for matching dataIds. - * @param group The group associated with the dataIds. - * @param namespace (Optional) The tenant associated with the dataIds (can be null or empty). - * @return A unique group key pattern for fuzzy listen. - * @throws IllegalArgumentException If the dataId pattern or group is blank. - */ - public static String generateFuzzyListenGroupKeyPattern(final String dataIdPattern, final String group, - final String namespace) { - if (StringUtils.isBlank(dataIdPattern)) { - throw new IllegalArgumentException("Param 'dataIdPattern' is illegal, dataIdPattern is blank"); - } - if (StringUtils.isBlank(group)) { - throw new IllegalArgumentException("Param 'group' is illegal, group is blank"); - } - StringBuilder sb = new StringBuilder(); - if (StringUtils.isNotBlank(namespace)) { - sb.append(namespace); - } - sb.append(NAMESPACE_ID_SPLITTER); - sb.append(group); - sb.append(DATA_ID_SPLITTER); - sb.append(dataIdPattern); - return sb.toString().intern(); - } - - /** - * Generates a fuzzy listen group key pattern based on the given dataId pattern and group. - * - *

This method generates a unique group key pattern for fuzzy listen based on the specified dataId pattern and - * group. It concatenates the dataId pattern and group with a delimiter and returns the resulting string. The - * resulting string is interned to improve memory efficiency. - * - * @param dataIdPattern The pattern for matching dataIds. - * @param group The group associated with the dataIds. - * @return A unique group key pattern for fuzzy listen. - * @throws IllegalArgumentException If the dataId pattern or group is blank. - */ - public static String generateFuzzyListenGroupKeyPattern(final String dataIdPattern, final String group) { - if (StringUtils.isBlank(dataIdPattern)) { - throw new IllegalArgumentException("Param 'dataIdPattern' is illegal, dataIdPattern is blank"); - } - if (StringUtils.isBlank(group)) { - throw new IllegalArgumentException("Param 'group' is illegal, group is blank"); - } - final String fuzzyListenGroupKey = group + DATA_ID_SPLITTER + dataIdPattern; - return fuzzyListenGroupKey.intern(); - } - - /** - * Checks whether a group key matches the specified pattern. - * - * @param groupKey The group key to match. - * @param groupKeyPattern The pattern to match against. - * @return {@code true} if the group key matches the pattern, otherwise {@code false}. - */ - public static boolean isMatchPatternWithNamespace(String groupKey, String groupKeyPattern) { - String[] parseKey = GroupKey.parseKey(groupKey); - String dataId = parseKey[0]; - String group = parseKey[1]; - String namespace = parseKey.length > 2 ? parseKey[2] : Constants.DEFAULT_NAMESPACE_ID; - - String namespacePattern = getNamespace(groupKeyPattern); - String groupPattern = getGroup(groupKeyPattern); - String dataIdPattern = getDataIdPattern(groupKeyPattern); - - if (dataIdPattern.equals(FUZZY_LISTEN_PATTERN_WILDCARD)) { - return namespace.equals(namespacePattern) && group.equals(groupPattern); - } - - if (dataIdPattern.endsWith(FUZZY_LISTEN_PATTERN_WILDCARD)) { - String dataIdPrefix = dataIdPattern.substring(0, dataIdPattern.length() - 1); - return namespace.equals(namespacePattern) && groupPattern.equals(group) && dataId.startsWith(dataIdPrefix); - } - - return namespace.equals(namespacePattern) && group.equals(groupPattern) && dataId.equals(dataIdPattern); - } - - /** - * Checks whether a group key matches the specified pattern. - * - * @param groupKey The group key to match. - * @param groupKeyPattern The pattern to match against. - * @return {@code true} if the group key matches the pattern, otherwise {@code false}. - */ - public static boolean isMatchPatternWithoutNamespace(String groupKey, String groupKeyPattern) { - String[] parseKey = GroupKey.parseKey(groupKey); - String dataId = parseKey[0]; - String group = parseKey[1]; - - String groupPattern = getGroup(groupKeyPattern); - String dataIdPattern = getDataIdPattern(groupKeyPattern); - - if (dataIdPattern.equals(FUZZY_LISTEN_PATTERN_WILDCARD)) { - return group.equals(groupPattern); - } - - if (dataIdPattern.endsWith(FUZZY_LISTEN_PATTERN_WILDCARD)) { - String dataIdPrefix = dataIdPattern.substring(0, dataIdPattern.length() - 1); - return groupPattern.equals(group) && dataId.startsWith(dataIdPrefix); - } - - return group.equals(groupPattern) && dataId.equals(dataIdPattern); - } - - /** - * Given a dataId, group, dataId pattern, and group pattern, determines whether it can match. - * - * @param dataId The dataId to match. - * @param group The group to match. - * @param dataIdPattern The dataId pattern to match against. - * @param groupPattern The group pattern to match against. - * @return {@code true} if the dataId and group match the patterns, otherwise {@code false}. - */ - public static boolean isMatchPatternWithoutNamespace(String dataId, String group, String dataIdPattern, - String groupPattern) { - String groupKey = GroupKey.getKey(dataId, group); - String groupKeyPattern = generateFuzzyListenGroupKeyPattern(dataIdPattern, groupPattern); - return isMatchPatternWithoutNamespace(groupKey, groupKeyPattern); - } - - /** - * Given a dataId, group, and a collection of completed group key patterns, returns the patterns that match. - * - * @param dataId The dataId to match. - * @param group The group to match. - * @param groupKeyPatterns The collection of completed group key patterns to match against. - * @return A set of patterns that match the dataId and group. - */ - public static Set getConfigMatchedPatternsWithoutNamespace(String dataId, String group, - Collection groupKeyPatterns) { - if (CollectionUtils.isEmpty(groupKeyPatterns)) { - return new HashSet<>(1); - } - Set matchedPatternList = new HashSet<>(); - for (String keyPattern : groupKeyPatterns) { - if (isMatchPatternWithoutNamespace(dataId, group, getDataIdPattern(keyPattern), getGroup(keyPattern))) { - matchedPatternList.add(keyPattern); - } - } - return matchedPatternList; - } - - /** - * Extracts the namespace from the given group key pattern. - * - * @param groupKeyPattern The group key pattern from which to extract the namespace. - * @return The namespace extracted from the group key pattern. - */ - public static String getNamespace(final String groupKeyPattern) { - if (StringUtils.isBlank(groupKeyPattern)) { - return StringUtils.EMPTY; - } - if (!groupKeyPattern.contains(NAMESPACE_ID_SPLITTER)) { - return StringUtils.EMPTY; - } - return groupKeyPattern.split(NAMESPACE_ID_SPLITTER)[0]; - } - - /** - * Extracts the group from the given group key pattern. - * - * @param groupKeyPattern The group key pattern from which to extract the group. - * @return The group extracted from the group key pattern. - */ - public static String getGroup(final String groupKeyPattern) { - if (StringUtils.isBlank(groupKeyPattern)) { - return StringUtils.EMPTY; - } - String groupWithNamespace; - if (!groupKeyPattern.contains(DATA_ID_SPLITTER)) { - groupWithNamespace = groupKeyPattern; - } else { - groupWithNamespace = groupKeyPattern.split(DATA_ID_SPLITTER)[0]; - } - - if (!groupKeyPattern.contains(NAMESPACE_ID_SPLITTER)) { - return groupWithNamespace; - } - return groupWithNamespace.split(NAMESPACE_ID_SPLITTER)[1]; - } - - /** - * Extracts the dataId pattern from the given group key pattern. - * - * @param groupKeyPattern The group key pattern from which to extract the dataId pattern. - * @return The dataId pattern extracted from the group key pattern. - */ - public static String getDataIdPattern(final String groupKeyPattern) { - if (StringUtils.isBlank(groupKeyPattern)) { - return StringUtils.EMPTY; - } - if (!groupKeyPattern.contains(DATA_ID_SPLITTER)) { - return StringUtils.EMPTY; - } - return groupKeyPattern.split(DATA_ID_SPLITTER)[1]; - } - - /** - * Given a completed pattern, removes the namespace. - * - * @param completedPattern The completed pattern from which to remove the namespace. - * @return The pattern with the namespace removed. - */ - public static String getPatternRemovedNamespace(String completedPattern) { - if (StringUtils.isBlank(completedPattern)) { - return StringUtils.EMPTY; - } - if (!completedPattern.contains(NAMESPACE_ID_SPLITTER)) { - return completedPattern; - } - return completedPattern.split(NAMESPACE_ID_SPLITTER)[1]; - } -} diff --git a/common/src/test/java/com/alibaba/nacos/common/utils/FuzzyGroupKeyPatternTest.java b/common/src/test/java/com/alibaba/nacos/common/utils/FuzzyGroupKeyPatternTest.java new file mode 100644 index 00000000000..374d429eac0 --- /dev/null +++ b/common/src/test/java/com/alibaba/nacos/common/utils/FuzzyGroupKeyPatternTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2023 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.common.utils; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * GroupKeyPatternUtilsTest. + * + * @author stone-98 + * @date 2024/3/19 + */ +public class FuzzyGroupKeyPatternTest { + + @Test + public void testGetGroupKeyPattern() { + String dataIdPattern = "examplePattern*"; + String group = "exampleGroup"; + String namespace = "exampleNamespace"; + + String groupKeyPattern = FuzzyGroupKeyPattern.generatePattern(dataIdPattern, group, namespace); + + assertEquals("exampleNamespace>>exampleGroup@@examplePattern*", groupKeyPattern); + } + +} + diff --git a/common/src/test/java/com/alibaba/nacos/common/utils/GroupKeyPatternTest.java b/common/src/test/java/com/alibaba/nacos/common/utils/GroupKeyPatternTest.java deleted file mode 100644 index a16e222ecbc..00000000000 --- a/common/src/test/java/com/alibaba/nacos/common/utils/GroupKeyPatternTest.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 1999-2023 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.common.utils; - -import org.junit.Assert; -import org.junit.Test; - -import java.util.HashSet; -import java.util.Set; - -/** - * GroupKeyPatternUtilsTest. - * - * @author stone-98 - * @date 2024/3/19 - */ -public class GroupKeyPatternTest { - - @Test - public void testGetGroupKeyPatternWithNamespace() { - String dataIdPattern = "examplePattern*"; - String group = "exampleGroup"; - String namespace = "exampleNamespace"; - - String groupKeyPattern = GroupKeyPattern.generateFuzzyListenGroupKeyPattern(dataIdPattern, group, namespace); - - Assert.assertEquals("exampleNamespace>>exampleGroup@@examplePattern*", groupKeyPattern); - } - - @Test - public void testGetGroupKeyPatternWithoutNamespace() { - String dataIdPattern = "examplePattern*"; - String group = "exampleGroup"; - - String groupKeyPattern = GroupKeyPattern.generateFuzzyListenGroupKeyPattern(dataIdPattern, group); - - Assert.assertEquals("exampleGroup@@examplePattern*", groupKeyPattern); - } - - @Test - public void testIsMatchPatternWithNamespace() { - String groupKey = "examplePattern+exampleGroup+exampleNamespace"; - String groupKeyPattern = "exampleNamespace>>exampleGroup@@examplePattern*"; - - boolean result = GroupKeyPattern.isMatchPatternWithNamespace(groupKey, groupKeyPattern); - - Assert.assertTrue(result); - } - - @Test - public void testIsMatchPatternWithoutNamespace() { - String groupKey = "examplePattern+exampleGroup+exampleNamespace"; - String groupKeyPattern = "exampleNamespace>>exampleGroup@@*"; - - boolean result = GroupKeyPattern.isMatchPatternWithoutNamespace(groupKey, groupKeyPattern); - - Assert.assertTrue(result); - } - - @Test - public void testIsMatchPatternWithoutNamespaceWithDataIdPrefix() { - String groupKey = "examplePattern+exampleGroup+exampleNamespace"; - String groupKeyPattern = "exampleNamespace>>exampleGroup@@examplePattern*"; - - boolean result = GroupKeyPattern.isMatchPatternWithoutNamespace(groupKey, groupKeyPattern); - - Assert.assertTrue(result); - } - - @Test - public void testGetConfigMatchedPatternsWithoutNamespace() { - String dataId = "exampleDataId"; - String group = "exampleGroup"; - Set groupKeyPatterns = new HashSet<>(); - groupKeyPatterns.add("exampleGroup@@exampleDataId*"); - groupKeyPatterns.add("exampleGroup@@exampleDataI*"); - - Set matchedPatterns = GroupKeyPattern.getConfigMatchedPatternsWithoutNamespace(dataId, group, - groupKeyPatterns); - - Assert.assertEquals(2, matchedPatterns.size()); - Assert.assertTrue(matchedPatterns.contains("exampleGroup@@exampleDataId*")); - Assert.assertTrue(matchedPatterns.contains("exampleGroup@@exampleDataI*")); - } - - @Test - public void testGetNamespace() { - String groupKeyPattern = "exampleNamespace>>exampleGroup@@examplePattern"; - - String namespace = GroupKeyPattern.getNamespace(groupKeyPattern); - - Assert.assertEquals("exampleNamespace", namespace); - } - - @Test - public void testGetGroup() { - String groupKeyPattern = "exampleNamespace>>exampleGroup@@examplePattern"; - - String group = GroupKeyPattern.getGroup(groupKeyPattern); - - Assert.assertEquals("exampleGroup", group); - } - - @Test - public void testGetDataIdPattern() { - String groupKeyPattern = "exampleNamespace>>exampleGroup@@examplePattern"; - - String dataIdPattern = GroupKeyPattern.getDataIdPattern(groupKeyPattern); - - Assert.assertEquals("examplePattern", dataIdPattern); - } - - @Test - public void testGetPatternRemovedNamespace() { - String groupKeyPattern = "exampleNamespace>>exampleGroup@@examplePattern"; - - String patternRemovedNamespace = GroupKeyPattern.getPatternRemovedNamespace(groupKeyPattern); - - Assert.assertEquals("exampleGroup@@examplePattern", patternRemovedNamespace); - } -} - diff --git a/config/pom.xml b/config/pom.xml index c10e7df3796..521f7d1b515 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -134,6 +134,10 @@ hamcrest test + + com.alibaba.nacos + nacos-control-plugin + diff --git a/config/src/main/java/com/alibaba/nacos/config/server/configuration/ConfigCommonConfig.java b/config/src/main/java/com/alibaba/nacos/config/server/configuration/ConfigCommonConfig.java index 722e5818493..b6af4a6f805 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/configuration/ConfigCommonConfig.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/configuration/ConfigCommonConfig.java @@ -35,7 +35,8 @@ public class ConfigCommonConfig extends AbstractDynamicConfig { private long pushTimeout = 3000L; - private int batchSize = 10; + private int batchSize = 20; + private boolean derbyOpsEnabled = false; private ConfigCommonConfig() { @@ -69,6 +70,8 @@ public int getBatchSize() { public void setBatchSize(int batchSize) { this.batchSize = batchSize; + } + public boolean isDerbyOpsEnabled() { return derbyOpsEnabled; } @@ -81,7 +84,7 @@ public void setDerbyOpsEnabled(boolean derbyOpsEnabled) { protected void getConfigFromEnv() { maxPushRetryTimes = EnvUtil.getProperty("nacos.config.push.maxRetryTime", Integer.class, 50); pushTimeout = EnvUtil.getProperty("nacos.config.push.timeout", Long.class, 3000L); - pushTimeout = EnvUtil.getProperty("nacos.config.push.batchSize", Integer.class, 10); + batchSize = EnvUtil.getProperty("nacos.config.push.batchSize", Integer.class, 20); derbyOpsEnabled = EnvUtil.getProperty("nacos.config.derby.ops.enabled", Boolean.class, false); } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/CacheItem.java b/config/src/main/java/com/alibaba/nacos/config/server/model/CacheItem.java index 836713a3be4..e0423f3daa7 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/model/CacheItem.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/CacheItem.java @@ -16,8 +16,6 @@ package com.alibaba.nacos.config.server.model; -import com.alibaba.nacos.common.utils.CollectionUtils; -import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.utils.SimpleReadWriteLock; import com.alibaba.nacos.core.utils.StringPool; @@ -129,19 +127,5 @@ public void clearConfigGrays() { this.configCacheGray = null; this.sortedConfigCacheGrayList = null; } - - /** - * Checks if the configuration is effective for the specified client IP and tag. - * - * @param tag The tag associated with the configuration. - * @param clientIp The IP address of the client. - * @return true if the configuration is effective for the client, false otherwise. - */ - public boolean effectiveForClient(String tag, String clientIp) { - if (isBeta && CollectionUtils.isNotEmpty(ips4Beta) && !ips4Beta.contains(clientIp)) { - return false; - } - return StringUtils.isBlank(tag) || (getConfigCacheTags() != null && getConfigCacheTags().containsKey(tag)); - } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigCancelFuzzyWatchEvent.java b/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigCancelFuzzyWatchEvent.java new file mode 100644 index 00000000000..77fbace8cc4 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigCancelFuzzyWatchEvent.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2023 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.config.server.model.event; + +import com.alibaba.nacos.common.notify.Event; + +/** + * This event represents a batch fuzzy listening event for configurations. It is used to notify the server about a batch + * of fuzzy listening requests from clients. Each request contains a client ID, a set of existing group keys associated + * with the client, a key group pattern, and a flag indicating whether the client is initializing. + * + * @author stone-98 + * @date 2024/3/5 + */ +public class ConfigCancelFuzzyWatchEvent extends Event { + + private static final long serialVersionUID = 1953965691384930209L; + + /** + * ID of the client making the request. + */ + private String connectionId; + + /** + * Pattern for matching group keys. + */ + private String groupKeyPattern; + + /** + * Constructs a new ConfigBatchFuzzyListenEvent with the specified parameters. + * + * @param connectionId ID of the client making the request + * @param groupKeyPattern Pattern for matching group keys + */ + public ConfigCancelFuzzyWatchEvent(String connectionId, String groupKeyPattern) { + this.connectionId = connectionId; + this.groupKeyPattern = groupKeyPattern; + } + + /** + * Get the ID of the client making the request. + * + * @return The client ID + */ + public String getConnectionId() { + return connectionId; + } + + public String getGroupKeyPattern() { + return groupKeyPattern; + } + + /** + * Set the ID of the client making the request. + * + * @param connectionId The client ID to be set + */ + public void setConnectionId(String connectionId) { + this.connectionId = connectionId; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigBatchFuzzyListenEvent.java b/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigFuzzyWatchEvent.java similarity index 79% rename from config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigBatchFuzzyListenEvent.java rename to config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigFuzzyWatchEvent.java index 2e44763ee9e..c6a75210c50 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigBatchFuzzyListenEvent.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigFuzzyWatchEvent.java @@ -28,19 +28,19 @@ * @author stone-98 * @date 2024/3/5 */ -public class ConfigBatchFuzzyListenEvent extends Event { +public class ConfigFuzzyWatchEvent extends Event { private static final long serialVersionUID = 1953965691384930209L; /** * ID of the client making the request. */ - private String clientId; + private String connectionId; /** * Pattern for matching group keys. */ - private String keyGroupPattern; + private String groupKeyPattern; /** * Set of existing group keys associated with the client. @@ -55,16 +55,16 @@ public class ConfigBatchFuzzyListenEvent extends Event { /** * Constructs a new ConfigBatchFuzzyListenEvent with the specified parameters. * - * @param clientId ID of the client making the request + * @param connectionId ID of the client making the request * @param clientExistingGroupKeys Set of existing group keys associated with the client - * @param keyGroupPattern Pattern for matching group keys + * @param groupKeyPattern Pattern for matching group keys * @param isInitializing Flag indicating whether the client is initializing */ - public ConfigBatchFuzzyListenEvent(String clientId, Set clientExistingGroupKeys, String keyGroupPattern, + public ConfigFuzzyWatchEvent(String connectionId, Set clientExistingGroupKeys, String groupKeyPattern, boolean isInitializing) { - this.clientId = clientId; + this.connectionId = connectionId; this.clientExistingGroupKeys = clientExistingGroupKeys; - this.keyGroupPattern = keyGroupPattern; + this.groupKeyPattern = groupKeyPattern; this.isInitializing = isInitializing; } @@ -73,17 +73,17 @@ public ConfigBatchFuzzyListenEvent(String clientId, Set clientExistingGr * * @return The client ID */ - public String getClientId() { - return clientId; + public String getConnectionId() { + return connectionId; } /** * Set the ID of the client making the request. * - * @param clientId The client ID to be set + * @param connectionId The client ID to be set */ - public void setClientId(String clientId) { - this.clientId = clientId; + public void setConnectionId(String connectionId) { + this.connectionId = connectionId; } /** @@ -91,17 +91,17 @@ public void setClientId(String clientId) { * * @return The key group pattern */ - public String getKeyGroupPattern() { - return keyGroupPattern; + public String getGroupKeyPattern() { + return groupKeyPattern; } /** * Set the pattern for matching group keys. * - * @param keyGroupPattern The key group pattern to be set + * @param groupKeyPattern The key group pattern to be set */ - public void setKeyGroupPattern(String keyGroupPattern) { - this.keyGroupPattern = keyGroupPattern; + public void setGroupKeyPattern(String groupKeyPattern) { + this.groupKeyPattern = groupKeyPattern; } /** diff --git a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigBatchFuzzyListenRequestHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigBatchFuzzyListenRequestHandler.java deleted file mode 100644 index 0a344ba2123..00000000000 --- a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigBatchFuzzyListenRequestHandler.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 1999-2023 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.config.server.remote; - -import com.alibaba.nacos.api.config.remote.request.ConfigBatchFuzzyListenRequest; -import com.alibaba.nacos.api.config.remote.response.ConfigBatchFuzzyListenResponse; -import com.alibaba.nacos.api.exception.NacosException; -import com.alibaba.nacos.api.remote.request.RequestMeta; -import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.common.notify.NotifyCenter; -import com.alibaba.nacos.common.utils.CollectionUtils; -import com.alibaba.nacos.common.utils.GroupKeyPattern; -import com.alibaba.nacos.config.server.model.event.ConfigBatchFuzzyListenEvent; -import com.alibaba.nacos.config.server.utils.GroupKey; -import com.alibaba.nacos.core.control.TpsControl; -import com.alibaba.nacos.core.paramcheck.ExtractorManager; -import com.alibaba.nacos.core.paramcheck.impl.ConfigBatchFuzzyListenRequestParamsExtractor; -import com.alibaba.nacos.core.remote.RequestHandler; -import com.alibaba.nacos.core.utils.StringPool; -import com.alibaba.nacos.plugin.auth.constant.ActionTypes; -import com.alibaba.nacos.plugin.auth.constant.SignType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Handler for processing batch fuzzy listen requests. - *

- * This handler is responsible for processing batch fuzzy listen requests sent by clients. It adds or removes clients - * from the fuzzy listening context based on the request, and publishes corresponding events to notify interested - * parties. - *

- * - * @author stone-98 - * @date 2024/3/4 - */ -@Component -public class ConfigBatchFuzzyListenRequestHandler - extends RequestHandler { - - /** - * Context for managing fuzzy listen changes. - */ - @Autowired - private ConfigChangeListenContext configChangeListenContext; - - /** - * Handles the batch fuzzy listen request. - *

- * This method processes the batch fuzzy listen request by adding or removing clients from the fuzzy listening - * context based on the request, and publishes corresponding events to notify interested parties. - *

- * - * @param request The batch fuzzy listen request - * @param meta Request meta information - * @return The response to the batch fuzzy listen request - * @throws NacosException If an error occurs while processing the request - */ - @Override - @TpsControl(pointName = "ConfigFuzzyListen") - @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) - @ExtractorManager.Extractor(rpcExtractor = ConfigBatchFuzzyListenRequestParamsExtractor.class) - public ConfigBatchFuzzyListenResponse handle(ConfigBatchFuzzyListenRequest request, RequestMeta meta) - throws NacosException { - String connectionId = StringPool.get(meta.getConnectionId()); - for (ConfigBatchFuzzyListenRequest.Context context : request.getContexts()) { - String groupKeyPattern = GroupKeyPattern.generateFuzzyListenGroupKeyPattern(context.getDataIdPattern(), - context.getGroup(), context.getTenant()); - groupKeyPattern = StringPool.get(groupKeyPattern); - if (context.isListen()) { - // Add client to the fuzzy listening context - configChangeListenContext.addFuzzyListen(groupKeyPattern, connectionId); - // Get existing group keys for the client and publish initialization event - Set clientExistingGroupKeys = null; - if (CollectionUtils.isNotEmpty(context.getDataIds())) { - clientExistingGroupKeys = context.getDataIds().stream() - .map(dataId -> GroupKey.getKeyTenant(dataId, context.getGroup(), context.getTenant())) - .collect(Collectors.toSet()); - } - NotifyCenter.publishEvent( - new ConfigBatchFuzzyListenEvent(connectionId, clientExistingGroupKeys, groupKeyPattern, - context.isInitializing())); - } else { - // Remove client from the fuzzy listening context - configChangeListenContext.removeFuzzyListen(groupKeyPattern, connectionId); - } - } - // Return response - return new ConfigBatchFuzzyListenResponse(); - } -} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeListenContext.java b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeListenContext.java index c71ef288f72..adf7453558a 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeListenContext.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeListenContext.java @@ -17,7 +17,6 @@ package com.alibaba.nacos.config.server.remote; import com.alibaba.nacos.common.utils.CollectionUtils; -import com.alibaba.nacos.common.utils.GroupKeyPattern; import org.springframework.stereotype.Component; import java.util.Collection; @@ -37,100 +36,18 @@ @Component public class ConfigChangeListenContext { - /** - * groupKeyPattern -> connection set. - */ - private final Map> keyPatternContext = new ConcurrentHashMap<>(); - /** * groupKey-> connection set. */ - private final ConcurrentHashMap> groupKeyContext = new ConcurrentHashMap<>(); + private ConcurrentHashMap> groupKeyContext = new ConcurrentHashMap<>(); /** * connectionId-> group key set. */ - private final ConcurrentHashMap> connectionIdContext = new ConcurrentHashMap<>(); + private ConcurrentHashMap> connectionIdContext = new ConcurrentHashMap<>(); /** - * Adds a fuzzy listen connection ID associated with the specified group key pattern. If the key pattern does not - * exist in the context, a new entry will be created. If the key pattern already exists, the connection ID will be - * added to the existing set. - * - * @param groupKeyPattern The group key pattern to associate with the listen connection. - * @param connectId The connection ID to be added. - */ - public synchronized void addFuzzyListen(String groupKeyPattern, String connectId) { - // Add the connection ID to the set associated with the key pattern in keyPatternContext - keyPatternContext.computeIfAbsent(groupKeyPattern, k -> new HashSet<>()).add(connectId); - } - - /** - * Removes a fuzzy listen connection ID associated with the specified group key pattern. If the group key pattern - * exists in the context and the connection ID is found in the associated set, the connection ID will be removed - * from the set. If the set becomes empty after removal, the entry for the group key pattern will be removed from - * the context. - * - * @param groupKeyPattern The group key pattern associated with the listen connection to be removed. - * @param connectionId The connection ID to be removed. - */ - public synchronized void removeFuzzyListen(String groupKeyPattern, String connectionId) { - // Retrieve the set of connection IDs associated with the group key pattern - Set connectIds = keyPatternContext.get(groupKeyPattern); - if (CollectionUtils.isNotEmpty(connectIds)) { - // Remove the connection ID from the set if it exists - connectIds.remove(connectionId); - // Remove the entry for the group key pattern if the set becomes empty after removal - if (connectIds.isEmpty()) { - keyPatternContext.remove(groupKeyPattern); - } - } - } - - /** - * Retrieves the set of fuzzy listen connection IDs associated with the specified group key pattern. - * - * @param groupKeyPattern The group key pattern to retrieve the associated connection IDs. - * @return The set of connection IDs associated with the group key pattern, or null if no connections are found. - */ - public synchronized Set getFuzzyListeners(String groupKeyPattern) { - // Retrieve the set of connection IDs associated with the group key pattern - Set connectionIds = keyPatternContext.get(groupKeyPattern); - // If the set is not empty, create a new set and safely copy the connection IDs into it - if (CollectionUtils.isNotEmpty(connectionIds)) { - Set listenConnections = new HashSet<>(); - safeCopy(connectionIds, listenConnections); - return listenConnections; - } - // Return null if no connections are found for the specified group key pattern - return null; - } - - /** - * Retrieves the set of connection IDs matched with the specified group key. - * - * @param groupKey The group key to match with the key patterns. - * @return The set of connection IDs matched with the group key. - */ - public Set getConnectIdMatchedPatterns(String groupKey) { - // Initialize a set to store the matched connection IDs - Set connectIds = new HashSet<>(); - // Iterate over each key pattern in the context - for (String keyPattern : keyPatternContext.keySet()) { - // Check if the group key matches the current key pattern - if (GroupKeyPattern.isMatchPatternWithNamespace(groupKey, keyPattern)) { - // If matched, add the associated connection IDs to the set - Set connectIdSet = keyPatternContext.get(keyPattern); - if (CollectionUtils.isNotEmpty(connectIdSet)) { - connectIds.addAll(connectIdSet); - } - } - } - return connectIds; - } - - /** - * Add listen. + * add listen. * * @param groupKey groupKey. * @param connectionId connectionId. @@ -210,7 +127,7 @@ public synchronized void clearContextForConnectionId(final String connectionId) return; } for (Map.Entry groupKey : listenKeys.entrySet()) { - + Set connectionIds = groupKeyContext.get(groupKey.getKey()); if (CollectionUtils.isNotEmpty(connectionIds)) { connectionIds.remove(connectionId); @@ -220,23 +137,9 @@ public synchronized void clearContextForConnectionId(final String connectionId) } else { groupKeyContext.remove(groupKey.getKey()); } - + } connectionIdContext.remove(connectionId); - - // Remove any remaining fuzzy listen connections - for (Map.Entry> keyPatternContextEntry : keyPatternContext.entrySet()) { - String keyPattern = keyPatternContextEntry.getKey(); - Set connectionIds = keyPatternContextEntry.getValue(); - if (CollectionUtils.isEmpty(connectionIds)) { - keyPatternContext.remove(keyPattern); - } else { - connectionIds.remove(keyPattern); - if (CollectionUtils.isEmpty(connectionIds)) { - keyPatternContext.remove(keyPattern); - } - } - } } /** diff --git a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigConnectionEventListener.java b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigConnectionEventListener.java index bd9ce128f0b..a007b1c463b 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigConnectionEventListener.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigConnectionEventListener.java @@ -16,6 +16,7 @@ package com.alibaba.nacos.config.server.remote; +import com.alibaba.nacos.config.server.service.ConfigFuzzyWatchContextService; import com.alibaba.nacos.core.remote.ClientConnectionEventListener; import com.alibaba.nacos.core.remote.Connection; import com.alibaba.nacos.core.utils.Loggers; @@ -32,8 +33,12 @@ public class ConfigConnectionEventListener extends ClientConnectionEventListener final ConfigChangeListenContext configChangeListenContext; - public ConfigConnectionEventListener(ConfigChangeListenContext configChangeListenContext) { + final ConfigFuzzyWatchContextService configFuzzyWatchContextService; + + public ConfigConnectionEventListener(ConfigChangeListenContext configChangeListenContext, + ConfigFuzzyWatchContextService configFuzzyWatchContextService) { this.configChangeListenContext = configChangeListenContext; + this.configFuzzyWatchContextService = configFuzzyWatchContextService; } @Override @@ -46,6 +51,7 @@ public void clientDisConnected(Connection connect) { String connectionId = connect.getMetaInfo().getConnectionId(); Loggers.REMOTE_DIGEST.info("[{}]client disconnected,clear config listen context", connectionId); configChangeListenContext.clearContextForConnectionId(connectionId); + configFuzzyWatchContextService.clearFuzzyWatchContext(connectionId); } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/remote/RpcFuzzyListenConfigChangeNotifier.java b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigFuzzyWatchChangeNotifier.java similarity index 63% rename from config/src/main/java/com/alibaba/nacos/config/server/remote/RpcFuzzyListenConfigChangeNotifier.java rename to config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigFuzzyWatchChangeNotifier.java index 3724a0eaf69..7478e7616c5 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/remote/RpcFuzzyListenConfigChangeNotifier.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigFuzzyWatchChangeNotifier.java @@ -16,7 +16,7 @@ package com.alibaba.nacos.config.server.remote; -import com.alibaba.nacos.api.config.remote.request.FuzzyListenNotifyChangeRequest; +import com.alibaba.nacos.api.config.remote.request.ConfigFuzzyWatchChangeNotifyRequest; import com.alibaba.nacos.api.remote.AbstractPushCallBack; import com.alibaba.nacos.common.notify.Event; import com.alibaba.nacos.common.notify.NotifyCenter; @@ -24,8 +24,8 @@ import com.alibaba.nacos.config.server.configuration.ConfigCommonConfig; import com.alibaba.nacos.config.server.model.event.LocalDataChangeEvent; import com.alibaba.nacos.config.server.service.ConfigCacheService; +import com.alibaba.nacos.config.server.service.ConfigFuzzyWatchContextService; import com.alibaba.nacos.config.server.utils.ConfigExecutor; -import com.alibaba.nacos.config.server.utils.GroupKey; import com.alibaba.nacos.core.remote.Connection; import com.alibaba.nacos.core.remote.ConnectionManager; import com.alibaba.nacos.core.remote.ConnectionMeta; @@ -38,6 +38,9 @@ import java.util.concurrent.TimeUnit; +import static com.alibaba.nacos.api.common.Constants.ConfigChangedType.CONFIG_CHANGED; +import static com.alibaba.nacos.api.common.Constants.ConfigChangedType.DELETE_CONFIG; + /** * Notify remote clients about fuzzy listen configuration changes. Use subscriber mode to monitor local data changes, * and push notifications to remote clients accordingly. @@ -45,16 +48,14 @@ * @author stone-98 * @date 2024/3/18 */ -@Component(value = "rpcFuzzyListenConfigChangeNotifier") -public class RpcFuzzyListenConfigChangeNotifier extends Subscriber { - - private static final String POINT_FUZZY_LISTEN_CONFIG_PUSH = "POINT_FUZZY_LISTEN_CONFIG_PUSH"; +@Component(value = "fuzzyWatchConfigChangeNotifier") +public class ConfigFuzzyWatchChangeNotifier extends Subscriber { - private static final String POINT_FUZZY_LISTEN_CONFIG_PUSH_SUCCESS = "POINT_FUZZY_LISTEN_CONFIG_PUSH_SUCCESS"; + private static final String POINT_FUZZY_WATCH_CONFIG_PUSH = "POINT_FUZZY_WATCH_CONFIG_PUSH"; - private static final String POINT_FUZZY_LISTEN_CONFIG_PUSH_FAIL = "POINT_FUZZY_LISTEN_CONFIG_PUSH_FAIL"; + private static final String POINT_FUZZY_WATCH_CONFIG_PUSH_SUCCESS = "POINT_FUZZY_WATCH_CONFIG_PUSH_SUCCESS"; - private final ConfigChangeListenContext configChangeListenContext; + private static final String POINT_FUZZY_WATCH_CONFIG_PUSH_FAIL = "POINT_FUZZY_WATCH_CONFIG_PUSH_FAIL"; private final ConnectionManager connectionManager; @@ -62,48 +63,51 @@ public class RpcFuzzyListenConfigChangeNotifier extends Subscriber 2 ? parseKey[2] : ""; - for (String clientId : configChangeListenContext.getConnectIdMatchedPatterns(groupKey)) { - Connection connection = connectionManager.getConnection(clientId); - if (null == connection) { - Loggers.REMOTE_PUSH.warn( - "clientId not found, Config change notification not sent. clientId={},keyGroupPattern={}", - clientId, event.groupKey); - continue; + boolean exists = ConfigCacheService.getContentCache(event.groupKey) != null; + //can not recognize add or update, set config_changed here + String changedType = exists ? CONFIG_CHANGED : DELETE_CONFIG; + boolean needNotify = configFuzzyWatchContextService.syncGroupKeyContext(event.groupKey, changedType); + if (needNotify) { + for (String clientId : configFuzzyWatchContextService.getMatchedClients(event.groupKey)) { + Connection connection = connectionManager.getConnection(clientId); + if (null == connection) { + Loggers.REMOTE_PUSH.warn( + "clientId not found, Config change notification not sent. clientId={},keyGroupPattern={}", + clientId, event.groupKey); + continue; + } + ConnectionMeta metaInfo = connection.getMetaInfo(); + String clientIp = metaInfo.getClientIp(); + String appName = metaInfo.getAppName(); + + ConfigFuzzyWatchChangeNotifyRequest request = new ConfigFuzzyWatchChangeNotifyRequest(event.groupKey, + changedType); + int maxPushRetryTimes = ConfigCommonConfig.getInstance().getMaxPushRetryTimes(); + RpcPushTask rpcPushTask = new RpcPushTask(request, maxPushRetryTimes, clientId, clientIp, appName); + push(rpcPushTask); } - ConnectionMeta metaInfo = connection.getMetaInfo(); - String clientIp = metaInfo.getClientIp(); - String clientTag = metaInfo.getTag(); - String appName = metaInfo.getAppName(); - boolean exists = ConfigCacheService.containsAndEffectiveForClient(groupKey, clientIp, clientTag); - FuzzyListenNotifyChangeRequest request = new FuzzyListenNotifyChangeRequest(tenant, group, dataId, exists); - int maxPushRetryTimes = ConfigCommonConfig.getInstance().getMaxPushRetryTimes(); - RpcPushTask rpcPushTask = new RpcPushTask(request, maxPushRetryTimes, clientId, clientIp, appName); - push(rpcPushTask); } + } @Override @@ -117,12 +121,11 @@ public Class subscribeType() { * @param retryTask The task for retrying to push notification. */ private void push(RpcPushTask retryTask) { - FuzzyListenNotifyChangeRequest notifyRequest = retryTask.notifyRequest; + ConfigFuzzyWatchChangeNotifyRequest notifyRequest = retryTask.notifyRequest; if (retryTask.isOverTimes()) { Loggers.REMOTE_PUSH.warn( - "push callback retry fail over times. dataId={},group={},tenant={},clientId={}, will unregister client.", - notifyRequest.getDataId(), notifyRequest.getGroup(), notifyRequest.getTenant(), - retryTask.connectionId); + "push callback retry fail over times.groupKey={},,clientId={}, will unregister client.", + notifyRequest.getGroupKey(), retryTask.connectionId); connectionManager.unregister(retryTask.connectionId); } else if (connectionManager.getConnection(retryTask.connectionId) != null) { // First time: delay 0s; Second time: delay 2s; Third time: delay 4s @@ -131,9 +134,8 @@ private void push(RpcPushTask retryTask) { } else { // Client is already offline, ignore the task. Loggers.REMOTE_PUSH.warn( - "Client is already offline, ignore the task. dataId={},group={},tenant={},clientId={}", - notifyRequest.getDataId(), notifyRequest.getGroup(), notifyRequest.getTenant(), - retryTask.connectionId); + "Client is already offline, ignore the task. dataId={},groupKey={},tenant={},clientId={}", + notifyRequest.getGroupKey(), retryTask.connectionId); } } @@ -142,7 +144,7 @@ private void push(RpcPushTask retryTask) { */ class RpcPushTask implements Runnable { - FuzzyListenNotifyChangeRequest notifyRequest; + ConfigFuzzyWatchChangeNotifyRequest notifyRequest; int maxRetryTimes; @@ -163,7 +165,7 @@ class RpcPushTask implements Runnable { * @param clientIp The IP address of the client. * @param appName The name of the application. */ - public RpcPushTask(FuzzyListenNotifyChangeRequest notifyRequest, int maxRetryTimes, String connectionId, + public RpcPushTask(ConfigFuzzyWatchChangeNotifyRequest notifyRequest, int maxRetryTimes, String connectionId, String clientIp, String appName) { this.notifyRequest = notifyRequest; this.maxRetryTimes = maxRetryTimes; @@ -185,7 +187,7 @@ public boolean isOverTimes() { public void run() { tryTimes++; TpsCheckRequest tpsCheckRequest = new TpsCheckRequest(); - tpsCheckRequest.setPointName(POINT_FUZZY_LISTEN_CONFIG_PUSH); + tpsCheckRequest.setPointName(POINT_FUZZY_WATCH_CONFIG_PUSH); if (!tpsControlManager.check(tpsCheckRequest).isSuccess()) { push(this); } else { @@ -194,17 +196,16 @@ public void run() { @Override public void onSuccess() { TpsCheckRequest tpsCheckRequest = new TpsCheckRequest(); - tpsCheckRequest.setPointName(POINT_FUZZY_LISTEN_CONFIG_PUSH_SUCCESS); + tpsCheckRequest.setPointName(POINT_FUZZY_WATCH_CONFIG_PUSH_SUCCESS); tpsControlManager.check(tpsCheckRequest); } @Override public void onFail(Throwable e) { TpsCheckRequest tpsCheckRequest = new TpsCheckRequest(); - tpsCheckRequest.setPointName(POINT_FUZZY_LISTEN_CONFIG_PUSH_FAIL); + tpsCheckRequest.setPointName(POINT_FUZZY_WATCH_CONFIG_PUSH_FAIL); tpsControlManager.check(tpsCheckRequest); - Loggers.REMOTE_PUSH.warn("Push fail, dataId={}, group={}, tenant={}, clientId={}", - notifyRequest.getDataId(), notifyRequest.getGroup(), notifyRequest.getTenant(), + Loggers.REMOTE_PUSH.warn("Push fail, groupKey={}, clientId={}", notifyRequest.getGroupKey(), connectionId, e); push(RpcPushTask.this); } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigFuzzyWatchRequestHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigFuzzyWatchRequestHandler.java new file mode 100644 index 00000000000..5426b5accb2 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigFuzzyWatchRequestHandler.java @@ -0,0 +1,95 @@ +/* + * Copyright 1999-2023 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.config.server.remote; + +import com.alibaba.nacos.api.config.remote.request.ConfigFuzzyWatchRequest; +import com.alibaba.nacos.api.config.remote.response.ConfigFuzzyWatchResponse; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.config.server.model.event.ConfigCancelFuzzyWatchEvent; +import com.alibaba.nacos.config.server.model.event.ConfigFuzzyWatchEvent; +import com.alibaba.nacos.config.server.service.ConfigFuzzyWatchContextService; +import com.alibaba.nacos.core.control.TpsControl; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.core.paramcheck.impl.ConfigFuzzyWatchRequestParamsExtractor; +import com.alibaba.nacos.core.remote.RequestHandler; +import com.alibaba.nacos.core.utils.StringPool; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.SignType; +import org.springframework.stereotype.Component; + +import java.util.Set; + +import static com.alibaba.nacos.api.common.Constants.WATCH_TYPE_CANCEL_WATCH; +import static com.alibaba.nacos.api.common.Constants.WATCH_TYPE_WATCH; + +/** + * Handler for processing batch fuzzy listen requests. + *

+ * This handler is responsible for processing batch fuzzy listen requests sent by clients. It adds or removes clients + * from the fuzzy listening context based on the request, and publishes corresponding events to notify interested + * parties. + *

+ * + * @author stone-98 + * @date 2024/3/4 + */ +@Component +public class ConfigFuzzyWatchRequestHandler extends RequestHandler { + + private ConfigFuzzyWatchContextService configFuzzyWatchContextService; + + public ConfigFuzzyWatchRequestHandler(ConfigFuzzyWatchContextService configFuzzyWatchContextService) { + this.configFuzzyWatchContextService = configFuzzyWatchContextService; + } + + /** + * Handles the batch fuzzy listen request. + *

+ * This method processes the batch fuzzy listen request by adding or removing clients from the fuzzy listening + * context based on the request, and publishes corresponding events to notify interested parties. + *

+ * + * @param request The batch fuzzy listen request + * @param meta Request meta information + * @return The response to the batch fuzzy listen request + * @throws NacosException If an error occurs while processing the request + */ + @Override + @TpsControl(pointName = "ConfigFuzzyWatch") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @ExtractorManager.Extractor(rpcExtractor = ConfigFuzzyWatchRequestParamsExtractor.class) + public ConfigFuzzyWatchResponse handle(ConfigFuzzyWatchRequest request, RequestMeta meta) throws NacosException { + String connectionId = StringPool.get(meta.getConnectionId()); + String groupKeyPattern = request.getGroupKeyPattern(); + if (WATCH_TYPE_WATCH.equals(request.getWatchType())) { + // Add client to the fuzzy listening context + configFuzzyWatchContextService.addFuzzyListen(groupKeyPattern, connectionId); + // Get existing group keys for the client and publish initialization event + Set clientExistingGroupKeys = request.getReceivedGroupKeys(); + NotifyCenter.publishEvent(new ConfigFuzzyWatchEvent(connectionId, clientExistingGroupKeys, groupKeyPattern, + request.isInitializing())); + } else if (WATCH_TYPE_CANCEL_WATCH.equals(request.getWatchType())) { + NotifyCenter.publishEvent(new ConfigCancelFuzzyWatchEvent(connectionId, groupKeyPattern)); + } + + // Return response + return new ConfigFuzzyWatchResponse(); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigFuzzyWatchSyncNotifier.java b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigFuzzyWatchSyncNotifier.java new file mode 100644 index 00000000000..4710cddb3a2 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigFuzzyWatchSyncNotifier.java @@ -0,0 +1,367 @@ +/* + * Copyright 1999-2023 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.config.server.remote; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.config.remote.request.ConfigFuzzyWatchSyncRequest; +import com.alibaba.nacos.api.remote.AbstractPushCallBack; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.SmartSubscriber; +import com.alibaba.nacos.common.task.BatchTaskCounter; +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.common.utils.FuzzyGroupKeyPattern; +import com.alibaba.nacos.config.server.configuration.ConfigCommonConfig; +import com.alibaba.nacos.config.server.model.event.ConfigCancelFuzzyWatchEvent; +import com.alibaba.nacos.config.server.model.event.ConfigFuzzyWatchEvent; +import com.alibaba.nacos.config.server.service.ConfigFuzzyWatchContextService; +import com.alibaba.nacos.config.server.utils.ConfigExecutor; +import com.alibaba.nacos.core.remote.ConnectionManager; +import com.alibaba.nacos.core.remote.RpcPushService; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.plugin.control.ControlManagerCenter; +import com.alibaba.nacos.plugin.control.tps.TpsControlManager; +import com.alibaba.nacos.plugin.control.tps.request.TpsCheckRequest; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_DIFF_SYNC_NOTIFY; +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_INIT_NOTIFY; + +/** + * Handles batch fuzzy listen events and pushes corresponding notifications to clients. + * + * @author stone-98 + * @date 2024/3/18 + */ +@Component(value = "configFuzzyWatchSyncNotifier") +public class ConfigFuzzyWatchSyncNotifier extends SmartSubscriber { + + private static final String FUZZY_LISTEN_CONFIG_DIFF_PUSH = "FUZZY_LISTEN_CONFIG_DIFF_PUSH_COUNT"; + + private static final String FUZZY_LISTEN_CONFIG_DIFF_PUSH_SUCCESS = "FUZZY_LISTEN_CONFIG_DIFF_PUSH_SUCCESS"; + + private static final String FUZZY_LISTEN_CONFIG_DIFF_PUSH_FAIL = "FUZZY_LISTEN_CONFIG_DIFF_PUSH_FAIL"; + + private final ConnectionManager connectionManager; + + private final TpsControlManager tpsControlManager; + + private final RpcPushService rpcPushService; + + private final ConfigFuzzyWatchContextService configFuzzyWatchContextService; + + public ConfigFuzzyWatchSyncNotifier(ConnectionManager connectionManager, RpcPushService rpcPushService, + ConfigFuzzyWatchContextService configFuzzyWatchContextService) { + this.connectionManager = connectionManager; + this.tpsControlManager = ControlManagerCenter.getInstance().getTpsControlManager(); + this.rpcPushService = rpcPushService; + this.configFuzzyWatchContextService = configFuzzyWatchContextService; + NotifyCenter.registerSubscriber(this); + } + + /** + * Pushes the retry task to the client connection manager for retrying the RPC push operation. + * + * @param retryTask The retry task containing the RPC push request + * @param connectionManager The connection manager for managing client connections + */ + private static void push(FuzzyWatchRpcPushTask retryTask, ConnectionManager connectionManager) { + ConfigFuzzyWatchSyncRequest notifyRequest = retryTask.notifyRequest; + // Check if the maximum retry times have been reached + if (retryTask.isOverTimes()) { + // If over the maximum retry times, log a warning and unregister the client connection + Loggers.REMOTE_PUSH.warn( + "Push callback retry failed over times. groupKeyPattern={}, clientId={}, will unregister client.", + notifyRequest.getGroupKeyPattern(), retryTask.connectionId); + connectionManager.unregister(retryTask.connectionId); + } else if (connectionManager.getConnection(retryTask.connectionId) != null) { + // Schedule a retry task with an increasing delay based on the number of retries + // First time: delay 0s; second time: delay 2s; third time: delay 4s, and so on + ConfigExecutor.scheduleClientConfigNotifier(retryTask, retryTask.tryTimes * 2L, TimeUnit.SECONDS); + } else { + // If the client is already offline, ignore the task + Loggers.REMOTE_PUSH.warn("Client is already offline, ignore the task. groupKeyPattern={}, clientId={}", + notifyRequest.getGroupKeyPattern(), retryTask.connectionId); + } + } + + /** + * Handles the ConfigBatchFuzzyListenEvent. This method is responsible for processing batch fuzzy listen events and + * pushing corresponding notifications to clients. + * + * @param event The ConfigBatchFuzzyListenEvent to handle + */ + public void handleFuzzyWatchEvent(ConfigFuzzyWatchEvent event) { + + // Match client effective group keys based on the event pattern, client IP, and tag + Set matchGroupKeys = configFuzzyWatchContextService.matchGroupKeys(event.getGroupKeyPattern()); + + // Retrieve existing group keys for the client from the event + Set clientExistingGroupKeys = event.getClientExistingGroupKeys(); + + // Calculate and merge configuration states based on matched and existing group keys + List configStates = FuzzyGroupKeyPattern.diffGroupKeys(matchGroupKeys, + clientExistingGroupKeys); + + if (CollectionUtils.isEmpty(configStates)) { + if (event.isInitializing()) { + ConfigFuzzyWatchSyncRequest request = ConfigFuzzyWatchSyncRequest.buildInitFinishRequest( + event.getGroupKeyPattern()); + int maxPushRetryTimes = ConfigCommonConfig.getInstance().getMaxPushRetryTimes(); + // Create RPC push task and push the request to the client + FuzzyWatchRpcPushTask fuzzyWatchRpcPushTask = new FuzzyWatchRpcPushTask(request, null, + maxPushRetryTimes, event.getConnectionId()); + push(fuzzyWatchRpcPushTask, connectionManager); + } + + } else { + String syncType = event.isInitializing() ? FUZZY_WATCH_INIT_NOTIFY : FUZZY_WATCH_DIFF_SYNC_NOTIFY; + + int batchSize = ConfigCommonConfig.getInstance().getBatchSize(); + // Divide config states into batches + List> divideConfigStatesIntoBatches = divideConfigStatesIntoBatches( + configStates, batchSize); + + // Calculate the number of batches and initialize push batch finish count + int totalBatch = divideConfigStatesIntoBatches.size(); + BatchTaskCounter batchTaskCounter = new BatchTaskCounter(divideConfigStatesIntoBatches.size()); + int currentBatch = 1; + for (List configStateList : divideConfigStatesIntoBatches) { + // Map config states to FuzzyListenNotifyDiffRequest.Context objects + Set contexts = configStateList.stream().map(state -> { + + String changeType = state.isExist() ? Constants.ConfigChangedType.ADD_CONFIG + : Constants.ConfigChangedType.DELETE_CONFIG; + return ConfigFuzzyWatchSyncRequest.Context.build(state.getGroupKey(), changeType); + }).collect(Collectors.toSet()); + + ConfigFuzzyWatchSyncRequest request = ConfigFuzzyWatchSyncRequest.buildSyncRequest(syncType, contexts, + event.getGroupKeyPattern(), totalBatch, currentBatch); + int maxPushRetryTimes = ConfigCommonConfig.getInstance().getMaxPushRetryTimes(); + // Create RPC push task and push the request to the client + FuzzyWatchRpcPushTask fuzzyWatchRpcPushTask = new FuzzyWatchRpcPushTask(request, batchTaskCounter, + maxPushRetryTimes, event.getConnectionId()); + push(fuzzyWatchRpcPushTask, connectionManager); + currentBatch++; + } + } + + } + + @Override + public List> subscribeTypes() { + List> result = new LinkedList<>(); + result.add(ConfigFuzzyWatchEvent.class); + result.add(ConfigCancelFuzzyWatchEvent.class); + return result; + } + + @Override + public void onEvent(Event event) { + if (event instanceof ConfigFuzzyWatchEvent) { + handleFuzzyWatchEvent((ConfigFuzzyWatchEvent) event); + } + + if (event instanceof ConfigCancelFuzzyWatchEvent) { + // Remove client from the fuzzy listening context + configFuzzyWatchContextService.removeFuzzyListen(((ConfigCancelFuzzyWatchEvent) event).getGroupKeyPattern(), + ((ConfigCancelFuzzyWatchEvent) event).getConnectionId()); + } + + } + + /** + * Divides a collection of items into batches. + * + * @param configStates The collection of items to be divided into batches + * @param batchSize The size of each batch + * @param The type of items in the collection + * @return A list of batches, each containing a sublist of items + */ + private List> divideConfigStatesIntoBatches(Collection configStates, int batchSize) { + // Initialize an index to track the current batch number + AtomicInteger index = new AtomicInteger(); + + // Group the elements into batches based on their index divided by the batch size + return new ArrayList<>( + configStates.stream().collect(Collectors.groupingBy(e -> index.getAndIncrement() / batchSize)) + .values()); + } + + /** + * Represents a task for pushing FuzzyListenNotifyDiffRequest to clients. + */ + class FuzzyWatchRpcPushTask implements Runnable { + + /** + * The FuzzyListenNotifyDiffRequest to be pushed. + */ + ConfigFuzzyWatchSyncRequest notifyRequest; + + /** + * The maximum number of times to retry pushing the request. + */ + int maxRetryTimes; + + /** + * The current number of attempts made to push the request. + */ + int tryTimes = 0; + + /** + * The ID of the connection associated with the client. + */ + String connectionId; + + BatchTaskCounter batchTaskCounter; + + /** + * Constructs a new RpcPushTask with the specified parameters. + * + * @param notifyRequest The FuzzyListenNotifyDiffRequest to be pushed + * @param batchTaskCounter The batchTaskCounter counter for tracking the number of finished push batches + * @param maxRetryTimes The maximum number of times to retry pushing the request + * @param connectionId The ID of the connection associated with the client + */ + public FuzzyWatchRpcPushTask(ConfigFuzzyWatchSyncRequest notifyRequest, BatchTaskCounter batchTaskCounter, + int maxRetryTimes, String connectionId) { + this.notifyRequest = notifyRequest; + this.batchTaskCounter = batchTaskCounter; + this.maxRetryTimes = maxRetryTimes; + this.connectionId = connectionId; + } + + /** + * Checks if the maximum number of retry times has been reached. + * + * @return true if the maximum number of retry times has been reached, otherwise false + */ + public boolean isOverTimes() { + return maxRetryTimes > 0 && this.tryTimes >= maxRetryTimes; + } + + /** + * Executes the task, attempting to push the request to the client. + */ + @Override + public void run() { + tryTimes++; + TpsCheckRequest tpsCheckRequest = new TpsCheckRequest(); + + tpsCheckRequest.setPointName(FUZZY_LISTEN_CONFIG_DIFF_PUSH); + if (!tpsControlManager.check(tpsCheckRequest).isSuccess()) { + push(this, connectionManager); + } else { + rpcPushService.pushWithCallback(connectionId, notifyRequest, + new FuzzyWatchRpcPushCallback(this, tpsControlManager, connectionManager, batchTaskCounter), + ConfigExecutor.getClientConfigNotifierServiceExecutor()); + } + } + } + + /** + * Represents a callback for handling the result of an RPC push operation. + */ + class FuzzyWatchRpcPushCallback extends AbstractPushCallBack { + + /** + * The RpcPushTask associated with the callback. + */ + FuzzyWatchRpcPushTask fuzzyWatchRpcPushTask; + + /** + * The TpsControlManager for checking TPS limits. + */ + TpsControlManager tpsControlManager; + + /** + * The ConnectionManager for managing client connections. + */ + ConnectionManager connectionManager; + + BatchTaskCounter batchTaskCounter; + + /** + * Constructs a new RpcPushCallback with the specified parameters. + * + * @param fuzzyWatchRpcPushTask The RpcPushTask associated with the callback + * @param tpsControlManager The TpsControlManager for checking TPS limits + * @param connectionManager The ConnectionManager for managing client connections + * @param batchTaskCounter The batchTaskCounter counter + */ + public FuzzyWatchRpcPushCallback(FuzzyWatchRpcPushTask fuzzyWatchRpcPushTask, + TpsControlManager tpsControlManager, ConnectionManager connectionManager, + BatchTaskCounter batchTaskCounter) { + super(3000L); + this.fuzzyWatchRpcPushTask = fuzzyWatchRpcPushTask; + this.tpsControlManager = tpsControlManager; + this.connectionManager = connectionManager; + this.batchTaskCounter = batchTaskCounter; + + } + + /** + * Handles the successful completion of the RPC push operation. + */ + @Override + public void onSuccess() { + // Check TPS limits + TpsCheckRequest tpsCheckRequest = new TpsCheckRequest(); + tpsCheckRequest.setPointName(FUZZY_LISTEN_CONFIG_DIFF_PUSH_SUCCESS); + tpsControlManager.check(tpsCheckRequest); + + if (batchTaskCounter != null) { + batchTaskCounter.batchSuccess(fuzzyWatchRpcPushTask.notifyRequest.getCurrentBatch()); + if (batchTaskCounter.batchCompleted() && fuzzyWatchRpcPushTask.notifyRequest.getSyncType() + .equals(FUZZY_WATCH_INIT_NOTIFY)) { + ConfigFuzzyWatchSyncRequest request = ConfigFuzzyWatchSyncRequest.buildInitFinishRequest( + fuzzyWatchRpcPushTask.notifyRequest.getGroupKeyPattern()); + push(new FuzzyWatchRpcPushTask(request, null, 50, fuzzyWatchRpcPushTask.connectionId), + connectionManager); + } + } + + } + + /** + * Handles the failure of the RPC push operation. + * + * @param e The exception thrown during the operation + */ + @Override + public void onFail(Throwable e) { + // Check TPS limits + TpsCheckRequest tpsCheckRequest = new TpsCheckRequest(); + tpsCheckRequest.setPointName(FUZZY_LISTEN_CONFIG_DIFF_PUSH_FAIL); + tpsControlManager.check(tpsCheckRequest); + + // Log the failure and retry the task + Loggers.REMOTE_PUSH.warn("Push fail, groupKeyPattern={}, clientId={}", + fuzzyWatchRpcPushTask.notifyRequest.getGroupKeyPattern(), fuzzyWatchRpcPushTask.connectionId, e); + push(fuzzyWatchRpcPushTask, connectionManager); + } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/remote/RpcFuzzyListenConfigDiffNotifier.java b/config/src/main/java/com/alibaba/nacos/config/server/remote/RpcFuzzyListenConfigDiffNotifier.java deleted file mode 100644 index 67e7a46de42..00000000000 --- a/config/src/main/java/com/alibaba/nacos/config/server/remote/RpcFuzzyListenConfigDiffNotifier.java +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Copyright 1999-2023 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.config.server.remote; - -import com.alibaba.nacos.api.common.Constants; -import com.alibaba.nacos.api.config.remote.request.FuzzyListenNotifyDiffRequest; -import com.alibaba.nacos.api.remote.AbstractPushCallBack; -import com.alibaba.nacos.common.notify.Event; -import com.alibaba.nacos.common.notify.NotifyCenter; -import com.alibaba.nacos.common.notify.listener.Subscriber; -import com.alibaba.nacos.common.utils.CollectionUtils; -import com.alibaba.nacos.common.utils.GroupKeyPattern; -import com.alibaba.nacos.config.server.configuration.ConfigCommonConfig; -import com.alibaba.nacos.config.server.model.event.ConfigBatchFuzzyListenEvent; -import com.alibaba.nacos.config.server.service.ConfigCacheService; -import com.alibaba.nacos.config.server.utils.ConfigExecutor; -import com.alibaba.nacos.config.server.utils.GroupKey; -import com.alibaba.nacos.core.remote.Connection; -import com.alibaba.nacos.core.remote.ConnectionManager; -import com.alibaba.nacos.core.remote.ConnectionMeta; -import com.alibaba.nacos.core.remote.RpcPushService; -import com.alibaba.nacos.core.utils.Loggers; -import com.alibaba.nacos.plugin.control.ControlManagerCenter; -import com.alibaba.nacos.plugin.control.tps.TpsControlManager; -import com.alibaba.nacos.plugin.control.tps.request.TpsCheckRequest; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * Handles batch fuzzy listen events and pushes corresponding notifications to clients. - * - * @author stone-98 - * @date 2024/3/18 - */ -@Component(value = "rpcFuzzyListenConfigDiffNotifier") -public class RpcFuzzyListenConfigDiffNotifier extends Subscriber { - - private static final String FUZZY_LISTEN_CONFIG_DIFF_PUSH = "FUZZY_LISTEN_CONFIG_DIFF_PUSH_COUNT"; - - private static final String FUZZY_LISTEN_CONFIG_DIFF_PUSH_SUCCESS = "FUZZY_LISTEN_CONFIG_DIFF_PUSH_SUCCESS"; - - private static final String FUZZY_LISTEN_CONFIG_DIFF_PUSH_FAIL = "FUZZY_LISTEN_CONFIG_DIFF_PUSH_FAIL"; - - private final ConnectionManager connectionManager; - - private final TpsControlManager tpsControlManager; - - private final RpcPushService rpcPushService; - - public RpcFuzzyListenConfigDiffNotifier(ConnectionManager connectionManager, RpcPushService rpcPushService) { - this.connectionManager = connectionManager; - this.tpsControlManager = ControlManagerCenter.getInstance().getTpsControlManager(); - this.rpcPushService = rpcPushService; - NotifyCenter.registerSubscriber(this); - } - - /** - * Pushes the retry task to the client connection manager for retrying the RPC push operation. - * - * @param retryTask The retry task containing the RPC push request - * @param connectionManager The connection manager for managing client connections - */ - private static void push(RpcPushTask retryTask, ConnectionManager connectionManager) { - FuzzyListenNotifyDiffRequest notifyRequest = retryTask.notifyRequest; - // Check if the maximum retry times have been reached - if (retryTask.isOverTimes()) { - // If over the maximum retry times, log a warning and unregister the client connection - Loggers.REMOTE_PUSH.warn( - "Push callback retry failed over times. groupKeyPattern={}, clientId={}, will unregister client.", - notifyRequest.getGroupKeyPattern(), retryTask.connectionId); - connectionManager.unregister(retryTask.connectionId); - } else if (connectionManager.getConnection(retryTask.connectionId) != null) { - // Schedule a retry task with an increasing delay based on the number of retries - // First time: delay 0s; second time: delay 2s; third time: delay 4s, and so on - ConfigExecutor.scheduleClientConfigNotifier(retryTask, retryTask.tryTimes * 2L, TimeUnit.SECONDS); - } else { - // If the client is already offline, ignore the task - Loggers.REMOTE_PUSH.warn("Client is already offline, ignore the task. groupKeyPattern={}, clientId={}", - notifyRequest.getGroupKeyPattern(), retryTask.connectionId); - } - } - - /** - * Handles the ConfigBatchFuzzyListenEvent. This method is responsible for processing batch fuzzy listen events and - * pushing corresponding notifications to clients. - * - * @param event The ConfigBatchFuzzyListenEvent to handle - */ - @Override - public void onEvent(ConfigBatchFuzzyListenEvent event) { - // Get the connection for the client - Connection connection = connectionManager.getConnection(event.getClientId()); - if (connection == null) { - Loggers.REMOTE_PUSH.warn( - "clientId not found, Config diff notification not sent. clientId={},keyGroupPattern={}", - event.getClientId(), event.getKeyGroupPattern()); - // If connection is not available, return - return; - } - - // Retrieve meta information for the connection - ConnectionMeta metaInfo = connection.getMetaInfo(); - String clientIp = metaInfo.getClientIp(); - String clientTag = metaInfo.getTag(); - - // Match client effective group keys based on the event pattern, client IP, and tag - Set matchGroupKeys = ConfigCacheService.matchClientEffectiveGroupKeys(event.getKeyGroupPattern(), - clientIp, clientTag); - - // Retrieve existing group keys for the client from the event - Set clientExistingGroupKeys = event.getClientExistingGroupKeys(); - - // Check if both matched and existing group keys are empty, if so, return - if (CollectionUtils.isEmpty(matchGroupKeys) && CollectionUtils.isEmpty(clientExistingGroupKeys)) { - return; - } - - // Calculate and merge configuration states based on matched and existing group keys - List configStates = calculateAndMergeToConfigState(matchGroupKeys, clientExistingGroupKeys); - - // If no config states are available, return - if (CollectionUtils.isEmpty(configStates)) { - return; - } - - int batchSize = ConfigCommonConfig.getInstance().getBatchSize(); - // Divide config states into batches - List> divideConfigStatesIntoBatches = divideConfigStatesIntoBatches(configStates, batchSize); - - // Calculate the number of batches and initialize push batch finish count - int originBatchSize = divideConfigStatesIntoBatches.size(); - AtomicInteger pushBatchFinishCount = new AtomicInteger(0); - - // Iterate over each batch of config states - for (List configStateList : divideConfigStatesIntoBatches) { - // Map config states to FuzzyListenNotifyDiffRequest.Context objects - Set contexts = configStateList.stream().map(state -> { - String[] parseKey = GroupKey.parseKey(state.getGroupKey()); - String dataId = parseKey[0]; - String group = parseKey[1]; - String tenant = parseKey.length > 2 ? parseKey[2] : Constants.DEFAULT_NAMESPACE_ID; - String changeType = event.isInitializing() ? Constants.ConfigChangeType.LISTEN_INIT - : (state.isExist() ? Constants.ConfigChangeType.ADD_CONFIG - : Constants.ConfigChangeType.DELETE_CONFIG); - return FuzzyListenNotifyDiffRequest.Context.build(tenant, group, dataId, changeType); - }).collect(Collectors.toSet()); - - // Remove namespace from the pattern - String patternWithoutNameSpace = GroupKeyPattern.getPatternRemovedNamespace(event.getKeyGroupPattern()); - - // Build FuzzyListenNotifyDiffRequest with contexts and pattern - FuzzyListenNotifyDiffRequest request = FuzzyListenNotifyDiffRequest.buildInitRequest(contexts, - patternWithoutNameSpace); - - int maxPushRetryTimes = ConfigCommonConfig.getInstance().getMaxPushRetryTimes(); - // Create RPC push task and push the request to the client - RpcPushTask rpcPushTask = new RpcPushTask(request, pushBatchFinishCount, originBatchSize, maxPushRetryTimes, - event.getClientId(), clientIp, metaInfo.getAppName()); - push(rpcPushTask, connectionManager); - } - } - - /** - * Calculates and merges the differences between the matched group keys and the client's existing group keys into a - * list of ConfigState objects. - * - * @param matchGroupKeys The matched group keys set - * @param clientExistingGroupKeys The client's existing group keys set - * @return The merged list of ConfigState objects representing the states to be added or removed - */ - private List calculateAndMergeToConfigState(Set matchGroupKeys, - Set clientExistingGroupKeys) { - // Calculate the set of group keys to be added and removed - Set addGroupKeys = new HashSet<>(); - if (CollectionUtils.isNotEmpty(matchGroupKeys)) { - addGroupKeys.addAll(matchGroupKeys); - } - if (CollectionUtils.isNotEmpty(clientExistingGroupKeys)) { - addGroupKeys.removeAll(clientExistingGroupKeys); - } - - Set removeGroupKeys = new HashSet<>(); - if (CollectionUtils.isNotEmpty(clientExistingGroupKeys)) { - removeGroupKeys.addAll(clientExistingGroupKeys); - } - if (CollectionUtils.isNotEmpty(matchGroupKeys)) { - removeGroupKeys.removeAll(matchGroupKeys); - } - - // Convert the group keys to be added and removed into corresponding ConfigState objects and merge them into a list - return Stream.concat(addGroupKeys.stream().map(groupKey -> new ConfigState(groupKey, true)), - removeGroupKeys.stream().map(groupKey -> new ConfigState(groupKey, false))) - .collect(Collectors.toList()); - } - - @Override - public Class subscribeType() { - return ConfigBatchFuzzyListenEvent.class; - } - - /** - * Divides a collection of items into batches. - * - * @param configStates The collection of items to be divided into batches - * @param batchSize The size of each batch - * @param The type of items in the collection - * @return A list of batches, each containing a sublist of items - */ - private List> divideConfigStatesIntoBatches(Collection configStates, int batchSize) { - // Initialize an index to track the current batch number - AtomicInteger index = new AtomicInteger(); - - // Group the elements into batches based on their index divided by the batch size - return new ArrayList<>( - configStates.stream().collect(Collectors.groupingBy(e -> index.getAndIncrement() / batchSize)) - .values()); - } - - /** - * ConfigState. - */ - public static class ConfigState { - - /** - * The group key associated with the configuration. - */ - private String groupKey; - - /** - * Indicates whether the configuration exists or not. - */ - private boolean exist; - - /** - * Constructs a new ConfigState instance with the given group key and existence flag. - * - * @param groupKey The group key associated with the configuration. - * @param exist {@code true} if the configuration exists, {@code false} otherwise. - */ - public ConfigState(String groupKey, boolean exist) { - this.groupKey = groupKey; - this.exist = exist; - } - - /** - * Retrieves the group key associated with the configuration. - * - * @return The group key. - */ - public String getGroupKey() { - return groupKey; - } - - /** - * Sets the group key associated with the configuration. - * - * @param groupKey The group key to set. - */ - public void setGroupKey(String groupKey) { - this.groupKey = groupKey; - } - - /** - * Checks whether the configuration exists or not. - * - * @return {@code true} if the configuration exists, {@code false} otherwise. - */ - public boolean isExist() { - return exist; - } - - /** - * Sets the existence flag of the configuration. - * - * @param exist {@code true} if the configuration exists, {@code false} otherwise. - */ - public void setExist(boolean exist) { - this.exist = exist; - } - } - - /** - * Represents a task for pushing FuzzyListenNotifyDiffRequest to clients. - */ - class RpcPushTask implements Runnable { - - /** - * The FuzzyListenNotifyDiffRequest to be pushed. - */ - FuzzyListenNotifyDiffRequest notifyRequest; - - /** - * The maximum number of times to retry pushing the request. - */ - int maxRetryTimes; - - /** - * The current number of attempts made to push the request. - */ - int tryTimes = 0; - - /** - * The ID of the connection associated with the client. - */ - String connectionId; - - /** - * The IP address of the client. - */ - String clientIp; - - /** - * The name of the client's application. - */ - String appName; - - /** - * The counter for tracking the number of finished push batches. - */ - AtomicInteger pushBatchFinishCount; - - /** - * The original size of the batch before splitting. - */ - int originBatchSize; - - /** - * Constructs a new RpcPushTask with the specified parameters. - * - * @param notifyRequest The FuzzyListenNotifyDiffRequest to be pushed - * @param pushBatchFinishCount The counter for tracking the number of finished push batches - * @param originBatchSize The original size of the batch before splitting - * @param maxRetryTimes The maximum number of times to retry pushing the request - * @param connectionId The ID of the connection associated with the client - * @param clientIp The IP address of the client - * @param appName The name of the client's application - */ - public RpcPushTask(FuzzyListenNotifyDiffRequest notifyRequest, AtomicInteger pushBatchFinishCount, - int originBatchSize, int maxRetryTimes, String connectionId, String clientIp, String appName) { - this.notifyRequest = notifyRequest; - this.pushBatchFinishCount = pushBatchFinishCount; - this.originBatchSize = originBatchSize; - this.maxRetryTimes = maxRetryTimes; - this.connectionId = connectionId; - this.clientIp = clientIp; - this.appName = appName; - } - - /** - * Checks if the maximum number of retry times has been reached. - * - * @return true if the maximum number of retry times has been reached, otherwise false - */ - public boolean isOverTimes() { - return maxRetryTimes > 0 && this.tryTimes >= maxRetryTimes; - } - - /** - * Executes the task, attempting to push the request to the client. - */ - @Override - public void run() { - tryTimes++; - TpsCheckRequest tpsCheckRequest = new TpsCheckRequest(); - - tpsCheckRequest.setPointName(FUZZY_LISTEN_CONFIG_DIFF_PUSH); - if (!tpsControlManager.check(tpsCheckRequest).isSuccess()) { - push(this, connectionManager); - } else { - rpcPushService.pushWithCallback(connectionId, notifyRequest, - new RpcPushCallback(this, tpsControlManager, connectionManager, pushBatchFinishCount, - originBatchSize), ConfigExecutor.getClientConfigNotifierServiceExecutor()); - } - } - } - - /** - * Represents a callback for handling the result of an RPC push operation. - */ - class RpcPushCallback extends AbstractPushCallBack { - - /** - * The RpcPushTask associated with the callback. - */ - RpcPushTask rpcPushTask; - - /** - * The TpsControlManager for checking TPS limits. - */ - TpsControlManager tpsControlManager; - - /** - * The ConnectionManager for managing client connections. - */ - ConnectionManager connectionManager; - - /** - * The counter for tracking the number of pushed batches. - */ - AtomicInteger pushBatchCount; - - /** - * The original size of the batch before splitting. - */ - int originBatchSize; - - /** - * Constructs a new RpcPushCallback with the specified parameters. - * - * @param rpcPushTask The RpcPushTask associated with the callback - * @param tpsControlManager The TpsControlManager for checking TPS limits - * @param connectionManager The ConnectionManager for managing client connections - * @param pushBatchCount The counter for tracking the number of pushed batches - * @param originBatchSize The original size of the batch before splitting - */ - public RpcPushCallback(RpcPushTask rpcPushTask, TpsControlManager tpsControlManager, - ConnectionManager connectionManager, AtomicInteger pushBatchCount, int originBatchSize) { - // Set the timeout for the callback - super(3000L); - this.rpcPushTask = rpcPushTask; - this.tpsControlManager = tpsControlManager; - this.connectionManager = connectionManager; - this.pushBatchCount = pushBatchCount; - this.originBatchSize = originBatchSize; - } - - /** - * Handles the successful completion of the RPC push operation. - */ - @Override - public void onSuccess() { - // Check TPS limits - TpsCheckRequest tpsCheckRequest = new TpsCheckRequest(); - tpsCheckRequest.setPointName(FUZZY_LISTEN_CONFIG_DIFF_PUSH_SUCCESS); - tpsControlManager.check(tpsCheckRequest); - - if (pushBatchCount.get() < originBatchSize) { - pushBatchCount.incrementAndGet(); - } else if (pushBatchCount.get() == originBatchSize) { - FuzzyListenNotifyDiffRequest request = FuzzyListenNotifyDiffRequest.buildInitFinishRequest( - rpcPushTask.notifyRequest.getGroupKeyPattern()); - push(new RpcPushTask(request, pushBatchCount, originBatchSize, 50, rpcPushTask.connectionId, - rpcPushTask.clientIp, rpcPushTask.appName), connectionManager); - } - } - - /** - * Handles the failure of the RPC push operation. - * - * @param e The exception thrown during the operation - */ - @Override - public void onFail(Throwable e) { - // Check TPS limits - TpsCheckRequest tpsCheckRequest = new TpsCheckRequest(); - tpsCheckRequest.setPointName(FUZZY_LISTEN_CONFIG_DIFF_PUSH_FAIL); - tpsControlManager.check(tpsCheckRequest); - - // Log the failure and retry the task - Loggers.REMOTE_PUSH.warn("Push fail, groupKeyPattern={}, clientId={}", - rpcPushTask.notifyRequest.getGroupKeyPattern(), rpcPushTask.connectionId, e); - push(rpcPushTask, connectionManager); - } - } -} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigCacheService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigCacheService.java index e6e0fe3f060..89e12debc8f 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigCacheService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigCacheService.java @@ -17,9 +17,6 @@ package com.alibaba.nacos.config.server.service; import com.alibaba.nacos.common.notify.NotifyCenter; -import com.alibaba.nacos.common.utils.CollectionUtils; -import com.alibaba.nacos.common.utils.GroupKeyPattern; -import com.alibaba.nacos.common.utils.InternetAddressUtil; import com.alibaba.nacos.common.utils.MD5Utils; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.model.CacheItem; @@ -34,14 +31,9 @@ import com.alibaba.nacos.sys.env.EnvUtil; import java.io.IOException; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; import static com.alibaba.nacos.api.common.Constants.CLIENT_IP; import static com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG; @@ -70,43 +62,12 @@ public class ConfigCacheService { /** * groupKey -> cacheItem. */ - private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>(); + static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>(); public static int groupCount() { return CACHE.size(); } - /** - * Matches the client effective group keys based on the specified group key pattern, client IP, and tag. - * - * @param groupKeyPattern The pattern to match group keys. - * @param clientIp The IP address of the client. - * @param tag The tag associated with the configuration. - * @return A set of group keys that match the pattern and are effective for the client. - */ - public static Set matchClientEffectiveGroupKeys(String groupKeyPattern, String clientIp, String tag) { - return CACHE.entrySet().stream() - .filter(entry -> GroupKeyPattern.isMatchPatternWithNamespace(entry.getKey(), groupKeyPattern)) - .filter(entry -> entry.getValue().effectiveForClient(tag, clientIp)).map(Map.Entry::getKey) - .collect(Collectors.toSet()); - } - - /** - * Checks if the specified group key is present in the cache and effective for the client. - * - * @param groupKey The group key to check. - * @param clientIp The IP address of the client. - * @param tag The tag associated with the configuration. - * @return true if the group key is present in the cache and effective for the client, false otherwise. - */ - public static boolean containsAndEffectiveForClient(String groupKey, String clientIp, String tag) { - if (!CACHE.containsKey(groupKey)) { - return false; - } - CacheItem cacheItem = CACHE.get(groupKey); - return cacheItem.effectiveForClient(tag, clientIp); - } - /** * Save config file and update md5 value in cache. * @@ -239,6 +200,8 @@ public static boolean dumpGray(String dataId, String group, String tenant, Strin //check timestamp long localGrayLastModifiedTs = ConfigCacheService.getGrayLastModifiedTs(groupKey, grayName); + + boolean timestampOutdated = lastModifiedTs < localGrayLastModifiedTs; if (timestampOutdated) { DUMP_LOG.warn("[dump-gray-ignore] timestamp is outdated,groupKey={}", groupKey); return true; diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigFuzzyWatchContextService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigFuzzyWatchContextService.java new file mode 100644 index 00000000000..673622d8f61 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigFuzzyWatchContextService.java @@ -0,0 +1,251 @@ +/* + * 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.config.server.service; + +import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException; +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.common.utils.FuzzyGroupKeyPattern; +import com.alibaba.nacos.config.server.utils.GroupKey; +import com.alibaba.nacos.config.server.utils.GroupKey2; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.core.utils.GlobalExecutor; +import org.springframework.stereotype.Component; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static com.alibaba.nacos.api.common.Constants.ConfigChangedType.ADD_CONFIG; +import static com.alibaba.nacos.api.common.Constants.ConfigChangedType.CONFIG_CHANGED; +import static com.alibaba.nacos.api.common.Constants.ConfigChangedType.DELETE_CONFIG; +import static com.alibaba.nacos.api.model.v2.ErrorCode.FUZZY_WATCH_PATTERN_MATCH_GROUP_KEY_OVER_LIMIT; +import static com.alibaba.nacos.api.model.v2.ErrorCode.FUZZY_WATCH_PATTERN_OVER_LIMIT; + +/** + * fuzzy watch context for config. + * + * @author shiyiyue + */ +@Component +public class ConfigFuzzyWatchContextService { + + /** + * groupKeyPattern -> watched client id set. + */ + private final Map> watchedClients = new ConcurrentHashMap<>(); + + /** + * groupKeyPattern -> matched groupKeys set. + */ + private final Map> matchedGroupKeys = new ConcurrentHashMap<>(); + + private static final int FUZZY_WATCH_MAX_PATTERN_COUNT = 50; + + private static final int FUZZY_WATCH_MAX_PATTERN_MATCHED_GROUP_KEY_COUNT = 1000; + + public ConfigFuzzyWatchContextService() { + + GlobalExecutor.scheduleWithFixDelayByCommon(() -> trimFuzzyWatchContext(), 30000); + } + + /** + * trim fuzzy watch context.
1.remove watchedClients if watched client is empty. 2.remove matchedServiceKeys + * if watchedClients is null. pattern matchedServiceKeys will be removed in second period to avoid frequently + * matchedServiceKeys init. + */ + private void trimFuzzyWatchContext() { + Iterator>> iterator = matchedGroupKeys.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> next = iterator.next(); + Set watchedClients = this.watchedClients.get(next.getKey()); + + if (watchedClients == null) { + iterator.remove(); + LogUtil.DEFAULT_LOG.info( + "[fuzzy-watch] no watchedClients context for pattern {},remove matchedGroupKeys context", + next.getKey()); + } else if (watchedClients.isEmpty()) { + LogUtil.DEFAULT_LOG.info("[fuzzy-watch] no client watched pattern {},remove watchedClients context", + next.getKey()); + this.watchedClients.remove(next.getKey()); + } + } + } + + /** + * get matched exist group keys with the groupKeyPattern. return null if not matched. + * + * @param groupKeyPattern groupKeyPattern. + * @return + */ + public Set matchGroupKeys(String groupKeyPattern) { + return matchedGroupKeys.get(groupKeyPattern); + } + + /** + * sync group key change to fuzzy context. + * + * @param groupKey groupKey. + * @param changedType changedType. + * @return need notify ot not. + */ + public boolean syncGroupKeyContext(String groupKey, String changedType) { + + boolean needNotify = false; + + String[] groupKeyItems = GroupKey.parseKey(groupKey); + String dataId = groupKeyItems[0]; + String group = groupKeyItems[1]; + String namespace = groupKeyItems[2]; + Iterator>> iterator = matchedGroupKeys.entrySet().iterator(); + boolean tryAdd = changedType.equals(ADD_CONFIG) || changedType.equals(CONFIG_CHANGED); + boolean tryRemove = changedType.equals(DELETE_CONFIG); + while (iterator.hasNext()) { + Map.Entry> entry = iterator.next(); + if (FuzzyGroupKeyPattern.matchPattern(entry.getKey(), dataId, group, namespace)) { + if (tryAdd && entry.getValue().add(groupKey)) { + needNotify = true; + } + if (tryRemove && entry.getValue().remove(groupKey)) { + needNotify = true; + } + } + } + return needNotify; + } + + /** + * Matches the client effective group keys based on the specified group key pattern, client IP, and tag. + * + * @param groupKeyPattern The pattern to match group keys. + * @return A set of group keys that match the pattern and are effective for the client. + */ + private void initMatchGroupKeys(String groupKeyPattern) { + if (matchedGroupKeys.containsKey(groupKeyPattern)) { + return; + } + + if (matchedGroupKeys.size() >= FUZZY_WATCH_MAX_PATTERN_COUNT) { + LogUtil.DEFAULT_LOG.warn( + "[fuzzy-watch] pattern count is over limit ,pattern {} init fail,current count is {}", + groupKeyPattern, matchedGroupKeys.size()); + throw new NacosRuntimeException(FUZZY_WATCH_PATTERN_OVER_LIMIT.getCode(), + FUZZY_WATCH_PATTERN_OVER_LIMIT.getMsg()); + } + + matchedGroupKeys.computeIfAbsent(groupKeyPattern, k -> new HashSet<>()); + Set matchedGroupKeys = this.matchedGroupKeys.get(groupKeyPattern); + long matchBeginTime = System.currentTimeMillis(); + for (String groupKey : ConfigCacheService.CACHE.keySet()) { + String[] groupKeyItems = GroupKey.parseKey(groupKey); + if (FuzzyGroupKeyPattern.matchPattern(groupKeyPattern, groupKeyItems[0], groupKeyItems[1], + groupKeyItems[2])) { + + if (matchedGroupKeys.size() >= FUZZY_WATCH_MAX_PATTERN_MATCHED_GROUP_KEY_COUNT) { + LogUtil.DEFAULT_LOG.warn("[fuzzy-watch] pattern matched service count is over limit , " + + "other services will stop notify for pattern {} ,current count is {}", groupKeyPattern, + matchedGroupKeys.size()); + + throw new NacosRuntimeException(FUZZY_WATCH_PATTERN_MATCH_GROUP_KEY_OVER_LIMIT.getCode(), + FUZZY_WATCH_PATTERN_MATCH_GROUP_KEY_OVER_LIMIT.getMsg()); + } + matchedGroupKeys.add(groupKey); + } + } + LogUtil.DEFAULT_LOG.info("[fuzzy-watch] pattern {} match {} group keys, cost {}ms", groupKeyPattern, + matchedGroupKeys.size(), System.currentTimeMillis() - matchBeginTime); + + } + + /** + * Adds a fuzzy listen connection ID associated with the specified group key pattern. If the key pattern does not + * exist in the context, a new entry will be created. If the key pattern already exists, the connection ID will be + * added to the existing set. + * + * @param groupKeyPattern The group key pattern to associate with the listen connection. + * @param connectId The connection ID to be added. + */ + public synchronized void addFuzzyListen(String groupKeyPattern, String connectId) { + + initMatchGroupKeys(groupKeyPattern); + // Add the connection ID to the set associated with the key pattern in keyPatternContext + watchedClients.computeIfAbsent(groupKeyPattern, k -> new HashSet<>()); + watchedClients.get(groupKeyPattern).add(connectId); + } + + /** + * Removes a fuzzy listen connection ID associated with the specified group key pattern. If the group key pattern + * exists in the context and the connection ID is found in the associated set, the connection ID will be removed + * from the set. If the set becomes empty after removal, the entry for the group key pattern will be removed from + * the context. + * + * @param groupKeyPattern The group key pattern associated with the listen connection to be removed. + * @param connectionId The connection ID to be removed. + */ + public synchronized void removeFuzzyListen(String groupKeyPattern, String connectionId) { + // Retrieve the set of connection IDs associated with the group key pattern + Set connectIds = watchedClients.get(groupKeyPattern); + if (CollectionUtils.isNotEmpty(connectIds)) { + // Remove the connection ID from the set if it exists + connectIds.remove(connectionId); + } + } + + /** + * remove watch context for connection id. + * + * @param connectionId connection id. + */ + public void clearFuzzyWatchContext(String connectionId) { + for (Map.Entry> keyPatternContextEntry : watchedClients.entrySet()) { + Set connectionIds = keyPatternContextEntry.getValue(); + if (CollectionUtils.isNotEmpty(connectionIds)) { + connectionIds.remove(connectionId); + } + } + } + + /** + * Retrieves the set of connection IDs matched with the specified group key. + * + * @param groupKey The group key to match with the key patterns. + * @return The set of connection IDs matched with the group key. + */ + public Set getMatchedClients(String groupKey) { + // Initialize a set to store the matched connection IDs + Set connectIds = new HashSet<>(); + // Iterate over each key pattern in the context + Iterator>> watchClientIterator = watchedClients.entrySet().iterator(); + + String[] groupItems = GroupKey2.parseKey(groupKey); + + while (watchClientIterator.hasNext()) { + Map.Entry> watchClientEntry = watchClientIterator.next(); + + String keyPattern = watchClientEntry.getKey(); + if (FuzzyGroupKeyPattern.matchPattern(keyPattern, groupItems[0], groupItems[1], groupItems[2])) { + if (CollectionUtils.isNotEmpty(watchClientEntry.getValue())) { + connectIds.addAll(watchClientEntry.getValue()); + } + } + } + return connectIds; + } + +} diff --git a/core/src/main/java/com/alibaba/nacos/core/paramcheck/impl/ConfigBatchFuzzyListenRequestParamsExtractor.java b/core/src/main/java/com/alibaba/nacos/core/paramcheck/impl/ConfigFuzzyWatchRequestParamsExtractor.java similarity index 51% rename from core/src/main/java/com/alibaba/nacos/core/paramcheck/impl/ConfigBatchFuzzyListenRequestParamsExtractor.java rename to core/src/main/java/com/alibaba/nacos/core/paramcheck/impl/ConfigFuzzyWatchRequestParamsExtractor.java index 73c3c01b834..d396986896e 100644 --- a/core/src/main/java/com/alibaba/nacos/core/paramcheck/impl/ConfigBatchFuzzyListenRequestParamsExtractor.java +++ b/core/src/main/java/com/alibaba/nacos/core/paramcheck/impl/ConfigFuzzyWatchRequestParamsExtractor.java @@ -16,26 +16,25 @@ package com.alibaba.nacos.core.paramcheck.impl; -import com.alibaba.nacos.api.config.remote.request.ConfigBatchFuzzyListenRequest; +import com.alibaba.nacos.api.config.remote.request.ConfigFuzzyWatchRequest; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.remote.request.Request; import com.alibaba.nacos.common.paramcheck.ParamInfo; -import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.common.utils.FuzzyGroupKeyPattern; import com.alibaba.nacos.core.paramcheck.AbstractRpcParamExtractor; import java.util.ArrayList; import java.util.List; -import java.util.Set; /** - * Extractor for parameters of {@link ConfigBatchFuzzyListenRequest}. This extractor retrieves parameter information - * from the request object and constructs {@link ParamInfo} instances representing the namespace ID, group, and data IDs + * Extractor for parameters of {@link ConfigFuzzyWatchRequest}. This extractor retrieves parameter information from the + * request object and constructs {@link ParamInfo} instances representing the namespace ID, group, and data IDs * contained in the request's contexts. * * @author stone-98 * @date 2024/3/5 */ -public class ConfigBatchFuzzyListenRequestParamsExtractor extends AbstractRpcParamExtractor { +public class ConfigFuzzyWatchRequestParamsExtractor extends AbstractRpcParamExtractor { /** * Extracts parameter information from the given request. @@ -46,28 +45,12 @@ public class ConfigBatchFuzzyListenRequestParamsExtractor extends AbstractRpcPar */ @Override public List extractParam(Request request) throws NacosException { - ConfigBatchFuzzyListenRequest req = (ConfigBatchFuzzyListenRequest) request; - Set contexts = req.getContexts(); + ConfigFuzzyWatchRequest req = (ConfigFuzzyWatchRequest) request; List paramInfos = new ArrayList<>(); - if (contexts == null) { - return paramInfos; - } - for (ConfigBatchFuzzyListenRequest.Context context : contexts) { - // Extract namespace ID and group from the context - ParamInfo paramInfo1 = new ParamInfo(); - paramInfo1.setNamespaceId(context.getTenant()); - paramInfo1.setGroup(context.getGroup()); - paramInfos.add(paramInfo1); - - // Extract data IDs from the context if present - if (CollectionUtils.isNotEmpty(context.getDataIds())) { - for (String dataId : context.getDataIds()) { - ParamInfo paramInfo2 = new ParamInfo(); - paramInfo2.setDataId(dataId); - paramInfos.add(paramInfo2); - } - } - } + // Extract namespace ID and group from the context + ParamInfo paramInfo1 = new ParamInfo(); + paramInfo1.setNamespaceId(FuzzyGroupKeyPattern.getNamespaceFromPattern(req.getGroupKeyPattern())); + paramInfos.add(paramInfo1); return paramInfos; } } diff --git a/core/src/main/java/com/alibaba/nacos/core/utils/GlobalExecutor.java b/core/src/main/java/com/alibaba/nacos/core/utils/GlobalExecutor.java index 275b50f1b98..7559b326ccb 100644 --- a/core/src/main/java/com/alibaba/nacos/core/utils/GlobalExecutor.java +++ b/core/src/main/java/com/alibaba/nacos/core/utils/GlobalExecutor.java @@ -72,6 +72,13 @@ public static void scheduleByCommon(Runnable runnable, long delayMs) { COMMON_EXECUTOR.schedule(runnable, delayMs, TimeUnit.MILLISECONDS); } + public static void scheduleWithFixDelayByCommon(Runnable runnable, long delayMs) { + if (COMMON_EXECUTOR.isShutdown()) { + return; + } + COMMON_EXECUTOR.scheduleWithFixedDelay(runnable, delayMs, delayMs, TimeUnit.MILLISECONDS); + } + public static void submitLoadDataTask(Runnable runnable) { DISTRO_EXECUTOR.submit(runnable); } diff --git a/core/src/main/java/com/alibaba/nacos/core/utils/StringPool.java b/core/src/main/java/com/alibaba/nacos/core/utils/StringPool.java index d7f7fca3545..69920b9e0cd 100644 --- a/core/src/main/java/com/alibaba/nacos/core/utils/StringPool.java +++ b/core/src/main/java/com/alibaba/nacos/core/utils/StringPool.java @@ -31,7 +31,11 @@ public class StringPool { private static Cache groupKeyCache = CacheBuilder.newBuilder().maximumSize(5000000) - .expireAfterAccess(180, TimeUnit.SECONDS).build(); + .expireAfterAccess(60, TimeUnit.SECONDS).build(); + + static { + GlobalExecutor.scheduleWithFixDelayByCommon(() -> groupKeyCache.cleanUp(), 30000); + } /** * get singleton string value from the pool. diff --git a/example/src/main/java/com/alibaba/nacos/example/FuzzyListenExample.java b/example/src/main/java/com/alibaba/nacos/example/ConfigFuzzyWatchExample.java similarity index 88% rename from example/src/main/java/com/alibaba/nacos/example/FuzzyListenExample.java rename to example/src/main/java/com/alibaba/nacos/example/ConfigFuzzyWatchExample.java index 40622c4c4d7..a1cf4bb3d24 100644 --- a/example/src/main/java/com/alibaba/nacos/example/FuzzyListenExample.java +++ b/example/src/main/java/com/alibaba/nacos/example/ConfigFuzzyWatchExample.java @@ -18,8 +18,9 @@ import com.alibaba.nacos.api.config.ConfigFactory; import com.alibaba.nacos.api.config.ConfigService; -import com.alibaba.nacos.api.config.listener.AbstractFuzzyListenListener; -import com.alibaba.nacos.api.config.listener.FuzzyListenConfigChangeEvent; +import com.alibaba.nacos.api.config.listener.AbstractFuzzyWatchEventWatcher; +import com.alibaba.nacos.api.config.listener.FuzzyWatchEventWatcher; +import com.alibaba.nacos.api.config.listener.ConfigFuzzyWatchChangeEvent; import com.alibaba.nacos.api.exception.NacosException; import java.util.Properties; @@ -47,7 +48,7 @@ * @author stone-98 * @date 2024/3/14 */ -public class FuzzyListenExample { +public class ConfigFuzzyWatchExample { public static void main(String[] args) throws NacosException, InterruptedException { // Set up properties for Nacos Config Service @@ -66,15 +67,15 @@ public static void main(String[] args) throws NacosException, InterruptedExcepti } // Define a fuzzy listener to handle configuration changes - AbstractFuzzyListenListener listener = new AbstractFuzzyListenListener() { + FuzzyWatchEventWatcher listener = new AbstractFuzzyWatchEventWatcher() { @Override - public void onEvent(FuzzyListenConfigChangeEvent event) { + public void onEvent(ConfigFuzzyWatchChangeEvent event) { System.out.println("[fuzzy listen config change]" + event.toString()); } }; // Add the fuzzy listener to monitor configurations starting with "test" - configService.addFuzzyListener("test*", "DEFAULT_GROUP", listener); + configService.fuzzyWatch("test*", "DEFAULT_GROUP", listener); System.out.println("[Fuzzy listening started.]"); // Publish more configurations to trigger the listener diff --git a/example/src/main/java/com/alibaba/nacos/example/NamingFuzzyWatchExample.java b/example/src/main/java/com/alibaba/nacos/example/NamingFuzzyWatchExample.java new file mode 100644 index 00000000000..c7efac16a9b --- /dev/null +++ b/example/src/main/java/com/alibaba/nacos/example/NamingFuzzyWatchExample.java @@ -0,0 +1,120 @@ +/* + * Copyright 1999-2023 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.example; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.listener.FuzzyWatchChangeEvent; +import com.alibaba.nacos.api.naming.listener.FuzzyWatchEventWatcher; +import com.alibaba.nacos.api.naming.utils.NamingUtils; + +import java.util.Properties; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static com.alibaba.nacos.api.common.Constants.DEFAULT_GROUP; + +/** + * Nacos naming fuzzy watch example. + *

Add the JVM parameter to run the NamingExample:

+ * {@code -DserverAddr=${nacos.server.ip}:${nacos.server.port} -Dnamespace=${namespaceId}} + * + * @author tanyongquan + */ +public class NamingFuzzyWatchExample { + + public static void main(String[] args) throws NacosException, InterruptedException { + + Properties properties = new Properties(); + properties.setProperty("serverAddr", System.getProperty("serverAddr", "localhost")); + properties.setProperty("namespace", System.getProperty("namespace", "public")); + + NamingService naming = NamingFactory.createNamingService(properties); + + int num = 5; + for (int i = 1; i <= num; i++) { + String s = "nacos.test." + i; + naming.registerInstance(s, "11.11.11.11", 8888); + } + + System.out.println(num + " instance have been registered"); + + Executor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), + runnable -> { + Thread thread = new Thread(runnable); + thread.setName("test-thread"); + return thread; + }); + + naming.fuzzyWatch(DEFAULT_GROUP, new FuzzyWatchEventWatcher() { + + //EventListener onEvent is sync to handle, If process too low in onEvent, maybe block other onEvent callback. + //So you can override getExecutor() to async handle event. + @Override + public Executor getExecutor() { + return executor; + } + + @Override + public void onEvent(FuzzyWatchChangeEvent event) { + System.out.println( + "[Fuzzy-Watch-GROUP]changed service name: " + NamingUtils.getServiceKey(event.getNamespace(), + event.getGroupName(), event.getServiceName())); + System.out.println("[Fuzzy-Watch-GROUP]change type: " + event.getChangeType()); + } + }); + + naming.fuzzyWatch("nacos.test.*", DEFAULT_GROUP, new FuzzyWatchEventWatcher() { + + @Override + public Executor getExecutor() { + return executor; + } + + @Override + public void onEvent(FuzzyWatchChangeEvent event) { + System.out.println( + "[Prefix-Fuzzy-Watch]changed service name: " + NamingUtils.getServiceKey(event.getNamespace(), + event.getGroupName(), event.getServiceName())); + System.out.println("[Prefix-Fuzzy-Watch]change type: " + event.getChangeType()); + } + }); + + naming.registerInstance("nacos.test.-1", "11.11.11.11", 8888); + + Thread.sleep(1000); + + naming.registerInstance("nacos.OTHER-PREFIX", "11.11.11.11", 8888); + + Thread.sleep(1000); + + naming.registerInstance("nacos.OTHER-GROUP", "OTHER-GROUP", "11.11.11.11", 8888); + + Thread.sleep(1000); + + for (int i = 1; i <= num; i++) { + String s = "nacos.test." + i; + naming.deregisterInstance(s, "11.11.11.11", 8888); + } + + Thread.sleep(1000); + + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/v2/event/client/ClientOperationEvent.java b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/event/client/ClientOperationEvent.java index 93ecd25cdea..b08d2d0c496 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/core/v2/event/client/ClientOperationEvent.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/event/client/ClientOperationEvent.java @@ -20,6 +20,8 @@ import com.alibaba.nacos.naming.core.v2.client.Client; import com.alibaba.nacos.naming.core.v2.pojo.Service; +import java.util.Set; + /** * Operation client event. * @@ -94,10 +96,80 @@ public ClientUnsubscribeServiceEvent(Service service, String clientId) { } } - public static class ClientReleaseEvent extends ClientOperationEvent { + /** + * Client fuzzy watch service event. + */ + public static class ClientFuzzyWatchEvent extends ClientOperationEvent { + + private static final long serialVersionUID = -4518919987813223119L; + + /** + * client watched pattern. + */ + private final String groupKeyPattern; + + /** + * client side received group keys. + */ + private Set clientReceivedServiceKeys; + + /** + * is fuzzy watch initializing. + */ + private boolean isInitializing; + + public ClientFuzzyWatchEvent(String groupKeyPattern, String clientId, Set clientReceivedServiceKeys, + boolean isInitializing) { + super(clientId, null); + this.groupKeyPattern = groupKeyPattern; + this.clientReceivedServiceKeys = clientReceivedServiceKeys; + this.isInitializing = isInitializing; + } + + public String getGroupKeyPattern() { + return groupKeyPattern; + } + + public Set getClientReceivedServiceKeys() { + return clientReceivedServiceKeys; + } + + public void setClientReceivedServiceKeys(Set clientReceivedServiceKeys) { + this.clientReceivedServiceKeys = clientReceivedServiceKeys; + } + + public boolean isInitializing() { + return isInitializing; + } + + public void setInitializing(boolean initializing) { + isInitializing = initializing; + } + } - private static final long serialVersionUID = -281486927726245701L; + /** + * Client cancel fuzzy watch service event. + */ + public static class ClientCancelFuzzyWatchEvent extends ClientOperationEvent { + + private static final long serialVersionUID = -4518919987813223118L; + + private final String pattern; + + public ClientCancelFuzzyWatchEvent(String pattern, String clientId) { + super(clientId, null); + this.pattern = pattern; + } + + public String getPattern() { + return pattern; + } + } + public static class ClientReleaseEvent extends ClientOperationEvent { + + private static final long serialVersionUID = -281486927726245701L; + private final Client client; private final boolean isNative; @@ -107,11 +179,11 @@ public ClientReleaseEvent(Client client, boolean isNative) { this.client = client; this.isNative = isNative; } - + public Client getClient() { return client; } - + public boolean isNative() { return isNative; } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/v2/event/service/ServiceEvent.java b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/event/service/ServiceEvent.java index 6d559529880..9530f95b377 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/core/v2/event/service/ServiceEvent.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/event/service/ServiceEvent.java @@ -45,17 +45,25 @@ public static class ServiceChangedEvent extends ServiceEvent { private static final long serialVersionUID = 2123694271992630822L; - public ServiceChangedEvent(Service service) { - this(service, false); + private final String changedType; + + public ServiceChangedEvent(Service service, String changedType) { + this(service, changedType, false); } - public ServiceChangedEvent(Service service, boolean incrementRevision) { + public ServiceChangedEvent(Service service, String changedType, boolean incrementRevision) { super(service); + this.changedType = changedType; service.renewUpdateTime(); if (incrementRevision) { service.incrementRevision(); } } + + public String getChangedType() { + return changedType; + } + } /** @@ -64,14 +72,14 @@ public ServiceChangedEvent(Service service, boolean incrementRevision) { public static class ServiceSubscribedEvent extends ServiceEvent { private static final long serialVersionUID = -2645441445867337345L; - + private final String clientId; public ServiceSubscribedEvent(Service service, String clientId) { super(service); this.clientId = clientId; } - + public String getClientId() { return clientId; } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/v2/index/ClientServiceIndexesManager.java b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/index/ClientServiceIndexesManager.java index 48ea5611ae6..2f3bae809c1 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/core/v2/index/ClientServiceIndexesManager.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/index/ClientServiceIndexesManager.java @@ -16,6 +16,7 @@ package com.alibaba.nacos.naming.core.v2.index; +import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.common.notify.Event; import com.alibaba.nacos.common.notify.NotifyCenter; import com.alibaba.nacos.common.notify.listener.SmartSubscriber; @@ -101,15 +102,15 @@ private void handleClientDisconnect(ClientOperationEvent.ClientReleaseEvent even for (Service each : client.getAllSubscribeService()) { removeSubscriberIndexes(each, client.getClientId()); } - DeregisterInstanceReason reason = event.isNative() - ? DeregisterInstanceReason.NATIVE_DISCONNECTED : DeregisterInstanceReason.SYNCED_DISCONNECTED; + DeregisterInstanceReason reason = event.isNative() ? DeregisterInstanceReason.NATIVE_DISCONNECTED + : DeregisterInstanceReason.SYNCED_DISCONNECTED; long currentTimeMillis = System.currentTimeMillis(); for (Service each : client.getAllPublishedService()) { removePublisherIndexes(each, client.getClientId()); InstancePublishInfo instance = client.getInstancePublishInfo(each); - NotifyCenter.publishEvent(new DeregisterInstanceTraceEvent(currentTimeMillis, - "", false, reason, each.getNamespace(), each.getGroup(), each.getName(), - instance.getIp(), instance.getPort())); + NotifyCenter.publishEvent( + new DeregisterInstanceTraceEvent(currentTimeMillis, "", false, reason, each.getNamespace(), + each.getGroup(), each.getName(), instance.getIp(), instance.getPort())); } } @@ -128,14 +129,21 @@ private void handleClientOperation(ClientOperationEvent event) { } private void addPublisherIndexes(Service service, String clientId) { + String serviceChangedType = Constants.ServiceChangedType.INSTANCE_CHANGED; + if (!publisherIndexes.containsKey(service)) { + // The only time the index needs to be updated is when the service is first created + serviceChangedType = Constants.ServiceChangedType.ADD_SERVICE; + } + NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, serviceChangedType, true)); publisherIndexes.computeIfAbsent(service, key -> new ConcurrentHashSet<>()).add(clientId); - NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true)); } private void removePublisherIndexes(Service service, String clientId) { publisherIndexes.computeIfPresent(service, (s, ids) -> { ids.remove(clientId); - NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true)); + String serviceChangedType = ids.isEmpty() ? Constants.ServiceChangedType.DELETE_SERVICE + : Constants.ServiceChangedType.INSTANCE_CHANGED; + NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, serviceChangedType, true)); return ids.isEmpty() ? null : ids; }); } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/v2/index/NamingFuzzyWatchContextService.java b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/index/NamingFuzzyWatchContextService.java new file mode 100644 index 00000000000..6f0b0cd5c55 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/index/NamingFuzzyWatchContextService.java @@ -0,0 +1,265 @@ +/* + * 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.v2.index; + +import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException; +import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.SmartSubscriber; +import com.alibaba.nacos.common.utils.ConcurrentHashSet; +import com.alibaba.nacos.common.utils.FuzzyGroupKeyPattern; +import com.alibaba.nacos.core.utils.GlobalExecutor; +import com.alibaba.nacos.naming.core.v2.ServiceManager; +import com.alibaba.nacos.naming.core.v2.event.client.ClientOperationEvent; +import com.alibaba.nacos.naming.core.v2.pojo.Service; +import com.alibaba.nacos.naming.misc.Loggers; +import org.springframework.stereotype.Component; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static com.alibaba.nacos.api.common.Constants.ServiceChangedType.ADD_SERVICE; +import static com.alibaba.nacos.api.common.Constants.ServiceChangedType.DELETE_SERVICE; +import static com.alibaba.nacos.api.model.v2.ErrorCode.FUZZY_WATCH_PATTERN_MATCH_GROUP_KEY_OVER_LIMIT; +import static com.alibaba.nacos.api.model.v2.ErrorCode.FUZZY_WATCH_PATTERN_OVER_LIMIT; +import static com.alibaba.nacos.common.utils.FuzzyGroupKeyPattern.getNamespaceFromPattern; + +/** + * naming fuzzy watch context service. + * + * @author shiyiyue + */ +@Component +public class NamingFuzzyWatchContextService extends SmartSubscriber { + + /** + * watched client ids of a pattern, {fuzzy watch pattern -> Set[watched clientID]}. + */ + private final ConcurrentMap> watchedClients = new ConcurrentHashMap<>(); + + /** + * The pattern matched service keys for pattern.{fuzzy watch pattern -> Set[matched service keys]}. initialized a + * new entry pattern when a client register a new pattern. destroyed a new entry pattern by task when no clients + * watch pattern in max 30s delay. + */ + private final ConcurrentMap> matchedServiceKeys = new ConcurrentHashMap<>(); + + private static final int FUZZY_WATCH_MAX_PATTERN_COUNT = 50; + + private static final int FUZZY_WATCH_MAX_PATTERN_MATCHED_GROUP_KEY_COUNT = 200; + + public NamingFuzzyWatchContextService() { + GlobalExecutor.scheduleWithFixDelayByCommon(() -> trimFuzzyWatchContext(), 30000); + NotifyCenter.registerSubscriber(this); + } + + /** + * trim fuzzy watch context.
1.remove watchedClients if watched client is empty. 2.remove matchedServiceKeys + * if watchedClients is null. pattern matchedServiceKeys will be removed in second period to avoid frequently + * matchedServiceKeys init. + */ + private void trimFuzzyWatchContext() { + try { + Iterator>> iterator = matchedServiceKeys.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> next = iterator.next(); + Set watchedClients = this.watchedClients.get(next.getKey()); + if (watchedClients == null) { + iterator.remove(); + } else if (watchedClients.isEmpty()) { + this.watchedClients.remove(next.getKey()); + } + } + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + } + + @Override + public List> subscribeTypes() { + List> result = new LinkedList<>(); + result.add(ClientOperationEvent.ClientReleaseEvent.class); + return result; + } + + @Override + public void onEvent(Event event) { + + //handle client disconnected event. + if (event instanceof ClientOperationEvent.ClientReleaseEvent) { + removeFuzzyWatchContext(((ClientOperationEvent.ClientReleaseEvent) event).getClientId()); + } + } + + /** + * get client that fuzzy watch this service. + * + * @param service service to check fuzzy watcher. + * @return client ids. + */ + public Set getFuzzyWatchedClients(Service service) { + Set matchedClients = new HashSet<>(); + Iterator>> iterator = watchedClients.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> entry = iterator.next(); + if (FuzzyGroupKeyPattern.matchPattern(entry.getKey(), service.getName(), service.getGroup(), + service.getNamespace())) { + matchedClients.addAll(entry.getValue()); + } + } + return matchedClients; + } + + /** + * sync changed service to fuzzy watch context. + * + * @param changedService changed service. + * @param changedType change type. + * @return + */ + public boolean syncServiceContext(Service changedService, String changedType) { + + boolean needNotify = false; + if (!changedType.equals(ADD_SERVICE) && !changedType.equals(DELETE_SERVICE)) { + return false; + } + + String serviceKey = NamingUtils.getServiceKey(changedService.getNamespace(), changedService.getGroup(), + changedService.getName()); + Loggers.SRV_LOG.warn("FUZZY_WATCH: service change matched,service key {},changed type {} ", serviceKey, + changedType); + + Iterator>> iterator = matchedServiceKeys.entrySet().iterator(); + + while (iterator.hasNext()) { + Map.Entry> next = iterator.next(); + if (FuzzyGroupKeyPattern.matchPattern(next.getKey(), changedService.getName(), changedService.getGroup(), + changedService.getNamespace())) { + + Set matchedServiceKeys = next.getValue(); + if (changedType.equals(ADD_SERVICE) && !matchedServiceKeys.contains(serviceKey)) { + if (matchedServiceKeys.size() >= FUZZY_WATCH_MAX_PATTERN_MATCHED_GROUP_KEY_COUNT) { + Loggers.SRV_LOG.warn("FUZZY_WATCH: pattern matched service count is over limit , " + + "current service will be ignore for pattern {} ,current count is {}", next.getKey(), + matchedServiceKeys.size()); + continue; + } + if (matchedServiceKeys.add(serviceKey)) { + Loggers.SRV_LOG.info("FUZZY_WATCH: pattern {} matched service keys count changed to {}", + next.getKey(), matchedServiceKeys.size()); + needNotify = true; + } + + } else if (changedType.equals(DELETE_SERVICE) && matchedServiceKeys.contains(serviceKey)) { + if (matchedServiceKeys.remove(serviceKey)) { + Loggers.SRV_LOG.info("FUZZY_WATCH: pattern {} matched service keys count changed to {}", + next.getKey(), matchedServiceKeys.size()); + needNotify = true; + } + } + } + } + return needNotify; + } + + /** + * sync fuzzy watch context. + * + * @param groupKeyPattern group key pattern. + * @param clientId client id. + * @return + */ + public Set syncFuzzyWatcherContext(String groupKeyPattern, String clientId) { + watchedClients.computeIfAbsent(groupKeyPattern, key -> new ConcurrentHashSet<>()).add(clientId); + Set matchedServiceKeys = initWatchMatchService(groupKeyPattern); + return matchedServiceKeys; + } + + private void removeFuzzyWatchContext(String clientId) { + Iterator>> iterator = watchedClients.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> next = iterator.next(); + next.getValue().remove(clientId); + } + } + + /** + * remove fuzzy watch context for a pattern and client id. + * + * @param groupKeyPattern group key pattern. + * @param clientId client id. + */ + public void removeFuzzyWatchContext(String groupKeyPattern, String clientId) { + if (watchedClients.containsKey(groupKeyPattern)) { + watchedClients.get(groupKeyPattern).remove(clientId); + } + } + + /** + * This method will build/update the fuzzy watch match index for given patterns. + * + * @param completedPattern the completed pattern of watch (with namespace id). + * @return a copy set of matched service keys in Nacos server + */ + public Set initWatchMatchService(String completedPattern) { + + if (!matchedServiceKeys.containsKey(completedPattern)) { + if (matchedServiceKeys.size() >= FUZZY_WATCH_MAX_PATTERN_COUNT) { + Loggers.SRV_LOG.warn( + "FUZZY_WATCH: fuzzy watch pattern count is over limit ,pattern {} init fail,current count is {}", + completedPattern, matchedServiceKeys.size()); + throw new NacosRuntimeException(FUZZY_WATCH_PATTERN_OVER_LIMIT.getCode(), + FUZZY_WATCH_PATTERN_OVER_LIMIT.getMsg()); + } + + long matchBeginTime = System.currentTimeMillis(); + Set namespaceServices = ServiceManager.getInstance() + .getSingletons(getNamespaceFromPattern(completedPattern)); + Set matchedServices = matchedServiceKeys.computeIfAbsent(completedPattern, k -> new HashSet<>()); + + for (Service service : namespaceServices) { + if (FuzzyGroupKeyPattern.matchPattern(completedPattern, service.getName(), service.getGroup(), + service.getNamespace())) { + if (matchedServices.size() >= FUZZY_WATCH_MAX_PATTERN_MATCHED_GROUP_KEY_COUNT) { + + Loggers.SRV_LOG.warn("FUZZY_WATCH: pattern matched service count is over limit , " + + "other services will stop notify for pattern {} ,current count is {}", + completedPattern, matchedServices.size()); + throw new NacosRuntimeException(FUZZY_WATCH_PATTERN_MATCH_GROUP_KEY_OVER_LIMIT.getCode(), + FUZZY_WATCH_PATTERN_MATCH_GROUP_KEY_OVER_LIMIT.getMsg()); + } + matchedServices.add( + NamingUtils.getServiceKey(service.getNamespace(), service.getGroup(), service.getName())); + } + } + matchedServiceKeys.putIfAbsent(completedPattern, matchedServices); + Loggers.SRV_LOG.info("FUZZY_WATCH: pattern {} match {} services, cost {}ms", completedPattern, + matchedServices.size(), System.currentTimeMillis() - matchBeginTime); + + } + + return new HashSet(matchedServiceKeys.get(completedPattern)); + } + +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/v2/metadata/InstanceMetadataProcessor.java b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/metadata/InstanceMetadataProcessor.java index 92cb1ee4f51..d7e6514f287 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/core/v2/metadata/InstanceMetadataProcessor.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/metadata/InstanceMetadataProcessor.java @@ -108,7 +108,8 @@ private void updateInstanceMetadata(MetadataOperation op) { Service service = Service.newService(op.getNamespace(), op.getGroup(), op.getServiceName()); service = ServiceManager.getInstance().getSingleton(service); namingMetadataManager.updateInstanceMetadata(service, op.getTag(), op.getMetadata()); - NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true)); + NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, + com.alibaba.nacos.api.common.Constants.ServiceChangedType.INSTANCE_CHANGED, true)); } private void deleteInstanceMetadata(MetadataOperation op) { diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/heartbeat/ClientBeatProcessorV2.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/heartbeat/ClientBeatProcessorV2.java index dee58b8c67f..d1125aac39a 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/heartbeat/ClientBeatProcessorV2.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/heartbeat/ClientBeatProcessorV2.java @@ -16,6 +16,7 @@ package com.alibaba.nacos.naming.healthcheck.heartbeat; +import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.common.notify.NotifyCenter; import com.alibaba.nacos.common.trace.event.naming.HealthStateChangeTraceEvent; @@ -67,7 +68,7 @@ public void run() { instance.setHealthy(true); Loggers.EVT_LOG.info("service: {} {POS} {IP-ENABLED} valid: {}:{}@{}, region: {}, msg: client beat ok", rsInfo.getServiceName(), ip, port, rsInfo.getCluster(), UtilsAndCommons.LOCALHOST_SITE); - NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service)); + NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, Constants.ServiceChangedType.HEART_BEAT)); NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(client)); NotifyCenter.publishEvent(new HealthStateChangeTraceEvent(System.currentTimeMillis(), service.getNamespace(), service.getGroup(), service.getName(), instance.getIp(), diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/heartbeat/UnhealthyInstanceChecker.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/heartbeat/UnhealthyInstanceChecker.java index 1614a5cf7aa..b3d65847d56 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/heartbeat/UnhealthyInstanceChecker.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/heartbeat/UnhealthyInstanceChecker.java @@ -76,7 +76,7 @@ private void changeHealthyStatus(Client client, Service service, HealthCheckInst .info("{POS} {IP-DISABLED} valid: {}:{}@{}@{}, region: {}, msg: client last beat: {}", instance.getIp(), instance.getPort(), instance.getCluster(), service.getName(), UtilsAndCommons.LOCALHOST_SITE, instance.getLastHeartBeatTime()); - NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service)); + NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, Constants.ServiceChangedType.HEART_BEAT)); NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(client)); NotifyCenter.publishEvent(new HealthStateChangeTraceEvent(System.currentTimeMillis(), service.getNamespace(), service.getGroup(), service.getName(), instance.getIp(), instance.getPort(), diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/NamingFuzzyWatchChangeNotifier.java b/naming/src/main/java/com/alibaba/nacos/naming/push/NamingFuzzyWatchChangeNotifier.java new file mode 100644 index 00000000000..28a693f4435 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/NamingFuzzyWatchChangeNotifier.java @@ -0,0 +1,90 @@ +/* + * 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.push; + +import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.SmartSubscriber; +import com.alibaba.nacos.naming.core.v2.event.service.ServiceEvent; +import com.alibaba.nacos.naming.core.v2.index.NamingFuzzyWatchContextService; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.push.v2.PushConfig; +import com.alibaba.nacos.naming.push.v2.task.FuzzyWatchChangeNotifyTask; +import com.alibaba.nacos.naming.push.v2.task.FuzzyWatchPushDelayTaskEngine; +import org.springframework.stereotype.Service; + +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * service change notify for fuzzy watch. + * + * @author shiyiyue + */ +@Service +public class NamingFuzzyWatchChangeNotifier extends SmartSubscriber { + + private NamingFuzzyWatchContextService namingFuzzyWatchContextService; + + private FuzzyWatchPushDelayTaskEngine fuzzyWatchPushDelayTaskEngine; + + public NamingFuzzyWatchChangeNotifier(NamingFuzzyWatchContextService namingFuzzyWatchContextService, + FuzzyWatchPushDelayTaskEngine fuzzyWatchPushDelayTaskEngine) { + this.fuzzyWatchPushDelayTaskEngine = fuzzyWatchPushDelayTaskEngine; + this.namingFuzzyWatchContextService = namingFuzzyWatchContextService; + NotifyCenter.registerSubscriber(this); + } + + @Override + public List> subscribeTypes() { + List> result = new LinkedList<>(); + result.add(ServiceEvent.ServiceChangedEvent.class); + return result; + } + + @Override + public void onEvent(Event event) { + if (event instanceof ServiceEvent.ServiceChangedEvent) { + ServiceEvent.ServiceChangedEvent serviceChangedEvent = (ServiceEvent.ServiceChangedEvent) event; + if (namingFuzzyWatchContextService.syncServiceContext(serviceChangedEvent.getService(), + serviceChangedEvent.getChangedType())) { + generateFuzzyWatchChangeNotifyTask(serviceChangedEvent.getService(), + serviceChangedEvent.getChangedType()); + } + } + } + + private void generateFuzzyWatchChangeNotifyTask(com.alibaba.nacos.naming.core.v2.pojo.Service service, + String changedType) { + + String serviceKey = NamingUtils.getServiceKey(service.getNamespace(), service.getGroup(), service.getName()); + Set fuzzyWatchedClients = namingFuzzyWatchContextService.getFuzzyWatchedClients(service); + + Loggers.SRV_LOG.info("FUZZY_WATCH:serviceKey {} has {} clients fuzzy watched", serviceKey, + fuzzyWatchedClients == null ? 0 : fuzzyWatchedClients.size()); + // watch notify push task specify by service + for (String clientId : fuzzyWatchedClients) { + FuzzyWatchChangeNotifyTask fuzzyWatchChangeNotifyTask = new FuzzyWatchChangeNotifyTask(serviceKey, + changedType, clientId, PushConfig.getInstance().getPushTaskDelay()); + fuzzyWatchPushDelayTaskEngine.addTask(FuzzyWatchPushDelayTaskEngine.getTaskKey(fuzzyWatchChangeNotifyTask), + fuzzyWatchChangeNotifyTask); + } + } + +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/NamingFuzzyWatchSyncNotifier.java b/naming/src/main/java/com/alibaba/nacos/naming/push/NamingFuzzyWatchSyncNotifier.java new file mode 100644 index 00000000000..c9e8f897257 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/NamingFuzzyWatchSyncNotifier.java @@ -0,0 +1,157 @@ +/* + * 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.push; + +import com.alibaba.nacos.api.naming.remote.request.NamingFuzzyWatchSyncRequest; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.SmartSubscriber; +import com.alibaba.nacos.common.task.BatchTaskCounter; +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.common.utils.FuzzyGroupKeyPattern; +import com.alibaba.nacos.naming.core.v2.event.client.ClientOperationEvent; +import com.alibaba.nacos.naming.core.v2.index.NamingFuzzyWatchContextService; +import com.alibaba.nacos.naming.push.v2.PushConfig; +import com.alibaba.nacos.naming.push.v2.task.FuzzyWatchPushDelayTaskEngine; +import com.alibaba.nacos.naming.push.v2.task.FuzzyWatchSyncNotifyTask; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import static com.alibaba.nacos.api.common.Constants.FINISH_FUZZY_WATCH_INIT_NOTIFY; +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_DIFF_SYNC_NOTIFY; +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_INIT_NOTIFY; +import static com.alibaba.nacos.api.common.Constants.ServiceChangedType.ADD_SERVICE; +import static com.alibaba.nacos.api.common.Constants.ServiceChangedType.DELETE_SERVICE; + +/** + * fuzzy watch event for fuzzy watch. + * @author shiyiyue + */ +@Service +public class NamingFuzzyWatchSyncNotifier extends SmartSubscriber { + + private NamingFuzzyWatchContextService namingFuzzyWatchContextService; + + private FuzzyWatchPushDelayTaskEngine fuzzyWatchPushDelayTaskEngine; + + private static final int BATCH_SIZE = 10; + + public NamingFuzzyWatchSyncNotifier(NamingFuzzyWatchContextService namingFuzzyWatchContextService, + FuzzyWatchPushDelayTaskEngine fuzzyWatchPushDelayTaskEngine) { + this.namingFuzzyWatchContextService = namingFuzzyWatchContextService; + this.fuzzyWatchPushDelayTaskEngine = fuzzyWatchPushDelayTaskEngine; + NotifyCenter.registerSubscriber(this); + } + + @Override + public List> subscribeTypes() { + List> result = new LinkedList<>(); + result.add(ClientOperationEvent.ClientFuzzyWatchEvent.class); + result.add(ClientOperationEvent.ClientCancelFuzzyWatchEvent.class); + return result; + } + + @Override + public void onEvent(Event event) { + + if (event instanceof ClientOperationEvent.ClientFuzzyWatchEvent) { + //fuzzy watch event + ClientOperationEvent.ClientFuzzyWatchEvent clientFuzzyWatchEvent = (ClientOperationEvent.ClientFuzzyWatchEvent) event; + handleClientFuzzyWatchEvent(clientFuzzyWatchEvent); + } else if (event instanceof ClientOperationEvent.ClientCancelFuzzyWatchEvent) { + //handle cancel fuzzy watch event for a client cancel a fuzzy pattern + String completedPattern = ((ClientOperationEvent.ClientCancelFuzzyWatchEvent) event).getPattern(); + namingFuzzyWatchContextService.removeFuzzyWatchContext(completedPattern, + ((ClientOperationEvent.ClientCancelFuzzyWatchEvent) event).getClientId()); + } + + } + + private void handleClientFuzzyWatchEvent(ClientOperationEvent.ClientFuzzyWatchEvent clientFuzzyWatchEvent) { + String completedPattern = clientFuzzyWatchEvent.getGroupKeyPattern(); + + //sync fuzzy watch context + Set patternMatchedServiceKeys = namingFuzzyWatchContextService.syncFuzzyWatcherContext(completedPattern, + clientFuzzyWatchEvent.getClientId()); + Set clientReceivedGroupKeys = clientFuzzyWatchEvent.getClientReceivedServiceKeys(); + List groupKeyStates = FuzzyGroupKeyPattern.diffGroupKeys( + patternMatchedServiceKeys, clientReceivedGroupKeys); + Set syncContext = convert(groupKeyStates); + String syncType = + clientFuzzyWatchEvent.isInitializing() ? FUZZY_WATCH_INIT_NOTIFY : FUZZY_WATCH_DIFF_SYNC_NOTIFY; + + if (CollectionUtils.isNotEmpty(groupKeyStates)) { + Set> dividedServices = divideServiceByBatch(syncContext); + BatchTaskCounter batchTaskCounter = new BatchTaskCounter(dividedServices.size()); + int currentBatch = 1; + for (Set batchData : dividedServices) { + FuzzyWatchSyncNotifyTask fuzzyWatchSyncNotifyTask = new FuzzyWatchSyncNotifyTask( + clientFuzzyWatchEvent.getClientId(), completedPattern, syncType, batchData, + PushConfig.getInstance().getPushTaskRetryDelay()); + fuzzyWatchSyncNotifyTask.setBatchTaskCounter(batchTaskCounter); + fuzzyWatchSyncNotifyTask.setTotalBatch(dividedServices.size()); + fuzzyWatchSyncNotifyTask.setCurrentBatch(currentBatch); + fuzzyWatchPushDelayTaskEngine.addTask( + FuzzyWatchPushDelayTaskEngine.getTaskKey(fuzzyWatchSyncNotifyTask), fuzzyWatchSyncNotifyTask); + currentBatch++; + } + } else if (FUZZY_WATCH_INIT_NOTIFY.equals(syncType)) { + FuzzyWatchSyncNotifyTask fuzzyWatchSyncNotifyTask = new FuzzyWatchSyncNotifyTask( + clientFuzzyWatchEvent.getClientId(), completedPattern, FINISH_FUZZY_WATCH_INIT_NOTIFY, null, + PushConfig.getInstance().getPushTaskRetryDelay()); + fuzzyWatchPushDelayTaskEngine.addTask(FuzzyWatchPushDelayTaskEngine.getTaskKey(fuzzyWatchSyncNotifyTask), + fuzzyWatchSyncNotifyTask); + + } + } + + private Set> divideServiceByBatch( + Collection matchedService) { + Set> result = new HashSet<>(); + if (matchedService.isEmpty()) { + return result; + } + Set currentBatch = new HashSet<>(); + for (NamingFuzzyWatchSyncRequest.Context groupedServiceName : matchedService) { + currentBatch.add(groupedServiceName); + if (currentBatch.size() >= this.BATCH_SIZE) { + result.add(currentBatch); + currentBatch = new HashSet<>(); + } + } + if (!currentBatch.isEmpty()) { + result.add(currentBatch); + } + return result; + } + + private Set convert(List diffGroupKeys) { + Set syncContext = new HashSet<>(); + for (FuzzyGroupKeyPattern.GroupKeyState groupKeyState : diffGroupKeys) { + NamingFuzzyWatchSyncRequest.Context context = new NamingFuzzyWatchSyncRequest.Context(); + context.setServiceKey(groupKeyState.getGroupKey()); + context.setChangedType(groupKeyState.isExist() ? ADD_SERVICE : DELETE_SERVICE); + syncContext.add(context); + } + return syncContext; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutor.java b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutor.java index 21bcfab0d4b..f081cc32b59 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutor.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutor.java @@ -16,6 +16,8 @@ package com.alibaba.nacos.naming.push.v2.executor; +import com.alibaba.nacos.api.naming.remote.request.AbstractFuzzyWatchNotifyRequest; +import com.alibaba.nacos.api.remote.PushCallBack; import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.naming.push.v2.PushDataWrapper; import com.alibaba.nacos.naming.push.v2.task.NamingPushCallback; @@ -45,4 +47,14 @@ public interface PushExecutor { * @param callBack callback */ void doPushWithCallback(String clientId, Subscriber subscriber, PushDataWrapper data, NamingPushCallback callBack); + + /** + * Do push to notify fuzzy watcher with call back. + * + * @param clientId client id + * @param fuzzyWatchNotifyRequest request for fuzzy watch notification + * @param callBack callback + */ + void doFuzzyWatchNotifyPushWithCallBack(String clientId, AbstractFuzzyWatchNotifyRequest fuzzyWatchNotifyRequest, PushCallBack callBack); + } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutorDelegate.java b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutorDelegate.java index f1fa7288e07..34006f21092 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutorDelegate.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutorDelegate.java @@ -16,6 +16,8 @@ package com.alibaba.nacos.naming.push.v2.executor; +import com.alibaba.nacos.api.naming.remote.request.AbstractFuzzyWatchNotifyRequest; +import com.alibaba.nacos.api.remote.PushCallBack; import com.alibaba.nacos.naming.core.v2.client.impl.IpPortBasedClient; import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.naming.push.v2.PushDataWrapper; @@ -53,6 +55,13 @@ public void doPushWithCallback(String clientId, Subscriber subscriber, PushDataW getPushExecuteService(clientId, subscriber).doPushWithCallback(clientId, subscriber, data, callBack); } + @Override + public void doFuzzyWatchNotifyPushWithCallBack(String clientId, AbstractFuzzyWatchNotifyRequest watchNotifyRequest, + PushCallBack callBack) { + // only support fuzzy watch by rpc + rpcPushExecuteService.doFuzzyWatchNotifyPushWithCallBack(clientId, watchNotifyRequest, callBack); + } + private PushExecutor getPushExecuteService(String clientId, Subscriber subscriber) { Optional result = SpiImplPushExecutorHolder.getInstance() .findPushExecutorSpiImpl(clientId, subscriber); diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutorRpcImpl.java b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutorRpcImpl.java index 143d4715bdf..a2084b5d380 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutorRpcImpl.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutorRpcImpl.java @@ -17,7 +17,9 @@ package com.alibaba.nacos.naming.push.v2.executor; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; +import com.alibaba.nacos.api.naming.remote.request.AbstractFuzzyWatchNotifyRequest; import com.alibaba.nacos.api.naming.remote.request.NotifySubscriberRequest; +import com.alibaba.nacos.api.remote.PushCallBack; import com.alibaba.nacos.core.remote.RpcPushService; import com.alibaba.nacos.naming.misc.GlobalExecutor; import com.alibaba.nacos.naming.pojo.Subscriber; @@ -60,4 +62,10 @@ private ServiceInfo getServiceInfo(PushDataWrapper data, Subscriber subscriber) .selectInstancesWithHealthyProtection(data.getOriginalData(), data.getServiceMetadata(), false, true, subscriber); } + + @Override + public void doFuzzyWatchNotifyPushWithCallBack(String clientId, AbstractFuzzyWatchNotifyRequest watchNotifyRequest, PushCallBack callBack) { + pushService.pushWithCallback(clientId, watchNotifyRequest, callBack, GlobalExecutor.getCallbackExecutor()); + } + } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutorUdpImpl.java b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutorUdpImpl.java index c60478166d9..431ea4f7050 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutorUdpImpl.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/executor/PushExecutorUdpImpl.java @@ -17,7 +17,9 @@ package com.alibaba.nacos.naming.push.v2.executor; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; +import com.alibaba.nacos.api.naming.remote.request.AbstractFuzzyWatchNotifyRequest; import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.api.remote.PushCallBack; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.naming.push.UdpPushService; @@ -93,4 +95,9 @@ private ServiceInfo handleClusterData(ServiceInfo data, Subscriber subscriber) { return StringUtils.isBlank(subscriber.getCluster()) ? data : ServiceUtil.selectInstances(data, subscriber.getCluster()); } + + @Override + public void doFuzzyWatchNotifyPushWithCallBack(String clientId, AbstractFuzzyWatchNotifyRequest watchNotifyRequest, PushCallBack callBack) { + + } } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchChangeNotifyExecuteTask.java b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchChangeNotifyExecuteTask.java new file mode 100644 index 00000000000..46cabbec2e7 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchChangeNotifyExecuteTask.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2023 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.push.v2.task; + +import com.alibaba.nacos.api.naming.remote.request.NamingFuzzyWatchChangeNotifyRequest; +import com.alibaba.nacos.api.remote.PushCallBack; +import com.alibaba.nacos.common.task.AbstractExecuteTask; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.push.v2.NoRequiredRetryException; +import com.alibaba.nacos.naming.push.v2.PushConfig; + +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_RESOURCE_CHANGED; + +/** + * Nacos naming fuzzy watch notify service change push delay task. + * + * @author tanyongquan + */ +public class FuzzyWatchChangeNotifyExecuteTask extends AbstractExecuteTask { + + private final String serviceKey; + + private final String changedType; + + private String clientId; + + private final FuzzyWatchPushDelayTaskEngine delayTaskEngine; + + public FuzzyWatchChangeNotifyExecuteTask(FuzzyWatchPushDelayTaskEngine delayTaskEngine, String serviceKey, + String changedType, String targetClient) { + this.serviceKey = serviceKey; + this.changedType = changedType; + this.clientId = targetClient; + this.delayTaskEngine = delayTaskEngine; + } + + @Override + public void run() { + + delayTaskEngine.getPushExecutor().doFuzzyWatchNotifyPushWithCallBack(clientId, + new NamingFuzzyWatchChangeNotifyRequest(serviceKey, changedType, FUZZY_WATCH_RESOURCE_CHANGED), + new FuzzyWatchChangeNotifyCallback(clientId, serviceKey, changedType)); + + } + + private class FuzzyWatchChangeNotifyCallback implements PushCallBack { + + private final String clientId; + + private String serviceKey; + + private String changedType; + + private FuzzyWatchChangeNotifyCallback(String clientId, String serviceKey, String changedType) { + this.clientId = clientId; + this.serviceKey = serviceKey; + this.changedType = changedType; + } + + @Override + public long getTimeout() { + return PushConfig.getInstance().getPushTaskTimeout(); + } + + @Override + public void onSuccess() { + Loggers.PUSH.info("[FUZZY-WATCH] change notify success ,clientId {}, serviceKey {] ,changedType {} ", + clientId, clientId, changedType); + + } + + @Override + public void onFail(Throwable e) { + + Loggers.PUSH.warn("[FUZZY-WATCH] change notify fail ,clientId {}, serviceKey {] ,changedType {} ", clientId, + clientId, changedType, e); + + if (!(e instanceof NoRequiredRetryException)) { + delayTaskEngine.addTask(System.currentTimeMillis(), + new FuzzyWatchChangeNotifyTask(serviceKey, changedType, clientId, + PushConfig.getInstance().getPushTaskRetryDelay())); + } + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchChangeNotifyTask.java b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchChangeNotifyTask.java new file mode 100644 index 00000000000..502cccdd9d8 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchChangeNotifyTask.java @@ -0,0 +1,64 @@ +/* + * Copyright 1999-2023 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.push.v2.task; + +import com.alibaba.nacos.common.task.AbstractDelayTask; + +/** + * Nacos naming fuzzy watch notify service change push delay task. + * + * @author tanyongquan + */ +public class FuzzyWatchChangeNotifyTask extends AbstractDelayTask { + + private final String serviceKey; + + private final String changedType; + + private final String clientId; + + private final long delay; + + public FuzzyWatchChangeNotifyTask(String serviceKey, String changedType, String clientId, long delay) { + this.serviceKey = serviceKey; + this.changedType = changedType; + this.delay = delay; + this.clientId = clientId; + + } + + public String getChangedType() { + return changedType; + } + + public String getClientId() { + return clientId; + } + + public long getDelay() { + return delay; + } + + public String getServiceKey() { + return serviceKey; + } + + @Override + public void merge(AbstractDelayTask task) { + + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchPushDelayTaskEngine.java b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchPushDelayTaskEngine.java new file mode 100644 index 00000000000..94e64d42f43 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchPushDelayTaskEngine.java @@ -0,0 +1,105 @@ +/* + * Copyright 1999-2023 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.push.v2.task; + +import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException; +import com.alibaba.nacos.common.task.NacosTask; +import com.alibaba.nacos.common.task.NacosTaskProcessor; +import com.alibaba.nacos.common.task.engine.NacosDelayTaskExecuteEngine; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.misc.NamingExecuteTaskDispatcher; +import com.alibaba.nacos.naming.misc.SwitchDomain; +import com.alibaba.nacos.naming.push.v2.executor.PushExecutor; +import com.alibaba.nacos.naming.push.v2.executor.PushExecutorDelegate; +import org.springframework.stereotype.Component; + +/** + * Nacos naming fuzzy watch notify service change push delay task execute engine. + * + * @author tanyongquan + */ +@Component +public class FuzzyWatchPushDelayTaskEngine extends NacosDelayTaskExecuteEngine { + + private final PushExecutorDelegate pushExecutor; + + private final SwitchDomain switchDomain; + + public FuzzyWatchPushDelayTaskEngine(PushExecutorDelegate pushExecutor, SwitchDomain switchDomain) { + super(FuzzyWatchPushDelayTaskEngine.class.getSimpleName(), Loggers.PUSH); + this.pushExecutor = pushExecutor; + this.switchDomain = switchDomain; + setDefaultTaskProcessor(new WatchPushDelayTaskProcessor(this)); + } + + public PushExecutor getPushExecutor() { + return pushExecutor; + } + + @Override + protected void processTasks() { + if (!switchDomain.isPushEnabled()) { + return; + } + super.processTasks(); + } + + private static class WatchPushDelayTaskProcessor implements NacosTaskProcessor { + + private final FuzzyWatchPushDelayTaskEngine fuzzyWatchPushExecuteEngine; + + public WatchPushDelayTaskProcessor(FuzzyWatchPushDelayTaskEngine fuzzyWatchPushExecuteEngine) { + this.fuzzyWatchPushExecuteEngine = fuzzyWatchPushExecuteEngine; + } + + @Override + public boolean process(NacosTask task) { + + if (task instanceof FuzzyWatchChangeNotifyTask) { + //process fuzzy watch change notify when a service changed + FuzzyWatchChangeNotifyTask fuzzyWatchChangeNotifyTask = (FuzzyWatchChangeNotifyTask) task; + NamingExecuteTaskDispatcher.getInstance().dispatchAndExecuteTask(getTaskKey(task), + new FuzzyWatchChangeNotifyExecuteTask(fuzzyWatchPushExecuteEngine, + fuzzyWatchChangeNotifyTask.getServiceKey(), fuzzyWatchChangeNotifyTask.getChangedType(), + fuzzyWatchChangeNotifyTask.getClientId())); + } else if (task instanceof FuzzyWatchSyncNotifyTask) { + //process fuzzy watch sync notify when a new client fuzzy watch a pattern + FuzzyWatchSyncNotifyTask fuzzyWatchSyncNotifyTask = (FuzzyWatchSyncNotifyTask) task; + String pattern = fuzzyWatchSyncNotifyTask.getPattern(); + String clientId = fuzzyWatchSyncNotifyTask.getClientId(); + NamingExecuteTaskDispatcher.getInstance().dispatchAndExecuteTask(getTaskKey(task), + new FuzzyWatchSyncNotifyExecuteTask(clientId, pattern, fuzzyWatchPushExecuteEngine, + fuzzyWatchSyncNotifyTask)); + } + return true; + } + + } + + public static String getTaskKey(NacosTask task) { + if (task instanceof FuzzyWatchChangeNotifyTask) { + return "fwcnT-" + ((FuzzyWatchChangeNotifyTask) task).getClientId() + + ((FuzzyWatchChangeNotifyTask) task).getServiceKey(); + } else if (task instanceof FuzzyWatchSyncNotifyTask) { + return "fwsnT-" + ((FuzzyWatchSyncNotifyTask) task).getSyncType() + "-" + + ((FuzzyWatchSyncNotifyTask) task).getClientId() + ((FuzzyWatchSyncNotifyTask) task).getPattern() + + "-" + ((FuzzyWatchSyncNotifyTask) task).getCurrentBatch(); + } else { + throw new NacosRuntimeException(500, "unknown fuzzy task type"); + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchSyncNotifyExecuteTask.java b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchSyncNotifyExecuteTask.java new file mode 100644 index 00000000000..243cb6336a8 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchSyncNotifyExecuteTask.java @@ -0,0 +1,158 @@ +/* + * Copyright 1999-2023 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.push.v2.task; + +import com.alibaba.nacos.api.naming.remote.request.NamingFuzzyWatchSyncRequest; +import com.alibaba.nacos.api.remote.PushCallBack; +import com.alibaba.nacos.common.task.AbstractExecuteTask; +import com.alibaba.nacos.common.task.BatchTaskCounter; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.push.v2.NoRequiredRetryException; +import com.alibaba.nacos.naming.push.v2.PushConfig; + +import static com.alibaba.nacos.api.common.Constants.FINISH_FUZZY_WATCH_INIT_NOTIFY; +import static com.alibaba.nacos.api.common.Constants.FUZZY_WATCH_INIT_NOTIFY; + +/** + * Nacos naming fuzzy watch initial push execute task. + * + * @author tanyongquan + */ +public class FuzzyWatchSyncNotifyExecuteTask extends AbstractExecuteTask { + + private final String clientId; + + private final String pattern; + + private final FuzzyWatchPushDelayTaskEngine fuzzyWatchPushDelayTaskEngine; + + private final FuzzyWatchSyncNotifyTask delayTask; + + /** + * Fuzzy watch origin push matched service size, if there is no failure while executing push, + * {@code originSize == latch}. just use to record log after finish all push. + */ + private final int originSize; + + /** + * TODO set batch size from config. + */ + + public FuzzyWatchSyncNotifyExecuteTask(String clientId, String pattern, + FuzzyWatchPushDelayTaskEngine fuzzyWatchPushDelayTaskEngine, FuzzyWatchSyncNotifyTask delayTask) { + this.clientId = clientId; + this.pattern = pattern; + this.fuzzyWatchPushDelayTaskEngine = fuzzyWatchPushDelayTaskEngine; + this.delayTask = delayTask; + this.originSize = delayTask.getSyncServiceKeys().size(); + } + + @Override + public void run() { + + NamingFuzzyWatchSyncRequest namingFuzzyWatchSyncRequest = NamingFuzzyWatchSyncRequest.buildSyncNotifyRequest( + pattern, delayTask.getSyncType(), delayTask.getSyncServiceKeys(), delayTask.getTotalBatch(), + delayTask.getCurrentBatch()); + fuzzyWatchPushDelayTaskEngine.getPushExecutor() + .doFuzzyWatchNotifyPushWithCallBack(clientId, namingFuzzyWatchSyncRequest, + new FuzzyWatchSyncNotifyExecuteTask.FuzzyWatchSyncNotifyCallback(namingFuzzyWatchSyncRequest, + delayTask.getBatchTaskCounter())); + } + + private class FuzzyWatchSyncNotifyCallback implements PushCallBack { + + private NamingFuzzyWatchSyncRequest namingFuzzyWatchSyncRequest; + + /** + * Record the push task execute start time. + */ + private final long executeStartTime; + + private BatchTaskCounter batchTaskCounter; + + private FuzzyWatchSyncNotifyCallback(NamingFuzzyWatchSyncRequest namingFuzzyWatchSyncRequest, + BatchTaskCounter batchTaskCounter) { + this.namingFuzzyWatchSyncRequest = namingFuzzyWatchSyncRequest; + this.executeStartTime = System.currentTimeMillis(); + this.batchTaskCounter = batchTaskCounter; + } + + @Override + public long getTimeout() { + return PushConfig.getInstance().getPushTaskTimeout(); + } + + @Override + public void onSuccess() { + long pushFinishTime = System.currentTimeMillis(); + long pushCostTimeForNetWork = pushFinishTime - executeStartTime; + long pushCostTimeForAll = pushFinishTime - delayTask.getLastProcessTime(); + + if (isFinishInitTask()) { + Loggers.PUSH.info( + "[FUZZY-WATCH-SYNC-COMPLETE] {}ms, all delay time {}ms for client {} watch init push finish," + + " pattern {}, all push service size {}", pushCostTimeForNetWork, pushCostTimeForAll, + clientId, pattern, originSize); + } else { + Loggers.PUSH.info( + "[FUZZY-WATCH-PUSH-SUCC] {}ms, all delay time {}ms for client {}, pattern {}, syncType={},push size {},currentBatch={}", + pushCostTimeForNetWork, pushCostTimeForAll, clientId, pattern, + namingFuzzyWatchSyncRequest.getSyncType(), namingFuzzyWatchSyncRequest.getContexts().size(), + namingFuzzyWatchSyncRequest.getCurrentBatch()); + // if total batch is success sync to client send + if (isInitNotifyTask()) { + batchTaskCounter.batchSuccess(namingFuzzyWatchSyncRequest.getCurrentBatch()); + if (batchTaskCounter.batchCompleted()) { + fuzzyWatchPushDelayTaskEngine.addTask(System.currentTimeMillis(), + new FuzzyWatchSyncNotifyTask(clientId, pattern, FINISH_FUZZY_WATCH_INIT_NOTIFY, null, + PushConfig.getInstance().getPushTaskDelay())); + } + } + + } + } + + private boolean isFinishInitTask() { + return FINISH_FUZZY_WATCH_INIT_NOTIFY.equals(namingFuzzyWatchSyncRequest.getSyncType()); + } + + private boolean isInitNotifyTask() { + return FUZZY_WATCH_INIT_NOTIFY.equals(namingFuzzyWatchSyncRequest.getSyncType()); + } + + @Override + public void onFail(Throwable e) { + long pushCostTime = System.currentTimeMillis() - executeStartTime; + Loggers.PUSH.error( + "[FUZZY-WATCH-SYNC-PUSH-FAIL] {}ms, pattern {} match {} service: {}, currentBatch={},reason={}, client={}", + pushCostTime, pattern, namingFuzzyWatchSyncRequest.getContexts().size(), + namingFuzzyWatchSyncRequest.getCurrentBatch(), e.getMessage(), clientId); + if (!(e instanceof NoRequiredRetryException)) { + Loggers.PUSH.error("Reason detail: ", e); + //resend request only + FuzzyWatchSyncNotifyTask fuzzyWatchSyncNotifyTask = new FuzzyWatchSyncNotifyTask(clientId, pattern, + delayTask.getSyncType(), namingFuzzyWatchSyncRequest.getContexts(), + PushConfig.getInstance().getPushTaskRetryDelay()); + fuzzyWatchSyncNotifyTask.setBatchTaskCounter(batchTaskCounter); + + fuzzyWatchPushDelayTaskEngine.addTask(System.currentTimeMillis(), fuzzyWatchSyncNotifyTask); + + } + } + } + +} \ No newline at end of file diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchSyncNotifyTask.java b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchSyncNotifyTask.java new file mode 100644 index 00000000000..fa4c633f206 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/v2/task/FuzzyWatchSyncNotifyTask.java @@ -0,0 +1,116 @@ +/* + * Copyright 1999-2023 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.push.v2.task; + +import com.alibaba.nacos.api.naming.remote.request.NamingFuzzyWatchSyncRequest; +import com.alibaba.nacos.common.task.AbstractDelayTask; +import com.alibaba.nacos.common.task.BatchTaskCounter; +import com.alibaba.nacos.naming.misc.Loggers; + +import java.util.HashSet; +import java.util.Set; + +/** + * Nacos naming fuzzy watch initial push delay task. + * + * @author tanyongquan + */ +public class FuzzyWatchSyncNotifyTask extends AbstractDelayTask { + + private final String clientId; + + private final String pattern; + + private final Set syncServiceKeys; + + private final String syncType; + + private int totalBatch; + + private int currentBatch; + + private BatchTaskCounter batchTaskCounter; + + public FuzzyWatchSyncNotifyTask(String clientId, String pattern, String syncType, + Set syncServiceKeys, long delay) { + this.clientId = clientId; + this.pattern = pattern; + this.syncType = syncType; + if (syncServiceKeys != null) { + this.syncServiceKeys = syncServiceKeys; + } else { + this.syncServiceKeys = new HashSet<>(); + } + setTaskInterval(delay); + setLastProcessTime(System.currentTimeMillis()); + } + + public int getTotalBatch() { + return totalBatch; + } + + public void setTotalBatch(int totalBatch) { + this.totalBatch = totalBatch; + } + + public int getCurrentBatch() { + return currentBatch; + } + + public void setCurrentBatch(int currentBatch) { + this.currentBatch = currentBatch; + } + + @Override + public void merge(AbstractDelayTask task) { + if (!(task instanceof FuzzyWatchSyncNotifyTask)) { + return; + } + FuzzyWatchSyncNotifyTask oldTask = (FuzzyWatchSyncNotifyTask) task; + + if (oldTask.getSyncServiceKeys() != null) { + syncServiceKeys.addAll(oldTask.getSyncServiceKeys()); + } + setLastProcessTime(Math.min(getLastProcessTime(), task.getLastProcessTime())); + Loggers.PUSH.info("[FUZZY-WATCH-INIT-PUSH] Task merge for pattern {}", pattern); + } + + public String getPattern() { + return pattern; + } + + public Set getSyncServiceKeys() { + return syncServiceKeys; + } + + public String getSyncType() { + return syncType; + } + + public String getClientId() { + return clientId; + } + + public BatchTaskCounter getBatchTaskCounter() { + return batchTaskCounter; + } + + public void setBatchTaskCounter(BatchTaskCounter batchTaskCounter) { + this.batchTaskCounter = batchTaskCounter; + } + +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/NamingFuzzyWatchRequestHandler.java b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/NamingFuzzyWatchRequestHandler.java new file mode 100644 index 00000000000..782038fdac8 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/NamingFuzzyWatchRequestHandler.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2023 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.remote.rpc.handler; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.remote.request.NamingFuzzyWatchRequest; +import com.alibaba.nacos.api.naming.remote.response.NamingFuzzyWatchResponse; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.core.remote.RequestHandler; +import com.alibaba.nacos.naming.core.v2.event.client.ClientOperationEvent; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import org.springframework.stereotype.Component; + +import static com.alibaba.nacos.api.common.Constants.WATCH_TYPE_CANCEL_WATCH; +import static com.alibaba.nacos.api.common.Constants.WATCH_TYPE_WATCH; + +/** + * Fuzzy watch service request handler. + * + * @author tanyongquan + */ +@Component("namingFuzzyWatchRequestHandler") +public class NamingFuzzyWatchRequestHandler extends RequestHandler { + + public NamingFuzzyWatchRequestHandler() { + NotifyCenter.registerToPublisher(ClientOperationEvent.ClientFuzzyWatchEvent.class, 1000); + NotifyCenter.registerToPublisher(ClientOperationEvent.ClientCancelFuzzyWatchEvent.class, 1000); + + } + + @Override + @Secured(action = ActionTypes.READ) + public NamingFuzzyWatchResponse handle(NamingFuzzyWatchRequest request, RequestMeta meta) throws NacosException { + + String groupKeyPattern = request.getGroupKeyPattern(); + switch (request.getWatchType()) { + case WATCH_TYPE_WATCH: + NotifyCenter.publishEvent( + new ClientOperationEvent.ClientFuzzyWatchEvent(groupKeyPattern, meta.getConnectionId(), + request.getReceivedGroupKeys(), request.isInitializing())); + return NamingFuzzyWatchResponse.buildSuccessResponse(); + case WATCH_TYPE_CANCEL_WATCH: + NotifyCenter.publishEvent( + new ClientOperationEvent.ClientCancelFuzzyWatchEvent(groupKeyPattern, meta.getConnectionId())); + return NamingFuzzyWatchResponse.buildSuccessResponse(); + default: + throw new NacosException(NacosException.INVALID_PARAM, + String.format("Unsupported request type %s", request.getWatchType())); + } + } +} diff --git a/naming/src/test/java/com/alibaba/nacos/naming/core/v2/index/ClientServiceIndexesManagerTest.java b/naming/src/test/java/com/alibaba/nacos/naming/core/v2/index/ClientServiceIndexesManagerTest.java index 0c9bc8676a4..c6a3c6c7687 100644 --- a/naming/src/test/java/com/alibaba/nacos/naming/core/v2/index/ClientServiceIndexesManagerTest.java +++ b/naming/src/test/java/com/alibaba/nacos/naming/core/v2/index/ClientServiceIndexesManagerTest.java @@ -120,7 +120,7 @@ void testSubscribeTypes() { List> classes = clientServiceIndexesManager.subscribeTypes(); assertNotNull(classes); - assertEquals(5, classes.size()); + assertEquals(7, classes.size()); } @Test diff --git a/naming/src/test/java/com/alibaba/nacos/naming/push/v2/NamingSubscriberServiceV2ImplTest.java b/naming/src/test/java/com/alibaba/nacos/naming/push/v2/NamingSubscriberServiceV2ImplTest.java index 84db805b5e5..abdce5ad864 100644 --- a/naming/src/test/java/com/alibaba/nacos/naming/push/v2/NamingSubscriberServiceV2ImplTest.java +++ b/naming/src/test/java/com/alibaba/nacos/naming/push/v2/NamingSubscriberServiceV2ImplTest.java @@ -16,10 +16,12 @@ package com.alibaba.nacos.naming.push.v2; +import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.naming.core.v2.client.Client; import com.alibaba.nacos.naming.core.v2.client.manager.ClientManagerDelegate; import com.alibaba.nacos.naming.core.v2.event.service.ServiceEvent; import com.alibaba.nacos.naming.core.v2.index.ClientServiceIndexesManager; +import com.alibaba.nacos.naming.core.v2.index.NamingFuzzyWatchContextService; import com.alibaba.nacos.naming.core.v2.pojo.Service; import com.alibaba.nacos.naming.misc.SwitchDomain; import com.alibaba.nacos.naming.pojo.Subscriber; @@ -61,6 +63,9 @@ class NamingSubscriberServiceV2ImplTest { @Mock private ClientServiceIndexesManager indexesManager; + @Mock + private NamingFuzzyWatchContextService namingFuzzyWatchContextService; + @Mock private PushDelayTaskExecuteEngine delayTaskEngine; @@ -74,10 +79,12 @@ class NamingSubscriberServiceV2ImplTest { @BeforeEach void setUp() throws Exception { - subscriberService = new NamingSubscriberServiceV2Impl(clientManager, indexesManager, null, null, null, switchDomain); + subscriberService = new NamingSubscriberServiceV2Impl(clientManager, indexesManager, null, null, null, + switchDomain); ReflectionTestUtils.setField(subscriberService, "delayTaskEngine", delayTaskEngine); when(indexesManager.getAllClientsSubscribeService(service)).thenReturn(Collections.singletonList(testClientId)); - when(indexesManager.getAllClientsSubscribeService(service1)).thenReturn(Collections.singletonList(testClientId)); + when(indexesManager.getAllClientsSubscribeService(service1)).thenReturn( + Collections.singletonList(testClientId)); Collection services = new LinkedList<>(); services.add(service); services.add(service1); @@ -91,7 +98,8 @@ void setUp() throws Exception { @Test void testGetSubscribersByString() { - Collection actual = subscriberService.getSubscribers(service.getNamespace(), service.getGroupedServiceName()); + Collection actual = subscriberService.getSubscribers(service.getNamespace(), + service.getGroupedServiceName()); assertEquals(1, actual.size()); assertEquals(service.getGroupedServiceName(), actual.iterator().next().getServiceName()); } @@ -105,7 +113,8 @@ void testGetSubscribersByService() { @Test void testGetFuzzySubscribersByString() { - Collection actual = subscriberService.getFuzzySubscribers(service.getNamespace(), service.getGroupedServiceName()); + Collection actual = subscriberService.getFuzzySubscribers(service.getNamespace(), + service.getGroupedServiceName()); assertEquals(2, actual.size()); } @@ -116,8 +125,9 @@ void testGetFuzzySubscribersByService() { } @Test - void onEvent() { - subscriberService.onEvent(new ServiceEvent.ServiceChangedEvent(service)); + public void onEvent() { + subscriberService.onEvent( + new ServiceEvent.ServiceChangedEvent(service, Constants.ServiceChangedType.ADD_SERVICE)); verify(delayTaskEngine).addTask(eq(service), any(PushDelayTask.class)); } } diff --git a/naming/src/test/java/com/alibaba/nacos/naming/push/v2/task/FixturePushExecutor.java b/naming/src/test/java/com/alibaba/nacos/naming/push/v2/task/FixturePushExecutor.java index f26fb516a50..6a9db2583a0 100644 --- a/naming/src/test/java/com/alibaba/nacos/naming/push/v2/task/FixturePushExecutor.java +++ b/naming/src/test/java/com/alibaba/nacos/naming/push/v2/task/FixturePushExecutor.java @@ -16,6 +16,8 @@ package com.alibaba.nacos.naming.push.v2.task; +import com.alibaba.nacos.api.naming.remote.request.AbstractFuzzyWatchNotifyRequest; +import com.alibaba.nacos.api.remote.PushCallBack; import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.naming.push.v2.PushDataWrapper; import com.alibaba.nacos.naming.push.v2.executor.PushExecutor; @@ -39,6 +41,16 @@ public void doPushWithCallback(String clientId, Subscriber subscriber, PushDataW } } + @Override + public void doFuzzyWatchNotifyPushWithCallBack(String clientId, AbstractFuzzyWatchNotifyRequest watchNotifyRequest, + PushCallBack callBack) { + if (shouldSuccess) { + callBack.onSuccess(); + } else { + callBack.onFail(failedException); + } + } + public void setShouldSuccess(boolean shouldSuccess) { this.shouldSuccess = shouldSuccess; } diff --git a/pom.xml b/pom.xml index bd03acffbb1..1c96d9a234a 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ - 3.0.0-ALPHA + 3.0.0-ALPHA-lzf-SNAPSHOT UTF-8 UTF-8