nacos的版本1.1.3. 客户端在启动的时候,需要去配紫中心的获取远程的配置,首先给出一个nacos项目的demo
public class ConfigExample {
public static void main(String[] args) throws NacosException, InterruptedException {
String serverAddr = "localhost";
String dataId = "test";
String group = "DEFAULT_GROUP";
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
ConfigService configService = NacosFactory.createConfigService(properties);
String content = configService.getConfig(dataId, group, 5000);
System.out.println(content);
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("receive:" + configInfo);
}
@Override
public Executor getExecutor() {
return null;
}
});
boolean isPublishOk = configService.publishConfig(dataId, group, "content");
System.out.println(isPublishOk);
Thread.sleep(3000);
content = configService.getConfig(dataId, group, 5000);
System.out.println(content);
boolean isRemoveOk = configService.removeConfig(dataId, group);
System.out.println(isRemoveOk);
Thread.sleep(3000);
content = configService.getConfig(dataId, group, 5000);
System.out.println(content);
Thread.sleep(300000);
}
}
首先,需要指定配置中心serverAddr的地址、dataId以及group, 然后通过NacosFactory工厂 创建NacosConfigService, 从下图看到,这里就是通过反射创建对象,并通过构造函数传入 Property的配置对象.
public static ConfigService createConfigService(Properties properties) throws NacosException {
try {
Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService");
Constructor constructor = driverImplClass.getConstructor(Properties.class);
ConfigService vendorImpl = (ConfigService) constructor.newInstance(properties);
return vendorImpl;
} catch (Throwable e) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
}
}
那么接下里就看NacosConfigService是怎么拉取配置的,首先看下NacosConfigServic的构造函数的初始化, 第一步: 获取properties配置文件的配置的编码,默认是UTF-8 第二步: 初始化Namespace,如果没有配置namespace,默认是空的字符串. 第三步: 创建MetricsHttpAgent,构造函数传入了ServerHttpAgent,这里其实是对ServerHttpAgent的装饰器模式, 第四步: 执行MetricsHttpAgent的start方法.去拉取服务列表. 第五步: 创建ClientWork对象, 客户端工作线程池取获取配置中心的配置.
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);
agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
agent.start();
worker = new ClientWorker(agent, configFilterChainManager, properties);
}
接着看下ServerHttpAgent的初始化中,新建了一个ServerListManager对象,然后初始化编码、AccessKey、MaxRetry(默认值是3次)三个参数
public ServerHttpAgent(Properties properties) throws NacosException {
serverListMgr = new ServerListManager(properties);
init(properties);
}
private void init(Properties properties) {
initEncode(properties);
initAkSk(properties);
initMaxRetry(properties);
}
初始化ServerListManager,初始endpoint、化contextPath、ClusterName等参数,然后对 serverAddrsStr配置参数进行处理,处理成标准的url地址赋值给serverUrls.
public ServerListManager(Properties properties) throws NacosException {
isStarted = false;
serverAddrsStr = properties.getProperty(PropertyKeyConst.SERVER_ADDR);
String namespace = properties.getProperty(PropertyKeyConst.NAMESPACE);
initParam(properties);
//判断serverAddrsStr是否为空
if (StringUtils.isNotEmpty(serverAddrsStr)) {
isFixed = true;
List<String> serverAddrs = new ArrayList<String>();
String[] serverAddrsArr = serverAddrsStr.split(",");
for (String serverAddr: serverAddrsArr) {
if (serverAddr.startsWith(HTTPS) || serverAddr.startsWith(HTTP)) {
serverAddrs.add(serverAddr);
} else {
String[] serverAddrArr = serverAddr.split(":");
if (serverAddrArr.length == 1) {
serverAddrs.add(HTTP + serverAddrArr[0] + ":" + ParamUtil.getDefaultServerPort());
} else {
serverAddrs.add(HTTP + serverAddr);
}
}
}
serverUrls = serverAddrs;
if (StringUtils.isBlank(namespace)) {
name = FIXED_NAME + "-" + getFixedNameSuffix(serverUrls.toArray(new String[serverUrls.size()]));
} else {
this.namespace = namespace;
this.tenant = namespace;
name = FIXED_NAME + "-" + getFixedNameSuffix(serverUrls.toArray(new String[serverUrls.size()])) + "-"
+ namespace;
}
} else {
//异常判断
if (StringUtils.isBlank(endpoint)) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "endpoint is blank");
}
isFixed = false;
if (StringUtils.isBlank(namespace)) {
name = endpoint;
addressServerUrl = String.format("http://%s:%d/%s/%s", endpoint, endpointPort, contentPath,
serverListName);
} else {
this.namespace = namespace;
this.tenant = namespace;
name = endpoint + "-" + namespace;
addressServerUrl = String.format("http://%s:%d/%s/%s?namespace=%s", endpoint, endpointPort,
contentPath, serverListName, namespace);
}
}
}
接下来就是执行MetricsHttpAgent的start,启动Http代理的逻辑,MetricsHttpAgen里面ServerHttpAgent对象,实际ServerHttpAgent对象的start方法.
public class MetricsHttpAgent implements HttpAgent {
//代理对象是ServerHttpAgent对象
private HttpAgent httpAgent;
@Override
public void start() throws NacosException {
httpAgent.start();
}
这里ServerListManager的start方法是通过GetServerListTask后服务中心去拉取配置中心的地址列表,由于demo中配置是固定的配置中心的地址,所以这里isFixed是true,直接返回
public synchronized void start() throws NacosException {
if (isStarted || isFixed) {
return;
}
GetServerListTask getServersTask = new GetServerListTask(addressServerUrl);
for (int i = 0; i < initServerlistRetryTimes && serverUrls.isEmpty(); ++i) {
getServersTask.run();
try {
this.wait((i + 1) * 100L);
} catch (Exception e) {
LOGGER.warn("get serverlist fail,url: {}", addressServerUrl);
}
}
if (serverUrls.isEmpty()) {
LOGGER.error("[init-serverlist] fail to get NACOS-server serverlist! env: {}, url: {}", name,
addressServerUrl);
throw new NacosException(NacosException.SERVER_ERROR,
"fail to get NACOS-server serverlist! env:" + name + ", not connnect url:" + addressServerUrl);
}
//定时任务每隔30s进行获取配置中心地址列表
TimerService.scheduleWithFixedDelay(getServersTask, 0L, 30L, TimeUnit.SECONDS);
isStarted = true;
}
到了这里,接下来出场的就是今天的主角----ClientWorker类,初始化NacosConfigService第五步中,可以看到创建ClientWorker时候,传入四个参数HttpAagent就是MetricsHttpAgent对象,第二个过滤器是ConfigFilterChainManager对象,第三个是properties配置,
public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager, final Properties properties) {
this.agent = agent;
this.configFilterChainManager = configFilterChainManager;
//初始化超时时间的配置
init(properties);
//客户端,周期执行的线程池,这里重写ThreadFactory,这样线程运行会com.alibaba.nacos.client.Worke的前前缀,主要是执行检查配置信息的周期的线程池
executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
t.setDaemon(true);
return t;
}
});
//客户端长轮询的线程池,配置中心拉去配置
executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());
t.setDaemon(true);
return t;
}
});
//每隔10毫秒执行checkConfigInfo方法
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
checkConfigInfo();
} catch (Throwable e) {
LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
}
}
}, 1L, 10L, TimeUnit.MILLISECONDS);
}
执行checkConfigInfo方法, 这里是每3000个任务为一个批次任务处理,使用另外一个 线程池的处理长轮询的处理,一个批次处理就是执行LongPollingRunnable这个线程.
public void checkConfigInfo() {
// 分任务
int listenerSize = cacheMap.get().size();
// 向上取整为批数
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;
}
}
下面看下LongPollingRunnable这个类,它是实现了Runnable接口的,所以丢给线程池执行,就会运行run方法, 首先循环cacheMap中缓存的数据,checkLocalConfig,主要是检查本地文件的 信息是否变更,如果是使用本地文件缓存的配置则需要校验文件内容的md5值, 获取有变化的 dataId、group,然后http从远程获取存储到cacheMap中
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);
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 content = getServerConfig(dataId, group, tenant, 3000L);
CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
cache.setContent(content);
LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}",
agent.getName(), dataId, group, tenant, cache.getMd5(),
ContentUtils.truncateContent(content));
} 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);
}
}
}
拉取配置就是利用NacosConfigService中getConfig方法,优先使用本地的文件的配置,没有则从远程拉取配置.
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
group = null2defaultGroup(group);
ParamUtils.checkKeyParam(dataId, group);
ConfigResponse cr = new ConfigResponse();
cr.setDataId(dataId);
cr.setTenant(tenant);
cr.setGroup(group);
// 优先使用本地配置
String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
if (content != null) {
LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", agent.getName(),
dataId, group, tenant, ContentUtils.truncateContent(content));
cr.setContent(content);
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
}
try {
content = worker.getServerConfig(dataId, group, tenant, timeoutMs);
cr.setContent(content);
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;
}
总结
今天主要分析nacos的初始化,并获取配置中心的过程分析.那么监听的呢,下一篇继续分析.