Nacos2.4.0源码分析05 nacos的配置发布

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 更新本地磁盘中的配置数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值