/
ApolloConfigHelper.java
216 lines (202 loc) · 9.28 KB
/
ApolloConfigHelper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package io.arex.inst.config.apollo;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.core.dto.ApolloConfig;
import com.ctrip.framework.apollo.internals.*;
import com.ctrip.framework.apollo.util.ConfigUtil;
import io.arex.agent.bootstrap.util.ReflectUtil;
import io.arex.agent.bootstrap.util.StringUtil;
import io.arex.inst.runtime.log.LogManager;
import io.arex.inst.runtime.model.ArexConstants;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
/**
* {@link com.ctrip.framework.apollo.spi.DefaultConfigFactory#create} </br>
* Config config = new DefaultConfig(new LocalFileConfigRepository(new RemoteConfigRepository(namespace)))
* <pre>
* process:
*
* --------------------------------
* | DefaultConfig |
* | ---------------------- |
* | | onRepositoryChange | |
* | ---------------------- |
* --------------------------------
* ¦ ↑
* 1 4
* ↓ ¦
* --------------------------------
* | LocalFileConfig |
* | ---------------------- |
* | | onRepositoryChange | |
* | ---------------------- |
* --------------------------------
* ¦ ↑
* 2 3
* ↓ ¦
* ---------------------------------------
* | RemoteConfig(trySync,longPolling) |
* | ------------------------ |
* | | fireRepositoryChange | |
* | ------------------------ |
* ---------------------------------------
*
* </pre>
*/
public class ApolloConfigHelper {
private static Field configInstancesField;
public static void initAndRecord(Supplier<String> recordIdSpl, Supplier<String> versionSpl) {
String recordId = recordIdSpl.get();
if (StringUtil.isEmpty(recordId)) {
return;
}
String configVersion = versionSpl.get();
initReplayState(recordId, configVersion);
if (StringUtil.isEmpty(configVersion)) {
return;
}
/*
Does not include increment config, as Apollo has not yet created an instance of this configuration
it will be replay in io.arex.inst.config.apollo.ApolloConfigHelper.getReplayConfig
*/
replayAllConfigs();
}
/**
* 1. first record init config(full & incremental) {@link ApolloServletV3RequestHandler#postHandle}
* 2. then record changed config within running {@link ApolloDefaultConfigInstrumentation}
*/
public static void recordAllConfigs() {
if (!ApolloConfigExtractor.needRecord()) {
return;
}
ApolloConfigExtractor extractor = ApolloConfigExtractor.tryCreateExtractor();
if (extractor == null) {
return;
}
Map<String, Config> configMap = getAllConfigInstance();
for (Map.Entry<String, Config> entry : configMap.entrySet()) {
try {
Properties properties = getConfigProperties(entry.getValue());
if (properties != null) {
extractor.record(entry.getKey(), properties);
}
} catch (Throwable e) {
LogManager.warn("record apollo config error", e);
}
}
}
/**
* ConfigService -> s_instance -> m_manager -> m_configs -> m_configProperties
*/
private static Map<String, Config> getAllConfigInstance() {
try {
if (configInstancesField == null) {
configInstancesField = ConfigService.class.getDeclaredField("s_instance");
configInstancesField.setAccessible(true);
}
Object configService = configInstancesField.get(null);
Object managerInstance = ReflectUtil.getFieldOrInvokeMethod(() ->
ConfigService.class.getDeclaredField("m_configManager"), configService);
Object configs = ReflectUtil.getFieldOrInvokeMethod(() ->
DefaultConfigManager.class.getDeclaredField("m_configs"), managerInstance);
if (configs instanceof Map) {
return (Map<String, Config>) configs;
}
} catch (Throwable e) {
LogManager.warn("get apollo all config instance error", e.getMessage());
}
return Collections.emptyMap();
}
private static Properties getConfigProperties(Config config) throws Exception {
Object configProperties = ReflectUtil.getFieldOrInvokeMethod(() ->
config.getClass().getDeclaredField("m_configProperties"), config);
if (configProperties instanceof AtomicReference) {
AtomicReference<Properties> properties = (AtomicReference<Properties>) configProperties;
return properties.get();
}
return null;
}
public static void initReplayState(String recordId, String configVersion) {
ApolloConfigExtractor.updateReplayState(recordId, configVersion);
}
/**
* you can also modify m_configs in {@link DefaultConfigManager} just like the recorded logic,
* But there are the following points to consider: <pre>
* 1. the case where configuration polling triggers a change during replay, it may overwrite the values replay
* 2. how can trigger ConfigChangeListener on business side
* 3. how to recover the original configuration after replay
* </pre>
* so the final entry point is sync() in the {@link RemoteConfigRepository}
*/
public static void replayAllConfigs() {
Map<String, Config> configMap = getAllConfigInstance();
for (Map.Entry<String, Config> entry : configMap.entrySet()) {
try {
triggerReplay((DefaultConfig) entry.getValue());
} catch (Exception e) {
LogManager.warn("replay apollo config error", e);
}
}
}
private static void triggerReplay(DefaultConfig config) throws Exception {
Object repositoryObj = ReflectUtil.getFieldOrInvokeMethod(() ->
config.getClass().getDeclaredField("m_configRepository"), config);
if (repositoryObj instanceof LocalFileConfigRepository) {
LocalFileConfigRepository localRepository = (LocalFileConfigRepository) repositoryObj;
Object remoteRepositoryObj = ReflectUtil.getFieldOrInvokeMethod(() ->
localRepository.getClass().getDeclaredField("m_upstream"), localRepository);
if (remoteRepositoryObj instanceof RemoteConfigRepository) {
RemoteConfigRepository remoteRepository = (RemoteConfigRepository) remoteRepositoryObj;
// sync -> loadApolloConfig(by arex transformed)
ReflectUtil.getFieldOrInvokeMethod(() ->
remoteRepository.getClass().getDeclaredMethod("sync"), remoteRepository);
}
}
}
public static ApolloConfig getReplayConfig(ApolloConfig previous, String namespace, ConfigUtil configUtil) {
if (!ApolloConfigExtractor.duringReplay() || previous == null) {
// execute original business code, not replay
return null;
}
/*
if releaseKey is same, then there is no need to replay it again,
because this configuration has already been replayed, during the first full replay(replayAllConfigs)
*/
if (getReleaseKey().equals(previous.getReleaseKey())) {
return previous;
}
/*
if releaseKey is different during replaying, then it needs to be replayed again, because:
1. switch to a new configuration(different configBatchNo) for next config version replay
2. this configuration belongs to a new configuration,
does not exist during the first full replay (replayAllConfigs)
and no instances were created, which belongs to a new configuration.
in other words, the configuration instance and content are only created when the business code is executed.
*/
Properties properties = ApolloConfigExtractor.replay(namespace);
if (properties == null) {
return null;
}
ApolloConfig config = new ApolloConfig(
configUtil.getAppId(),
configUtil.getCluster(),
namespace,
getReleaseKey());
config.setConfigurations((Map) properties);
return config;
}
/**
* format: arex-fff55f0a-b457-4cbe-9183-ca1adcf0d851
*
* during long polling pull configuration,
* the Apollo-Config-Server will determine whether it is the latest configuration based on the request param: releaseKey,
* if it is different between local and server, the server will return the latest configuration.
* the purpose of doing this is to use the real configuration after replay finished,
* that is, restore the real configuration.
*/
private static String getReleaseKey() {
return ArexConstants.PREFIX + ApolloConfigExtractor.currentReplayConfigBatchNo();
}
}