Nacos的配置发布
ConfigService 通过ConfigService进行发布配置
nacos的配置发布、配置删除都是通过ConfigService类进行调用的。通过example模块的ConfigExample类为入口查看配置的发布,代码如下
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 configService = NacosFactory.createConfigService(properties);
// 增加一个监听者
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("[publish result] " + isPublishOk);
}
}
ConfigService创建逻辑
ConfigService 是通过NacosFactory工厂类进行创建的代码如下
public static ConfigService createConfigService(Properties properties) throws NacosException {
try {
Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService");
// 通过反射调用参数为Properties 的构造器进行对象的创建
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);
}
}
ConfigServcie构造方法
ConfigService 的构造方法如下
public NacosConfigService(Properties properties) throws NacosException {
PreInitUtils.asyncPreLoadCostComponent();
//
final NacosClientProperties clientProperties = NacosClientProperties.PROTOTYPE.derive(properties);
LOGGER.info(ParamUtil.getInputParameters(clientProperties.asProperties()));
ValidatorUtils.checkInitParam(clientProperties);
initNamespace(clientProperties);
this.configFilterChainManager = new ConfigFilterChainManager(clientProperties.asProperties());
ServerListManager serverListManager = new ServerListManager(clientProperties);
serverListManager.start();
this.worker = new ClientWorker(this.configFilterChainManager, serverListManager, clientProperties);
// will be deleted in 2.0 later versions
agent = new ServerHttpAgent(serverListManager);
}
ConfigService的publish方法
接下来看看ConfigService的publish方法。最后还是通过Grpc调用,而处理此次请求的处理器ConfigPublishRequestHandler的handle方法
private boolean publishConfigInner(String tenant, String dataId, String group, String tag, String appName,
String betaIps, String content, String type, String casMd5) throws NacosException {
group = blank2defaultGroup(group);
ParamUtils.checkParam(dataId, group, content);
ConfigRequest cr = new ConfigRequest();
cr.setDataId(dataId);
cr.setTenant(tenant);
cr.setGroup(group);
cr.setContent(content);
cr.setType(type);
configFilterChainManager.doFilter(cr, null);
content = cr.getContent();
String encryptedDataKey = cr.getEncryptedDataKey();
return worker
.publishConfig(dataId, group, tenant, appName, tag, betaIps, content, encryptedDataKey, casMd5, type);
}
ConfigPublishRequestHandler的处理
查看ConfigPublishRequestHandler的handel方法
@Override
@TpsControl(pointName = "ConfigPublish")
@Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG)
@ExtractorManager.Extractor(rpcExtractor = ConfigRequestParamExtractor.class)
public ConfigPublishResponse handle(ConfigPublishRequest request, RequestMeta meta) throws NacosException {
try {
// 此处省略一些参数的获取
Map<String, Object> configAdvanceInfo = new HashMap<>(10);
ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content);
configInfo.setMd5(request.getCasMd5());
configInfo.setType(type);
configInfo.setEncryptedDataKey(encryptedDataKey);
String betaIps = request.getAdditionParam("betaIps");
ConfigOperateResult configOperateResult = null;
String persistEvent = ConfigTraceService.PERSISTENCE_EVENT;
// 去除部分逻辑 最后调用这个insertOrUpdate方法其实就是将配置新增或者修改到数据库
configOperateResult = configInfoPersistService.insertOrUpdate(srcIp, srcUser, configInfo, configAdvanceInfo);
// 发布一个配置变更事件
ConfigChangePublisher.notifyConfigChange(new ConfigDataChangeEvent(false, dataId, group, tenant,
configOperateResult.getLastModified()));
ConfigTraceService.logPersistenceEvent(dataId, group, tenant, requestIpApp,
configOperateResult.getLastModified(), srcIp, persistEvent, ConfigTraceService.PERSISTENCE_TYPE_PUB,
content);
return ConfigPublishResponse.buildSuccessResponse();
} catch (Exception e) {
Loggers.REMOTE_DIGEST.error("[ConfigPublishRequestHandler] publish config error ,request ={}", request, e);
return ConfigPublishResponse.buildFailResponse(
(e instanceof NacosException) ? ((NacosException) e).getErrCode() : ResponseCode.FAIL.getCode(),
e.getMessage());
}
}
ConfigDataChangeEvent的事件监听
接下来看看配置变更事件
可以看到有两处对此事件做了处理
分别是DumpService、AsyncNotifyService
先查看AsyncNotifyService对事件的处理
AsyncNotifyService : 处理的nacos-server之间的数据逻辑,其实就是通知其它server配置发生了变更。
DumpService : 处理的就是server本身,以及server和client之间的逻辑。
ConfigDataChangeEvent事件处理之AsyncNotifyService
void handleConfigDataChangeEvent(Event event) {
if (event instanceof ConfigDataChangeEvent) {
ConfigDataChangeEvent evt = (ConfigDataChangeEvent) event;
long dumpTs = evt.lastModifiedTs;
String dataId = evt.dataId;
String group = evt.group;
String tenant = evt.tenant;
String tag = evt.tag;
MetricsMonitor.incrementConfigChangeCount(tenant, group, dataId);
// 获取除了当前服务的其他Server
Collection<Member> ipList = memberManager.allMembersWithoutSelf();
Queue<NotifySingleRpcTask> rpcQueue = new LinkedList<>();
for (Member member : ipList) {
// grpc report data change only
rpcQueue.add(
new NotifySingleRpcTask(dataId, group, tenant, tag, dumpTs, evt.isBeta, evt.isBatch, member));
}
if (!rpcQueue.isEmpty()) {
ConfigExecutor.executeAsyncNotify(new AsyncRpcTask(rpcQueue));
}
}
}
通过上述代码可以看到显示获取其他的Service,由于我本地是集群部署,所以队列信息一定不为空。所以就会执行下面的ConfigExecutor.executeAsyncNotify(new AsyncRpcTask(rpcQueue));这段逻辑。接下来看看AsyncRpcTask内部类的逻辑
public class AsyncRpcTask implements Runnable {
private Queue<NotifySingleRpcTask> queue;
public AsyncRpcTask(Queue<NotifySingleRpcTask> queue) {
this.queue = queue;
}
@Override
public void run() {
executeAsyncRpcTask(queue);
}
}
void executeAsyncRpcTask(Queue<NotifySingleRpcTask> queue) {
while (!queue.isEmpty()) {
NotifySingleRpcTask task = queue.poll();
// 封装请求
ConfigChangeClusterSyncRequest syncRequest = new ConfigChangeClusterSyncRequest();
syncRequest.setDataId(task.getDataId());
syncRequest.setGroup(task.getGroup());
syncRequest.setBeta(task.isBeta());
syncRequest.setLastModified(task.getLastModified());
syncRequest.setTag(task.getTag());
syncRequest.setBatch(task.isBatch());
syncRequest.setTenant(task.getTenant());
Member member = task.member;
String event = getNotifyEvent(task);
if (memberManager.hasMember(member.getAddress())) {
// start the health check and there are ips that are not monitored, put them directly in the notification queue, otherwise notify
boolean unHealthNeedDelay = isUnHealthy(member.getAddress());
if (unHealthNeedDelay) {
// target ip is unhealthy, then put it in the notification list
ConfigTraceService.logNotifyEvent(task.getDataId(), task.getGroup(), task.getTenant(), null,
task.getLastModified(), InetUtils.getSelfIP(), event,
ConfigTraceService.NOTIFY_TYPE_UNHEALTH, 0, member.getAddress());
// get delay time and set fail count to the task
asyncTaskExecute(task);
} else {
// grpc report data change only
try {
configClusterRpcClientProxy.syncConfigChange(member, syncRequest,
new AsyncRpcNotifyCallBack(AsyncNotifyService.this, task));
} catch (Exception e) {
MetricsMonitor.getConfigNotifyException().increment();
asyncTaskExecute(task);
}
}
} else {
//No nothing if member has offline.
}
}
}
继续跟踪会发现还是通过grpc调用,发送一个ConfigChangeClusterSyncRequest请求,直接找到请求处理的地方ConfigChangeClusterSyncRequestHandler的handle方法,而最后的dumpService.dump(dumpRequest);方法调用同DumpService的一致
@TpsControl(pointName = "ClusterConfigChangeNotify")
@Override
@ExtractorManager.Extractor(rpcExtractor = ConfigRequestParamExtractor.class)
public ConfigChangeClusterSyncResponse handle(ConfigChangeClusterSyncRequest configChangeSyncRequest,
RequestMeta meta) throws NacosException {
DumpRequest dumpRequest = DumpRequest.create(configChangeSyncRequest.getDataId(),
configChangeSyncRequest.getGroup(), configChangeSyncRequest.getTenant(),
configChangeSyncRequest.getLastModified(), meta.getClientIp());
dumpRequest.setBeta(configChangeSyncRequest.isBeta());
dumpRequest.setBatch(configChangeSyncRequest.isBatch());
dumpRequest.setTag(configChangeSyncRequest.getTag());
dumpService.dump(dumpRequest);
return new ConfigChangeClusterSyncResponse();
}
ConfigDataChangeEvent事件处理之DumpServeice
void handleConfigDataChange(Event event) {
// Generate ConfigDataChangeEvent concurrently
if (event instanceof ConfigDataChangeEvent) {
ConfigDataChangeEvent evt = (ConfigDataChangeEvent) event;
DumpRequest dumpRequest = DumpRequest.create(evt.dataId, evt.group, evt.tenant, evt.lastModifiedTs,
NetUtils.localIP());
dumpRequest.setBeta(evt.isBeta);
dumpRequest.setBatch(evt.isBatch);
dumpRequest.setTag(evt.tag);
DumpService.this.dump(dumpRequest);
}
}
public void dump(DumpRequest dumpRequest) {
if (dumpRequest.isBeta()) {
dumpBeta(dumpRequest.getDataId(), dumpRequest.getGroup(), dumpRequest.getTenant(),
dumpRequest.getLastModifiedTs(), dumpRequest.getSourceIp());
} else if (dumpRequest.isBatch()) {
dumpBatch(dumpRequest.getDataId(), dumpRequest.getGroup(), dumpRequest.getTenant(),
dumpRequest.getLastModifiedTs(), dumpRequest.getSourceIp());
} else if (StringUtils.isNotBlank(dumpRequest.getTag())) {
dumpTag(dumpRequest.getDataId(), dumpRequest.getGroup(), dumpRequest.getTenant(), dumpRequest.getTag(),
dumpRequest.getLastModifiedTs(), dumpRequest.getSourceIp());
} else {
// 最后会执行这个分支
dumpFormal(dumpRequest.getDataId(), dumpRequest.getGroup(), dumpRequest.getTenant(),
dumpRequest.getLastModifiedTs(), dumpRequest.getSourceIp());
}
}
private void dumpFormal(String dataId, String group, String tenant, long lastModified, String handleIp) {
String groupKey = GroupKey2.getKey(dataId, group, tenant);
String taskKey = groupKey;
// 加入到任务管理器中
dumpTaskMgr.addTask(taskKey, new DumpTask(groupKey, false, false, false, null, lastModified, handleIp));
DUMP_LOG.info("[dump] add formal task. groupKey={}", groupKey);
}
最后会执行DumpProcessor 的process方法
@Override
public boolean process(NacosTask task) {
DumpTask dumpTask = (DumpTask) task;
String[] pair = GroupKey2.parseKey(dumpTask.getGroupKey());
String dataId = pair[0];
String group = pair[1];
String tenant = pair[2];
long lastModifiedOut = dumpTask.getLastModified();
String handleIp = dumpTask.getHandleIp();
boolean isBeta = dumpTask.isBeta();
String tag = dumpTask.getTag();
ConfigDumpEvent.ConfigDumpEventBuilder build = ConfigDumpEvent.builder().namespaceId(tenant).dataId(dataId)
.group(group).isBeta(isBeta).tag(tag).handleIp(handleIp);
String type = "formal";
if (isBeta) {
type = "beta";
} else if (StringUtils.isNotBlank(tag)) {
type = "tag-" + tag;
}
LogUtil.DUMP_LOG.info("[dump] process {} task. groupKey={}", type, dumpTask.getGroupKey());
if (isBeta) {
// if publish beta, then dump config, update beta cache
ConfigInfoBetaWrapper cf = configInfoBetaPersistService.findConfigInfo4Beta(dataId, group, tenant);
build.remove(Objects.isNull(cf));
build.betaIps(Objects.isNull(cf) ? null : cf.getBetaIps());
build.content(Objects.isNull(cf) ? null : cf.getContent());
build.type(Objects.isNull(cf) ? null : cf.getType());
build.encryptedDataKey(Objects.isNull(cf) ? null : cf.getEncryptedDataKey());
build.lastModifiedTs(Objects.isNull(cf) ? lastModifiedOut : cf.getLastModified());
return DumpConfigHandler.configDump(build.build());
}
if (StringUtils.isNotBlank(tag)) {
ConfigInfoTagWrapper cf = configInfoTagPersistService.findConfigInfo4Tag(dataId, group, tenant, tag);
build.remove(Objects.isNull(cf));
build.content(Objects.isNull(cf) ? null : cf.getContent());
build.type(Objects.isNull(cf) ? null : cf.getType());
build.encryptedDataKey(Objects.isNull(cf) ? null : cf.getEncryptedDataKey());
build.lastModifiedTs(Objects.isNull(cf) ? lastModifiedOut : cf.getLastModified());
return DumpConfigHandler.configDump(build.build());
}
ConfigInfoWrapper cf = configInfoPersistService.findConfigInfo(dataId, group, tenant);
build.remove(Objects.isNull(cf));
build.content(Objects.isNull(cf) ? null : cf.getContent());
build.type(Objects.isNull(cf) ? null : cf.getType());
build.encryptedDataKey(Objects.isNull(cf) ? null : cf.getEncryptedDataKey());
build.lastModifiedTs(Objects.isNull(cf) ? lastModifiedOut : cf.getLastModified());
return DumpConfigHandler.configDump(build.build());
}
会调用DumpConfigHandler的configDump方法,其实就是根据配置的增删改对磁盘中的数据进行变更
public static boolean configDump(ConfigDumpEvent event) {
final String dataId = event.getDataId();
final String group = event.getGroup();
final String namespaceId = event.getNamespaceId();
final String content = event.getContent();
final String type = event.getType();
final long lastModified = event.getLastModifiedTs();
//beta
if (event.isBeta()) {
boolean result = false;
if (event.isRemove()) {
result = ConfigCacheService.removeBeta(dataId, group, namespaceId);
if (result) {
ConfigTraceService.logDumpBetaEvent(dataId, group, namespaceId, null, lastModified,
event.getHandleIp(), ConfigTraceService.DUMP_TYPE_REMOVE_OK,
System.currentTimeMillis() - lastModified, 0);
}
return result;
} else {
result = ConfigCacheService.dumpBeta(dataId, group, namespaceId, content, lastModified,
event.getBetaIps(), event.getEncryptedDataKey());
if (result) {
ConfigTraceService.logDumpBetaEvent(dataId, group, namespaceId, null, lastModified,
event.getHandleIp(), ConfigTraceService.DUMP_TYPE_OK,
System.currentTimeMillis() - lastModified, content.length());
}
}
return result;
}
//tag
if (StringUtils.isNotBlank(event.getTag())) {
//
boolean result;
if (!event.isRemove()) {
result = ConfigCacheService.dumpTag(dataId, group, namespaceId, event.getTag(), content, lastModified,
event.getEncryptedDataKey());
if (result) {
ConfigTraceService.logDumpTagEvent(dataId, group, namespaceId, event.getTag(), null, lastModified,
event.getHandleIp(), ConfigTraceService.DUMP_TYPE_OK,
System.currentTimeMillis() - lastModified, content.length());
}
} else {
result = ConfigCacheService.removeTag(dataId, group, namespaceId, event.getTag());
if (result) {
ConfigTraceService.logDumpTagEvent(dataId, group, namespaceId, event.getTag(), null, lastModified,
event.getHandleIp(), ConfigTraceService.DUMP_TYPE_REMOVE_OK,
System.currentTimeMillis() - lastModified, 0);
}
}
return result;
}
//default
if (dataId.equals(AggrWhitelist.AGGRIDS_METADATA)) {
AggrWhitelist.load(content);
}
if (dataId.equals(ClientIpWhiteList.CLIENT_IP_WHITELIST_METADATA)) {
ClientIpWhiteList.load(content);
}
if (dataId.equals(SwitchService.SWITCH_META_DATA_ID)) {
SwitchService.load(content);
}
boolean result;
if (!event.isRemove()) {
result = ConfigCacheService.dump(dataId, group, namespaceId, content, lastModified, event.getType(),
event.getEncryptedDataKey());
if (result) {
ConfigTraceService.logDumpEvent(dataId, group, namespaceId, null, lastModified, event.getHandleIp(),
ConfigTraceService.DUMP_TYPE_OK, System.currentTimeMillis() - lastModified, content.length());
}
} else {
result = ConfigCacheService.remove(dataId, group, namespaceId);
if (result) {
ConfigTraceService.logDumpEvent(dataId, group, namespaceId, null, lastModified, event.getHandleIp(),
ConfigTraceService.DUMP_TYPE_REMOVE_OK, System.currentTimeMillis() - lastModified, 0);
}
}
return result;
}
总结
在发布配置时,会将配置信息存入数据库,然后发布一个ConfigDataChangeEvent事件,而处理这个事件的入口有两个分别是DumpService、AsyncNotifyService。AsyncNotifyService所有的服务端实例都会调用各自的DumpService 更新本地磁盘中的配置数据