Skip to content

Commit

Permalink
Feature : Agent mode supports dynamic thread pool changes, alarm and …
Browse files Browse the repository at this point in the history
…monitoring functions without modifying code, and adapts to Nacos and Apollo Configuration Centers (#1572)

* fix:Fix send threadPool change notification message log

* feat:Agent Nacos dynamic refresh Initialize

* feat:Agent dynamic alarm Initialize

* feat:Agent dynamic monitor Initialize

* refactor:Agent Listener logic, add configuration refreshes platform push, and carries the unique application ID

* feat:Apollo Configuration Center Plugin Logic Adaptation

* feat:Completed the implementation of Nacos Configuration Center plugin and Nacos,Apollo plugins adapted to Spring 1.x , 2.x environment

* fix: Fixed jar package mounting and startup problem in Agent mode
  • Loading branch information
Pan-YuJie authored Sep 30, 2024
1 parent 9bdb810 commit c521a97
Show file tree
Hide file tree
Showing 69 changed files with 3,130 additions and 250 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@

package cn.hippo4j.agent.core.conf;

import cn.hippo4j.agent.core.boot.AgentPackagePath;
import cn.hippo4j.agent.core.util.PropertyPlaceholderHelper;
import cn.hippo4j.agent.core.util.StringUtil;
import cn.hippo4j.common.boot.AgentPackageNotFoundException;
import cn.hippo4j.common.boot.AgentPackagePath;
import cn.hippo4j.common.conf.Config;
import cn.hippo4j.common.conf.ConfigNotFoundException;
import cn.hippo4j.common.logging.api.ILog;
import cn.hippo4j.common.logging.api.LogManager;
import cn.hippo4j.common.logging.core.JsonLogResolver;
import cn.hippo4j.common.logging.core.PatternLogResolver;
import cn.hippo4j.common.toolkit.StringUtil;
import cn.hippo4j.common.toolkit.agent.ConfigInitializer;
import cn.hippo4j.common.toolkit.agent.PropertyPlaceholderHelper;

import java.io.File;
import java.io.FileInputStream;
Expand Down
10 changes: 10 additions & 0 deletions agent/hippo4j-agent-plugin/apollo-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@
</properties>

<dependencies>
<dependency>
<groupId>cn.hippo4j</groupId>
<artifactId>hippo4j-agent-spring-plugin-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.hippo4j</groupId>
<artifactId>hippo4j-threadpool-dynamic-mode-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
* limitations under the License.
*/

package cn.hippo4j.agent.plugin.spring.boot.v2;
package cn.hippo4j.agent.plugin.apollo;

import cn.hippo4j.agent.plugin.spring.common.conf.SpringBootConfig;
import cn.hippo4j.agent.plugin.spring.common.toolkit.SpringPropertyBinder;
import cn.hippo4j.common.logging.api.ILog;
import cn.hippo4j.common.logging.api.LogManager;
import cn.hippo4j.agent.plugin.spring.common.conf.SpringBootConfig;
import cn.hippo4j.threadpool.dynamic.mode.config.properties.BootstrapConfigProperties;
import cn.hippo4j.threadpool.dynamic.mode.config.refresher.AbstractConfigThreadPoolDynamicRefresh;
import com.ctrip.framework.apollo.Config;
Expand All @@ -28,10 +29,7 @@
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.model.ConfigChange;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;

import java.util.HashMap;
import java.util.List;
Expand All @@ -40,12 +38,15 @@
import static cn.hippo4j.agent.core.conf.Constants.SPRING_BOOT_CONFIG_PREFIX;

/**
* Dynamic thread pool change handler spring 2x
* Dynamic thread pool change handler
*/
public class DynamicThreadPoolChangeHandlerSpring2x extends AbstractConfigThreadPoolDynamicRefresh {
public class ApolloDynamicThreadPoolChangeHandler extends AbstractConfigThreadPoolDynamicRefresh {

private static ILog LOGGER = LogManager.getLogger(DynamicThreadPoolChangeHandlerSpring2x.class);
private static final ILog LOGGER = LogManager.getLogger(ApolloDynamicThreadPoolChangeHandler.class);

/**
* Registers a listener with Apollo to monitor for changes in the thread pool configuration.
*/
@Override
public void registerListener() {
List<String> apolloNamespaces = SpringBootConfig.Spring.Dynamic.Thread_Pool.Apollo.NAMESPACE;
Expand All @@ -68,11 +69,19 @@ public void registerListener() {
LOGGER.info("[Hippo4j-Agent] Dynamic thread pool refresher, add apollo listener success. namespace: {}", namespace);
}

/**
* Builds and binds the {@link BootstrapConfigProperties} from the given configuration map.
* <p>
* This method uses Spring's {@link Binder} to bind the configuration values to an instance
* of {@link BootstrapConfigProperties}, which can then be used to configure the thread pool
* dynamically.
*
* @param configInfo the configuration map containing properties to bind.
* @return the bound {@link BootstrapConfigProperties} instance.
*/
@Override
public BootstrapConfigProperties buildBootstrapProperties(Map<Object, Object> configInfo) {
BootstrapConfigProperties bindableBootstrapConfigProperties = new BootstrapConfigProperties();
ConfigurationPropertySource sources = new MapConfigurationPropertySource(configInfo);
Binder binder = new Binder(sources);
return binder.bind(BootstrapConfigProperties.PREFIX, Bindable.ofInstance(bindableBootstrapConfigProperties)).get();
BootstrapConfigProperties bindableBootstrapConfigProperties = SpringPropertyBinder.bindProperties(configInfo, BootstrapConfigProperties.PREFIX, BootstrapConfigProperties.class);
return bindableBootstrapConfigProperties;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class ApolloInstrumentation extends ClassInstanceMethodsEnhancePluginDefi

private static final String ENHANCE_CLASS = "com.ctrip.framework.apollo.internals.DefaultConfig";

private static final String CONSTRUCTOR_INTERCEPT_CLASS = "cn.hippo4j.agent.plugin.apollo.interceptor.DefaultConfigConstructorInterceptor";
private static final String CONSTRUCTOR_INTERCEPT_CLASS = "cn.hippo4j.agent.plugin.apollo.interceptor.ApolloConfigConstructorInterceptor";

@Override
protected ClassMatch enhanceClass() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cn.hippo4j.agent.plugin.apollo.interceptor;

import cn.hippo4j.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import cn.hippo4j.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
import cn.hippo4j.agent.plugin.apollo.listeners.ApolloConfigPropertiesLoaderCompletedListener;
import cn.hippo4j.common.extension.design.AbstractSubjectCenter;

import java.util.concurrent.atomic.AtomicBoolean;

/**
* Default config constructor interceptor
*/
public class ApolloConfigConstructorInterceptor implements InstanceConstructorInterceptor {

private static final AtomicBoolean isExecuted = new AtomicBoolean(false);

@Override
public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable {

// This logic will only be executed once
if (isExecuted.compareAndSet(false, true)) {
// The Apollo plugin triggers before the Spring configuration plug-in.
// This means that when the Apollo plug-in executes, Spring's Environment is not yet ready,
// so the configuration cannot be read
// After listening to the AGENT_SPRING_PROPERTIES_LOADER_COMPLETED event, register the listener for Apollo
AbstractSubjectCenter.register(AbstractSubjectCenter.SubjectType.AGENT_SPRING_PROPERTIES_LOADER_COMPLETED,
new ApolloConfigPropertiesLoaderCompletedListener());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cn.hippo4j.agent.plugin.apollo.listeners;

import cn.hippo4j.agent.plugin.apollo.ApolloDynamicThreadPoolChangeHandler;
import cn.hippo4j.common.extension.design.Observer;
import cn.hippo4j.common.extension.design.ObserverMessage;
import cn.hippo4j.threadpool.dynamic.api.ThreadPoolDynamicRefresh;

/**
* Apollo Config Properties Loader Completed Listener
*/
public class ApolloConfigPropertiesLoaderCompletedListener implements Observer<String> {

@Override
public void accept(ObserverMessage<String> observerMessage) {
ThreadPoolDynamicRefresh dynamicRefresh = new ApolloDynamicThreadPoolChangeHandler();
dynamicRefresh.registerListener();
}
}
37 changes: 37 additions & 0 deletions agent/hippo4j-agent-plugin/nacos-plugin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.hippo4j</groupId>
<artifactId>hippo4j-agent-plugin</artifactId>
<version>${revision}</version>
</parent>

<artifactId>hippo4j-agent-nacos-plugin</artifactId>

<properties>
<nacos.version>2.2.1</nacos.version>
</properties>

<dependencies>
<dependency>
<groupId>cn.hippo4j</groupId>
<artifactId>hippo4j-agent-spring-plugin-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.hippo4j</groupId>
<artifactId>hippo4j-threadpool-dynamic-mode-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${nacos.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cn.hippo4j.agent.plugin.nacos;

import cn.hippo4j.agent.plugin.spring.common.conf.NacosCloudConfig;
import cn.hippo4j.agent.plugin.spring.common.conf.NacosConfig;
import cn.hippo4j.agent.plugin.spring.common.conf.SpringBootConfig;
import cn.hippo4j.agent.plugin.spring.common.toolkit.SpringPropertyBinder;
import cn.hippo4j.common.executor.ThreadFactoryBuilder;
import cn.hippo4j.common.logging.api.ILog;
import cn.hippo4j.common.logging.api.LogManager;
import cn.hippo4j.common.toolkit.StringUtil;
import cn.hippo4j.threadpool.dynamic.mode.config.parser.ConfigParserHandler;
import cn.hippo4j.threadpool.dynamic.mode.config.properties.BootstrapConfigProperties;
import cn.hippo4j.threadpool.dynamic.mode.config.refresher.AbstractConfigThreadPoolDynamicRefresh;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import org.springframework.boot.context.properties.bind.Binder;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledThreadPoolExecutor;

import static cn.hippo4j.common.constant.Constants.DEFAULT_NAMESPACE_ID;

/**
* NacosDynamicThreadPoolChangeHandler is responsible for handling dynamic thread pool
* configuration changes in a Spring environment by listening to configuration updates from Nacos.
* <p>
* This class extends {@link AbstractConfigThreadPoolDynamicRefresh} and implements the logic
* to register a Nacos listener, handle configuration changes, and dynamically refresh the thread pool
* properties based on the new configuration.
* <p>
*/
public class NacosDynamicThreadPoolChangeHandler extends AbstractConfigThreadPoolDynamicRefresh {

private static final ILog LOGGER = LogManager.getLogger(NacosDynamicThreadPoolChangeHandler.class);

/**
* Registers a listener with Nacos to monitor for changes in the thread pool configuration.
* <p>
* This method sets up the Nacos {@link ConfigService} with the server address and namespace
* from the Spring Boot configuration. It then adds a listener that will receive and process
* configuration updates, triggering a dynamic refresh of thread pool settings.
*/
@Override
public void registerListener() {
// Retrieve necessary configuration properties
String configFileType = SpringBootConfig.Spring.Dynamic.Thread_Pool.CONFIG_FILE_TYPE;
String serverAddr = Optional.ofNullable(NacosCloudConfig.Spring.Cloud.Nacos.Config.SERVER_ADDR).filter(s -> !StringUtil.isEmpty(s))
.orElse(Optional.ofNullable(NacosConfig.Nacos.Config.SERVER_ADDR).filter(s -> !StringUtil.isEmpty(s))
.orElse(""));
if (StringUtil.isEmpty(serverAddr)) {
LOGGER.error("[Hippo4j-Agent] add Nacos listener failure. Nacos Registry address not configured");
return;
}
String dataId = SpringBootConfig.Spring.Dynamic.Thread_Pool.Nacos.DATA_ID;
String group = SpringBootConfig.Spring.Dynamic.Thread_Pool.Nacos.GROUP;
String namespace = SpringBootConfig.Spring.Dynamic.Thread_Pool.Nacos.NAMESPACE.get(0);
namespace = namespace.equals(DEFAULT_NAMESPACE_ID) ? "" : namespace;
try {
// Initialize Nacos ConfigService with the provided properties
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
properties.put(PropertyKeyConst.NAMESPACE, namespace);
ConfigService configService = NacosFactory.createConfigService(properties);

// Define the listener to handle configuration changes
Listener configChangeListener = new Listener() {

@Override
public void receiveConfigInfo(String configInfo) {
LOGGER.debug("Received configuration: " + configInfo);
Map<String, Object> changeValueMap = new HashMap<>();
try {
// Parse the configuration and map the values to the appropriate keys
Map<Object, Object> configInfoMap = ConfigParserHandler.getInstance().parseConfig(configInfo, configFileType);
configInfoMap.forEach((key, value) -> {
if (key instanceof String) {
changeValueMap.put((String) key, value);
}
});
} catch (IOException e) {
LOGGER.error(e, "[Hippo4j-Agent] Dynamic thread pool refresher, Failed to resolve configuration. configFileType: {} configInfo: {} ", configFileType, configInfo);
}
// Trigger the dynamic refresh with the parsed configuration
dynamicRefresh(configFileType, configInfo, changeValueMap);
}

@Override
public Executor getExecutor() {
return new ScheduledThreadPoolExecutor(1, ThreadFactoryBuilder.builder().daemon(true).prefix("client.dynamic.refresh.agent").build());
}
};
// Add the listener to the Nacos ConfigService
configService.addListener(dataId, group, configChangeListener);
LOGGER.info("[Hippo4j-Agent] Dynamic thread pool refresher, add Nacos listener successfully. serverAddr: {} namespace: {} data-id: {} group: {}", serverAddr, namespace, dataId, group);
} catch (Exception e) {
LOGGER.error(e, "[Hippo4j-Agent] Dynamic thread pool refresher, add Nacos listener failure. serverAddr: {} namespace: {} data-id: {} group: {}", serverAddr, namespace, dataId, group);
}
}

/**
* Builds and binds the {@link BootstrapConfigProperties} from the given configuration map.
* <p>
* This method uses Spring's {@link Binder} to bind the configuration values to an instance
* of {@link BootstrapConfigProperties}, which can then be used to configure the thread pool
* dynamically.
*
* @param configInfo the configuration map containing properties to bind.
* @return the bound {@link BootstrapConfigProperties} instance.
*/
@Override
public BootstrapConfigProperties buildBootstrapProperties(Map<Object, Object> configInfo) {
BootstrapConfigProperties bindableBootstrapConfigProperties = SpringPropertyBinder.bindProperties(configInfo, BootstrapConfigProperties.PREFIX, BootstrapConfigProperties.class);
return bindableBootstrapConfigProperties;
}
}
Loading

0 comments on commit c521a97

Please sign in to comment.