Nacos配置中心客户端主要具有如下能力
初始化代码如下
// NacosConfigService
public NacosConfigService(Properties properties) throws NacosException {
String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);
if (StringUtils.isBlank(encodeTmp)) {
encode = Constants.ENCODE;
} else {
encode = encodeTmp.trim();
}
initNamespace(properties);
// 初始化HTTP代理MetricsHttpAgent
agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
agent.start();
// 创建客户端工作线程ClientWorker
worker = new ClientWorker(agent, configFilterChainManager, properties);
}
// NacosConfigService
public class NacosConfigService implements ConfigService {
......
// http代理MetricsHttpAgent
private HttpAgent agent;
// 客户端工作线程
private ClientWorker worker;
......
}
public class EventDispatcher {
......
// key是事件类型,value是监听器列表
static final Map<Class<? extends AbstractEvent>, CopyOnWriteArrayList<AbstractEventListener>> LISTENER_MAP
= new HashMap<Class<? extends AbstractEvent>, CopyOnWriteArrayList<AbstractEventListener>>();
......
}
// ServerListManager
public class ServerListManager {
......
// 服务列表(拼接成一行)
private String serverAddrsStr;
// 服务器列表(分解serverAddrsStr得到)
volatile List<String> serverUrls = new ArrayList<String>();
// 服务端点地址(查询服务列表请求地址)
public String addressServerUrl;
......
}
// ClientWorker
public class ClientWorker {
// 配置文件监听器,key是配置文件的标识,value是CacheData
private final AtomicReference<Map<String, CacheData>> cacheMap = new AtomicReference<Map<String, CacheData>>(
new HashMap<String, CacheData>());
}
// CacheData
public class CacheData {
// 配置文件内容
private volatile String content;
// 监听器列表
private final CopyOnWriteArrayList<ManagerListenerWrap> listeners;
}
CacheData是配置文件缓存,应用从CacheData中获取文件内容,CacheData由客户端线程不断拉取更新
优先从本地缓存文件读取配置文件内容,失败则从服务器获取,仍然失败,则从历史快照文件获取
// NacosConfigService
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
...
// 优先使用本地配置
String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
if (content != null) {
......
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
......
return content;
}
// 本地没有配置文件内容则请求服务端获取
try {
String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs);
cr.setContent(ct[0]);
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
} catch (NacosException ioe) {
if (NacosException.NO_RIGHT == ioe.getErrCode()) {
throw ioe;
}
LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}",
agent.getName(), dataId, group, tenant, ioe.toString());
}
// 从快照获取
LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", agent.getName(),
dataId, group, tenant, ContentUtils.truncateContent(content));
content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
cr.setContent(content);
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
}
调用API修改配置文件内容
客户端定时拉取指定文件内容,如果发生变化,更新配置文件内容(存放在cacheMap里),并调用监听器进行相应处理。
增加文件监控代码如下
// ClientWorker
public void checkConfigInfo() {
// 分任务
int listenerSize = cacheMap.get().size();
// 向上取整为批数
// ParamUtil.getPerTaskConfigSize()为每个线程处理的文件数目,添加文件监听时,会向cacheMap增加一条记录
int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
if (longingTaskCount > currentLongingTaskCount) {
for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
// 要判断任务是否在执行 这块需要好好想想。 任务列表现在是无序的。变化过程可能有问题
executorService.execute(new LongPollingRunnable(i));
}
currentLongingTaskCount = longingTaskCount;
}
}
拉取文件内容并更新代码如下
// ClientWorker
class LongPollingRunnable implements Runnable {
private int taskId;
public LongPollingRunnable(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
List<CacheData> cacheDatas = new ArrayList<CacheData>();
List<String> inInitializingCacheList = new ArrayList<String>();
try {
// check failover config
for (CacheData cacheData : cacheMap.get().values()) {
if (cacheData.getTaskId() == taskId) {
cacheDatas.add(cacheData);
try {
checkLocalConfig(cacheData);
if (cacheData.isUseLocalConfigInfo()) {
cacheData.checkListenerMd5();
}
} catch (Exception e) {
LOGGER.error("get local config info error", e);
}
}
}
// check server config
List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
for (String groupKey : changedGroupKeys) {
String[] key = GroupKey.parseKey(groupKey);
String dataId = key[0];
String group = key[1];
String tenant = null;
if (key.length == 3) {
tenant = key[2];
}
try {
String[] ct = getServerConfig(dataId, group, tenant, 3000L);
CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
// 更新配置文件内容
cache.setContent(ct[0]);
if (null != ct[1]) {
cache.setType(ct[1]);
}
LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}",
agent.getName(), dataId, group, tenant, cache.getMd5(),
ContentUtils.truncateContent(ct[0]), ct[1]);
} catch (NacosException ioe) {
String message = String.format(
"[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s",
agent.getName(), dataId, group, tenant);
LOGGER.error(message, ioe);
}
}
for (CacheData cacheData : cacheDatas) {
if (!cacheData.isInitializing() || inInitializingCacheList
.contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
// 调用监听器处理
cacheData.checkListenerMd5();
cacheData.setInitializing(false);
}
}
inInitializingCacheList.clear();
executorService.execute(this);
} catch (Throwable e) {
// If the rotation training task is abnormal, the next execution time of the task will be punished
LOGGER.error("longPolling error : ", e);
executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
}
}
}
// ClientWorker
public void removeListener(String dataId, String group, Listener listener) {
group = null2defaultGroup(group);
CacheData cache = getCache(dataId, group);
if (null != cache) {
cache.removeListener(listener);
if (cache.getListeners().isEmpty()) {
removeCache(dataId, group);
}
}
}
本文基于Nacos的1.2.0版本