Skip to content

Commit c521a97

Browse files
authored
Feature : Agent mode supports dynamic thread pool changes, alarm and 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
1 parent 9bdb810 commit c521a97

File tree

69 files changed

+3130
-250
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+3130
-250
lines changed

agent/hippo4j-agent-core/src/main/java/cn/hippo4j/agent/core/conf/SnifferConfigInitializer.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@
1717

1818
package cn.hippo4j.agent.core.conf;
1919

20+
import cn.hippo4j.agent.core.boot.AgentPackagePath;
21+
import cn.hippo4j.agent.core.util.PropertyPlaceholderHelper;
22+
import cn.hippo4j.agent.core.util.StringUtil;
2023
import cn.hippo4j.common.boot.AgentPackageNotFoundException;
21-
import cn.hippo4j.common.boot.AgentPackagePath;
2224
import cn.hippo4j.common.conf.Config;
2325
import cn.hippo4j.common.conf.ConfigNotFoundException;
2426
import cn.hippo4j.common.logging.api.ILog;
2527
import cn.hippo4j.common.logging.api.LogManager;
2628
import cn.hippo4j.common.logging.core.JsonLogResolver;
2729
import cn.hippo4j.common.logging.core.PatternLogResolver;
28-
import cn.hippo4j.common.toolkit.StringUtil;
2930
import cn.hippo4j.common.toolkit.agent.ConfigInitializer;
30-
import cn.hippo4j.common.toolkit.agent.PropertyPlaceholderHelper;
3131

3232
import java.io.File;
3333
import java.io.FileInputStream;

agent/hippo4j-agent-plugin/apollo-plugin/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@
1616
</properties>
1717

1818
<dependencies>
19+
<dependency>
20+
<groupId>cn.hippo4j</groupId>
21+
<artifactId>hippo4j-agent-spring-plugin-common</artifactId>
22+
<version>${project.version}</version>
23+
</dependency>
24+
<dependency>
25+
<groupId>cn.hippo4j</groupId>
26+
<artifactId>hippo4j-threadpool-dynamic-mode-config</artifactId>
27+
<version>${project.version}</version>
28+
</dependency>
1929
<dependency>
2030
<groupId>com.ctrip.framework.apollo</groupId>
2131
<artifactId>apollo-client</artifactId>
Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
* limitations under the License.
1616
*/
1717

18-
package cn.hippo4j.agent.plugin.spring.boot.v2;
18+
package cn.hippo4j.agent.plugin.apollo;
1919

20+
import cn.hippo4j.agent.plugin.spring.common.conf.SpringBootConfig;
21+
import cn.hippo4j.agent.plugin.spring.common.toolkit.SpringPropertyBinder;
2022
import cn.hippo4j.common.logging.api.ILog;
2123
import cn.hippo4j.common.logging.api.LogManager;
22-
import cn.hippo4j.agent.plugin.spring.common.conf.SpringBootConfig;
2324
import cn.hippo4j.threadpool.dynamic.mode.config.properties.BootstrapConfigProperties;
2425
import cn.hippo4j.threadpool.dynamic.mode.config.refresher.AbstractConfigThreadPoolDynamicRefresh;
2526
import com.ctrip.framework.apollo.Config;
@@ -28,10 +29,7 @@
2829
import com.ctrip.framework.apollo.ConfigService;
2930
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
3031
import com.ctrip.framework.apollo.model.ConfigChange;
31-
import org.springframework.boot.context.properties.bind.Bindable;
3232
import org.springframework.boot.context.properties.bind.Binder;
33-
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
34-
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
3533

3634
import java.util.HashMap;
3735
import java.util.List;
@@ -40,12 +38,15 @@
4038
import static cn.hippo4j.agent.core.conf.Constants.SPRING_BOOT_CONFIG_PREFIX;
4139

4240
/**
43-
* Dynamic thread pool change handler spring 2x
41+
* Dynamic thread pool change handler
4442
*/
45-
public class DynamicThreadPoolChangeHandlerSpring2x extends AbstractConfigThreadPoolDynamicRefresh {
43+
public class ApolloDynamicThreadPoolChangeHandler extends AbstractConfigThreadPoolDynamicRefresh {
4644

47-
private static ILog LOGGER = LogManager.getLogger(DynamicThreadPoolChangeHandlerSpring2x.class);
45+
private static final ILog LOGGER = LogManager.getLogger(ApolloDynamicThreadPoolChangeHandler.class);
4846

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

72+
/**
73+
* Builds and binds the {@link BootstrapConfigProperties} from the given configuration map.
74+
* <p>
75+
* This method uses Spring's {@link Binder} to bind the configuration values to an instance
76+
* of {@link BootstrapConfigProperties}, which can then be used to configure the thread pool
77+
* dynamically.
78+
*
79+
* @param configInfo the configuration map containing properties to bind.
80+
* @return the bound {@link BootstrapConfigProperties} instance.
81+
*/
7182
@Override
7283
public BootstrapConfigProperties buildBootstrapProperties(Map<Object, Object> configInfo) {
73-
BootstrapConfigProperties bindableBootstrapConfigProperties = new BootstrapConfigProperties();
74-
ConfigurationPropertySource sources = new MapConfigurationPropertySource(configInfo);
75-
Binder binder = new Binder(sources);
76-
return binder.bind(BootstrapConfigProperties.PREFIX, Bindable.ofInstance(bindableBootstrapConfigProperties)).get();
84+
BootstrapConfigProperties bindableBootstrapConfigProperties = SpringPropertyBinder.bindProperties(configInfo, BootstrapConfigProperties.PREFIX, BootstrapConfigProperties.class);
85+
return bindableBootstrapConfigProperties;
7786
}
7887
}

agent/hippo4j-agent-plugin/apollo-plugin/src/main/java/cn/hippo4j/agent/plugin/apollo/define/ApolloInstrumentation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public class ApolloInstrumentation extends ClassInstanceMethodsEnhancePluginDefi
3434

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

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

3939
@Override
4040
protected ClassMatch enhanceClass() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package cn.hippo4j.agent.plugin.apollo.interceptor;
19+
20+
import cn.hippo4j.agent.core.plugin.interceptor.enhance.EnhancedInstance;
21+
import cn.hippo4j.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
22+
import cn.hippo4j.agent.plugin.apollo.listeners.ApolloConfigPropertiesLoaderCompletedListener;
23+
import cn.hippo4j.common.extension.design.AbstractSubjectCenter;
24+
25+
import java.util.concurrent.atomic.AtomicBoolean;
26+
27+
/**
28+
* Default config constructor interceptor
29+
*/
30+
public class ApolloConfigConstructorInterceptor implements InstanceConstructorInterceptor {
31+
32+
private static final AtomicBoolean isExecuted = new AtomicBoolean(false);
33+
34+
@Override
35+
public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable {
36+
37+
// This logic will only be executed once
38+
if (isExecuted.compareAndSet(false, true)) {
39+
// The Apollo plugin triggers before the Spring configuration plug-in.
40+
// This means that when the Apollo plug-in executes, Spring's Environment is not yet ready,
41+
// so the configuration cannot be read
42+
// After listening to the AGENT_SPRING_PROPERTIES_LOADER_COMPLETED event, register the listener for Apollo
43+
AbstractSubjectCenter.register(AbstractSubjectCenter.SubjectType.AGENT_SPRING_PROPERTIES_LOADER_COMPLETED,
44+
new ApolloConfigPropertiesLoaderCompletedListener());
45+
}
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package cn.hippo4j.agent.plugin.apollo.listeners;
19+
20+
import cn.hippo4j.agent.plugin.apollo.ApolloDynamicThreadPoolChangeHandler;
21+
import cn.hippo4j.common.extension.design.Observer;
22+
import cn.hippo4j.common.extension.design.ObserverMessage;
23+
import cn.hippo4j.threadpool.dynamic.api.ThreadPoolDynamicRefresh;
24+
25+
/**
26+
* Apollo Config Properties Loader Completed Listener
27+
*/
28+
public class ApolloConfigPropertiesLoaderCompletedListener implements Observer<String> {
29+
30+
@Override
31+
public void accept(ObserverMessage<String> observerMessage) {
32+
ThreadPoolDynamicRefresh dynamicRefresh = new ApolloDynamicThreadPoolChangeHandler();
33+
dynamicRefresh.registerListener();
34+
}
35+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>cn.hippo4j</groupId>
8+
<artifactId>hippo4j-agent-plugin</artifactId>
9+
<version>${revision}</version>
10+
</parent>
11+
12+
<artifactId>hippo4j-agent-nacos-plugin</artifactId>
13+
14+
<properties>
15+
<nacos.version>2.2.1</nacos.version>
16+
</properties>
17+
18+
<dependencies>
19+
<dependency>
20+
<groupId>cn.hippo4j</groupId>
21+
<artifactId>hippo4j-agent-spring-plugin-common</artifactId>
22+
<version>${project.version}</version>
23+
</dependency>
24+
<dependency>
25+
<groupId>cn.hippo4j</groupId>
26+
<artifactId>hippo4j-threadpool-dynamic-mode-config</artifactId>
27+
<version>${project.version}</version>
28+
</dependency>
29+
<dependency>
30+
<groupId>com.alibaba.nacos</groupId>
31+
<artifactId>nacos-client</artifactId>
32+
<version>${nacos.version}</version>
33+
<scope>provided</scope>
34+
</dependency>
35+
</dependencies>
36+
37+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package cn.hippo4j.agent.plugin.nacos;
19+
20+
import cn.hippo4j.agent.plugin.spring.common.conf.NacosCloudConfig;
21+
import cn.hippo4j.agent.plugin.spring.common.conf.NacosConfig;
22+
import cn.hippo4j.agent.plugin.spring.common.conf.SpringBootConfig;
23+
import cn.hippo4j.agent.plugin.spring.common.toolkit.SpringPropertyBinder;
24+
import cn.hippo4j.common.executor.ThreadFactoryBuilder;
25+
import cn.hippo4j.common.logging.api.ILog;
26+
import cn.hippo4j.common.logging.api.LogManager;
27+
import cn.hippo4j.common.toolkit.StringUtil;
28+
import cn.hippo4j.threadpool.dynamic.mode.config.parser.ConfigParserHandler;
29+
import cn.hippo4j.threadpool.dynamic.mode.config.properties.BootstrapConfigProperties;
30+
import cn.hippo4j.threadpool.dynamic.mode.config.refresher.AbstractConfigThreadPoolDynamicRefresh;
31+
import com.alibaba.nacos.api.NacosFactory;
32+
import com.alibaba.nacos.api.PropertyKeyConst;
33+
import com.alibaba.nacos.api.config.ConfigService;
34+
import com.alibaba.nacos.api.config.listener.Listener;
35+
import org.springframework.boot.context.properties.bind.Binder;
36+
37+
import java.io.IOException;
38+
import java.util.HashMap;
39+
import java.util.Map;
40+
import java.util.Optional;
41+
import java.util.Properties;
42+
import java.util.concurrent.Executor;
43+
import java.util.concurrent.ScheduledThreadPoolExecutor;
44+
45+
import static cn.hippo4j.common.constant.Constants.DEFAULT_NAMESPACE_ID;
46+
47+
/**
48+
* NacosDynamicThreadPoolChangeHandler is responsible for handling dynamic thread pool
49+
* configuration changes in a Spring environment by listening to configuration updates from Nacos.
50+
* <p>
51+
* This class extends {@link AbstractConfigThreadPoolDynamicRefresh} and implements the logic
52+
* to register a Nacos listener, handle configuration changes, and dynamically refresh the thread pool
53+
* properties based on the new configuration.
54+
* <p>
55+
*/
56+
public class NacosDynamicThreadPoolChangeHandler extends AbstractConfigThreadPoolDynamicRefresh {
57+
58+
private static final ILog LOGGER = LogManager.getLogger(NacosDynamicThreadPoolChangeHandler.class);
59+
60+
/**
61+
* Registers a listener with Nacos to monitor for changes in the thread pool configuration.
62+
* <p>
63+
* This method sets up the Nacos {@link ConfigService} with the server address and namespace
64+
* from the Spring Boot configuration. It then adds a listener that will receive and process
65+
* configuration updates, triggering a dynamic refresh of thread pool settings.
66+
*/
67+
@Override
68+
public void registerListener() {
69+
// Retrieve necessary configuration properties
70+
String configFileType = SpringBootConfig.Spring.Dynamic.Thread_Pool.CONFIG_FILE_TYPE;
71+
String serverAddr = Optional.ofNullable(NacosCloudConfig.Spring.Cloud.Nacos.Config.SERVER_ADDR).filter(s -> !StringUtil.isEmpty(s))
72+
.orElse(Optional.ofNullable(NacosConfig.Nacos.Config.SERVER_ADDR).filter(s -> !StringUtil.isEmpty(s))
73+
.orElse(""));
74+
if (StringUtil.isEmpty(serverAddr)) {
75+
LOGGER.error("[Hippo4j-Agent] add Nacos listener failure. Nacos Registry address not configured");
76+
return;
77+
}
78+
String dataId = SpringBootConfig.Spring.Dynamic.Thread_Pool.Nacos.DATA_ID;
79+
String group = SpringBootConfig.Spring.Dynamic.Thread_Pool.Nacos.GROUP;
80+
String namespace = SpringBootConfig.Spring.Dynamic.Thread_Pool.Nacos.NAMESPACE.get(0);
81+
namespace = namespace.equals(DEFAULT_NAMESPACE_ID) ? "" : namespace;
82+
try {
83+
// Initialize Nacos ConfigService with the provided properties
84+
Properties properties = new Properties();
85+
properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
86+
properties.put(PropertyKeyConst.NAMESPACE, namespace);
87+
ConfigService configService = NacosFactory.createConfigService(properties);
88+
89+
// Define the listener to handle configuration changes
90+
Listener configChangeListener = new Listener() {
91+
92+
@Override
93+
public void receiveConfigInfo(String configInfo) {
94+
LOGGER.debug("Received configuration: " + configInfo);
95+
Map<String, Object> changeValueMap = new HashMap<>();
96+
try {
97+
// Parse the configuration and map the values to the appropriate keys
98+
Map<Object, Object> configInfoMap = ConfigParserHandler.getInstance().parseConfig(configInfo, configFileType);
99+
configInfoMap.forEach((key, value) -> {
100+
if (key instanceof String) {
101+
changeValueMap.put((String) key, value);
102+
}
103+
});
104+
} catch (IOException e) {
105+
LOGGER.error(e, "[Hippo4j-Agent] Dynamic thread pool refresher, Failed to resolve configuration. configFileType: {} configInfo: {} ", configFileType, configInfo);
106+
}
107+
// Trigger the dynamic refresh with the parsed configuration
108+
dynamicRefresh(configFileType, configInfo, changeValueMap);
109+
}
110+
111+
@Override
112+
public Executor getExecutor() {
113+
return new ScheduledThreadPoolExecutor(1, ThreadFactoryBuilder.builder().daemon(true).prefix("client.dynamic.refresh.agent").build());
114+
}
115+
};
116+
// Add the listener to the Nacos ConfigService
117+
configService.addListener(dataId, group, configChangeListener);
118+
LOGGER.info("[Hippo4j-Agent] Dynamic thread pool refresher, add Nacos listener successfully. serverAddr: {} namespace: {} data-id: {} group: {}", serverAddr, namespace, dataId, group);
119+
} catch (Exception e) {
120+
LOGGER.error(e, "[Hippo4j-Agent] Dynamic thread pool refresher, add Nacos listener failure. serverAddr: {} namespace: {} data-id: {} group: {}", serverAddr, namespace, dataId, group);
121+
}
122+
}
123+
124+
/**
125+
* Builds and binds the {@link BootstrapConfigProperties} from the given configuration map.
126+
* <p>
127+
* This method uses Spring's {@link Binder} to bind the configuration values to an instance
128+
* of {@link BootstrapConfigProperties}, which can then be used to configure the thread pool
129+
* dynamically.
130+
*
131+
* @param configInfo the configuration map containing properties to bind.
132+
* @return the bound {@link BootstrapConfigProperties} instance.
133+
*/
134+
@Override
135+
public BootstrapConfigProperties buildBootstrapProperties(Map<Object, Object> configInfo) {
136+
BootstrapConfigProperties bindableBootstrapConfigProperties = SpringPropertyBinder.bindProperties(configInfo, BootstrapConfigProperties.PREFIX, BootstrapConfigProperties.class);
137+
return bindableBootstrapConfigProperties;
138+
}
139+
}

0 commit comments

Comments
 (0)