Nacos源碼解讀09——配置中心配置信息創建修改怎么處理的

存儲配置

在這里插入圖片描述
從整體上Nacos服務端的配置存儲分為三層:
內存:Nacos每個節點都在內存里緩存了配置,但是只包含配置的md5(緩存配置文件太多了),所以內存級別的配置只能用于比較配置是否發生了變更,只用于客戶端長輪詢配置等場景。
文件系統:文件系統配置來源于數據庫寫入的配置。如果是集群啟動或mysql單機啟動,服務端會以本地文件系統的配置響應客戶端查詢。
數據庫:所有寫數據都會先寫入數據庫。只有當以derby數據源(-DembeddedStorage=true)單機(-Dnacos.standalone=true)啟動時,客戶端的查詢配置請求會實時查詢derby數據庫。

服務端創建修改配置

可以看到在服務端創建和修改配置的時候會調用到ConfigController的publishConfig中進行配置的發布
在這里插入圖片描述

@PostMapping@Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG)public Boolean publishConfig(HttpServletRequest request, @RequestParam(value = "dataId") String dataId,@RequestParam(value = "group") String group,@RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant,@RequestParam(value = "content") String content, @RequestParam(value = "tag", required = false) String tag,@RequestParam(value = "appName", required = false) String appName,@RequestParam(value = "src_user", required = false) String srcUser,@RequestParam(value = "config_tags", required = false) String configTags,@RequestParam(value = "desc", required = false) String desc,@RequestParam(value = "use", required = false) String use,@RequestParam(value = "effect", required = false) String effect,@RequestParam(value = "type", required = false) String type,@RequestParam(value = "schema", required = false) String schema) throws NacosException {......// 目標灰度機器的IP地址。String betaIps = request.getHeader("betaIps");//構建配置信息ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content);configInfo.setType(type);String encryptedDataKey = pair.getFirst();configInfo.setEncryptedDataKey(encryptedDataKey);//如果沒配置灰度地址if (StringUtils.isBlank(betaIps)) {//沒有配置標簽if (StringUtils.isBlank(tag)) {//添加配置信息persistService.insertOrUpdate(srcIp, srcUser, configInfo, time, configAdvanceInfo, false);//發布一個配置變更事件ConfigChangePublisher.notifyConfigChange(new ConfigDataChangeEvent(false, dataId, group, tenant, time.getTime()));} else {persistService.insertOrUpdateTag(configInfo, tag, srcIp, srcUser, time, false);ConfigChangePublisher.notifyConfigChange(new ConfigDataChangeEvent(false, dataId, group, tenant, tag, time.getTime()));}} else {// 灰度發布configInfo.setEncryptedDataKey(encryptedDataKey);persistService.insertOrUpdateBeta(configInfo, betaIps, srcIp, srcUser, time, false);ConfigChangePublisher.notifyConfigChange(new ConfigDataChangeEvent(true, dataId, group, tenant, time.getTime()));}//持久化日志ConfigTraceService.logPersistenceEvent(dataId, group, tenant, requestIpApp, time.getTime(),InetUtils.getSelfIP(), ConfigTraceService.PERSISTENCE_EVENT_PUB, content);return true;            }

PersistService有兩個實現類一個是EmbeddedStoragePersistServiceImpl(嵌入式數據源derby)
一個ExternalStoragePersistServiceImpl(外部數據源) 而我使用的是mysql做的配置的持久化 所以用到的是ExternalStoragePersistServiceImpl 將數據寫到mysql 的config_info表里

    @Overridepublic void insertOrUpdate(String srcIp, String srcUser, ConfigInfo configInfo, Timestamp time,Map<String, Object> configAdvanceInfo, boolean notify) {try {//添加配置addConfigInfo(srcIp, srcUser, configInfo, time, configAdvanceInfo, notify);} catch (DataIntegrityViolationException ive) { // 2. 唯一約束沖突,更新updateConfigInfo(configInfo, srcIp, srcUser, time, configAdvanceInfo, notify);}}

將數據持久化

addConfigInfo嘗試插入在一個事務里保存了config_info、config_tags_relation、his_config_info。

    @Overridepublic void addConfigInfo(final String srcIp, final String srcUser, final ConfigInfo configInfo,final Timestamp time, final Map<String, Object> configAdvanceInfo, final boolean notify) {//tjc 就是TransactionTemplate 這里其實就是做的mysql的數據寫入boolean result = tjt.execute(status -> {try {// 1. 保存config_infolong configId = addConfigInfoAtomic(-1, srcIp, srcUser, configInfo, time, configAdvanceInfo);// 2. 保存config_tags_relationString configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");addConfigTagsRelation(configId, configTags, configInfo.getDataId(), configInfo.getGroup(),configInfo.getTenant());/ 3. 記錄日志his_config_infoinsertConfigHistoryAtomic(0, configInfo, srcIp, srcUser, time, "I");} catch (CannotGetJdbcConnectionException e) {LogUtil.FATAL_LOG.error("[db-error] " + e.toString(), e);throw e;}return Boolean.TRUE;});}

當發生了唯一索引沖突的時候會去修改配置信息

    @Overridepublic void updateConfigInfo(final ConfigInfo configInfo, final String srcIp, final String srcUser,final Timestamp time, final Map<String, Object> configAdvanceInfo, final boolean notify) {boolean result = tjt.execute(status -> {try {// 1. 查詢config_infoConfigInfo oldConfigInfo = findConfigInfo(configInfo.getDataId(), configInfo.getGroup(),configInfo.getTenant());String appNameTmp = oldConfigInfo.getAppName();/*If the appName passed by the user is not empty, use the persistent user's appName,otherwise use db; when emptying appName, you need to pass an empty string*/if (configInfo.getAppName() == null) {configInfo.setAppName(appNameTmp);}// 2. 更新config_infoupdateConfigInfoAtomic(configInfo, srcIp, srcUser, time, configAdvanceInfo);String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");if (configTags != null) {// 3. 刪除所有老tag config_tags_relationremoveTagByIdAtomic(oldConfigInfo.getId());// 4. 新增tag config_tags_relationaddConfigTagsRelation(oldConfigInfo.getId(), configTags, configInfo.getDataId(),configInfo.getGroup(), configInfo.getTenant());}// 5. 記錄日志insertConfigHistoryAtomic(oldConfigInfo.getId(), oldConfigInfo, srcIp, srcUser, time, "U");} catch (CannotGetJdbcConnectionException e) {LogUtil.FATAL_LOG.error("[db-error] " + e.toString(), e);throw e;}return Boolean.TRUE;});}

數據變更事件發布

當數據持久化之后會去發送一個配置數據變更的ConfigDataChangeEvent事件
AsyncNotifyService會監聽到ConfigDataChangeEvent事件來進行處理

    @Autowiredpublic AsyncNotifyService(ServerMemberManager memberManager) {this.memberManager = memberManager;// 將ConfigDataChangeEvent注冊到NotifyCentre。NotifyCenter.registerToPublisher(ConfigDataChangeEvent.class, NotifyCenter.ringBufferSize);// 注冊訂閱服務器以訂閱ConfigDataChangeEvent。NotifyCenter.registerSubscriber(new Subscriber() {@Overridepublic void onEvent(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;//獲取集群雖有節點列表Collection<Member> ipList = memberManager.allMembers();// 每個節點組成一個TaskQueue<NotifySingleTask> httpQueue = new LinkedList<NotifySingleTask>();Queue<NotifySingleRpcTask> rpcQueue = new LinkedList<NotifySingleRpcTask>();//遍歷集群節點信息 for (Member member : ipList) {//判斷是否是長輪詢if (!MemberUtil.isSupportedLongCon(member)) {// 添加一個長輪詢的異步dump任務httpQueue.add(new NotifySingleTask(dataId, group, tenant, tag, dumpTs, member.getAddress(),evt.isBeta));} else {// 添加一個長連接的異步dump任務rpcQueue.add(new NotifySingleRpcTask(dataId, group, tenant, tag, dumpTs, evt.isBeta, member));}}// 判斷并執行長輪詢的異步dump任務if (!httpQueue.isEmpty()) {ConfigExecutor.executeAsyncNotify(new AsyncTask(nacosAsyncRestTemplate, httpQueue));}// 判斷并執行長連接的異步dump任務if (!rpcQueue.isEmpty()) {ConfigExecutor.executeAsyncNotify(new AsyncRpcTask(rpcQueue));}}}@Overridepublic Class<? extends Event> subscribeType() {return ConfigDataChangeEvent.class;}});}

在接收到ConfigDataChangeEvent之后,如果Nacos2.0以上的版本,會創建一個RpcTask用以執行配置變更的通知,由內部類AsyncRpcTask執行,AsyncRpcTask具體邏輯如下所示。

    class AsyncRpcTask implements Runnable {private Queue<NotifySingleRpcTask> queue;public AsyncRpcTask(Queue<NotifySingleRpcTask> queue) {this.queue = queue;}@Overridepublic void run() {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.tag);syncRequest.setTenant(task.getTenant());Member member = task.member;// 如果是自身的數據變更,直接執行dump操作if (memberManager.getSelf().equals(member)) {if (syncRequest.isBeta()) {// 同步Beta配置dumpService.dump(syncRequest.getDataId(), syncRequest.getGroup(), syncRequest.getTenant(),syncRequest.getLastModified(), NetUtils.localIP(), true);} else {// 像連接自己節點的client端進行配置信息的推送dumpService.dump(syncRequest.getDataId(), syncRequest.getGroup(), syncRequest.getTenant(),syncRequest.getTag(), syncRequest.getLastModified(), NetUtils.localIP());}continue;}// 通知集群其他節點進行dumpif (memberManager.hasMember(member.getAddress())) {// start the health check and there are ips that are not monitored, put them directly in the notification queue, otherwise notifyboolean unHealthNeedDelay = memberManager.isUnHealth(member.getAddress());if (unHealthNeedDelay) {// target ip is unhealthy, then put it in the notification listConfigTraceService.logNotifyEvent(task.getDataId(), task.getGroup(), task.getTenant(), null,task.getLastModified(), InetUtils.getSelfIP(), ConfigTraceService.NOTIFY_EVENT_UNHEALTH,0, member.getAddress());// get delay time and set fail count to the taskasyncTaskExecute(task);} else {if (!MemberUtil.isSupportedLongCon(member)) {asyncTaskExecute(new NotifySingleTask(task.getDataId(), task.getGroup(), task.getTenant(), task.tag,task.getLastModified(), member.getAddress(), task.isBeta));} else {try {configClusterRpcClientProxy.syncConfigChange(member, syncRequest, new AsyncRpcNotifyCallBack(task));} catch (Exception e) {MetricsMonitor.getConfigNotifyException().increment();asyncTaskExecute(task);}}}} else {//No nothig if  member has offline.}}}}

向連接自己的Client端發送配置變更通知

這里首先創建了一個ConfigChangeClusterSyncRequest,并將配置信息寫入。然后獲取集群信息,通知相應的Server處理的數據同步請求。同步配置變更信息的核心邏輯由DumpService來執行。我們主要查看同步正式配置的操作,DumpService的dump方法如下所示。

    public void dump(String dataId, String group, String tenant, String tag, long lastModified, String handleIp,boolean isBeta) {//dataId+分組    String groupKey = GroupKey2.getKey(dataId, group, tenant);//dataId+分組+是否灰度發布+標簽String taskKey = String.join("+", dataId, group, tenant, String.valueOf(isBeta), tag);//添加dump任務dumpTaskMgr.addTask(taskKey, new DumpTask(groupKey, tag, lastModified, handleIp, isBeta));DUMP_LOG.info("[dump-task] add task. groupKey={}, taskKey={}", groupKey, taskKey);}

在該方法中,這里會根據配置變更信息,提交一個異步的DumpTask任務,后續會由DumpProcessor類的process方法進行處理

DumpProcessor執行dump任務

public class DumpProcessor implements NacosTaskProcessor {final DumpService dumpService;public DumpProcessor(DumpService dumpService) {this.dumpService = dumpService;}@Overridepublic boolean process(NacosTask task) {final PersistService persistService = dumpService.getPersistService();DumpTask dumpTask = (DumpTask) task;String[] pair = GroupKey2.parseKey(dumpTask.getGroupKey());String dataId = pair[0];String group = pair[1];String tenant = pair[2];long lastModified = dumpTask.getLastModified();String handleIp = dumpTask.getHandleIp();boolean isBeta = dumpTask.isBeta();String tag = dumpTask.getTag();//構建 ConfigDumpEventConfigDumpEvent.ConfigDumpEventBuilder build = ConfigDumpEvent.builder().namespaceId(tenant).dataId(dataId).group(group).isBeta(isBeta).tag(tag).lastModifiedTs(lastModified).handleIp(handleIp);//如果是灰度發布if (isBeta) {// if publish beta, then dump config, update beta cacheConfigInfo4Beta cf = persistService.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.encryptedDataKey(Objects.isNull(cf) ? null : cf.getEncryptedDataKey());return DumpConfigHandler.configDump(build.build());}//判斷是否有標簽 if (StringUtils.isBlank(tag)) {// 1. 查詢當前配置ConfigInfo cf = persistService.findConfigInfo(dataId, group, tenant);build.remove(Objects.isNull(cf));// 2. 設置ConfigDumpEvent的content為最新的contentbuild.content(Objects.isNull(cf) ? null : cf.getContent());build.type(Objects.isNull(cf) ? null : cf.getType());build.encryptedDataKey(Objects.isNull(cf) ? null : cf.getEncryptedDataKey());} else {ConfigInfo4Tag cf = persistService.findConfigInfo4Tag(dataId, group, tenant, tag);build.remove(Objects.isNull(cf));build.content(Objects.isNull(cf) ? null : cf.getContent());}// 3. 執行ConfigDumpEvent處理return DumpConfigHandler.configDump(build.build());}
}
    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();final String encryptedDataKey = event.getEncryptedDataKey();......//真正執行dump請求的地方result = ConfigCacheService.dump(dataId, group, namespaceId, content, lastModified, type, encryptedDataKey);......}
    /*** Save config file and update md5 value in cache.** @param dataId         dataId string value.* @param group          group string value.* @param tenant         tenant string value.* @param content        content string value.* @param lastModifiedTs lastModifiedTs.* @param type           file type.* @return dumpChange success or not.*/public static boolean dump(String dataId, String group, String tenant, String content, long lastModifiedTs,String type, String encryptedDataKey) {String groupKey = GroupKey2.getKey(dataId, group, tenant);CacheItem ci = makeSure(groupKey, encryptedDataKey, false);ci.setType(type);final int lockResult = tryWriteLock(groupKey);assert (lockResult != 0);if (lockResult < 0) {DUMP_LOG.warn("[dump-error] write lock failed. {}", groupKey);return false;}try {//獲取md5final String md5 = MD5Utils.md5Hex(content, Constants.ENCODE);if (lastModifiedTs < ConfigCacheService.getLastModifiedTs(groupKey)) {DUMP_LOG.warn("[dump-ignore] the content is old. groupKey={}, md5={}, lastModifiedOld={}, "+ "lastModifiedNew={}", groupKey, md5, ConfigCacheService.getLastModifiedTs(groupKey),lastModifiedTs);return true;}if (md5.equals(ConfigCacheService.getContentMd5(groupKey)) && DiskUtil.targetFile(dataId, group, tenant).exists()) {DUMP_LOG.warn("[dump-ignore] ignore to save cache file. groupKey={}, md5={}, lastModifiedOld={}, "+ "lastModifiedNew={}", groupKey, md5, ConfigCacheService.getLastModifiedTs(groupKey),lastModifiedTs);//若直接讀取,則從持久層獲取數據      } else if (!PropertyUtil.isDirectRead()) {//持久化數據DiskUtil.saveToDisk(dataId, group, tenant, content);}//更新md5值updateMd5(groupKey, md5, lastModifiedTs, encryptedDataKey);return true;} catch (IOException ioe) {DUMP_LOG.error("[dump-exception] save disk error. " + groupKey + ", " + ioe);if (ioe.getMessage() != null) {String errMsg = ioe.getMessage();if (NO_SPACE_CN.equals(errMsg) || NO_SPACE_EN.equals(errMsg) || errMsg.contains(DISK_QUATA_CN) || errMsg.contains(DISK_QUATA_EN)) {// Protect from disk full.FATAL_LOG.error("磁盤滿自殺退出", ioe);System.exit(0);}}return false;} finally {releaseWriteLock(groupKey);}}
    public static void updateMd5(String groupKey, String md5, long lastModifiedTs, String encryptedDataKey) {CacheItem cache = makeSure(groupKey, encryptedDataKey, false);if (cache.md5 == null || !cache.md5.equals(md5)) {cache.md5 = md5;cache.lastModifiedTs = lastModifiedTs;//發送本地數據變更事件NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey));}}

RpcConfigChangeNotifier

    @Overridepublic void onEvent(LocalDataChangeEvent event) {String groupKey = event.groupKey;boolean isBeta = event.isBeta;List<String> betaIps = event.betaIps;String[] strings = GroupKey.parseKey(groupKey);String dataId = strings[0];String group = strings[1];String tenant = strings.length > 2 ? strings[2] : "";String tag = event.tag;//配置數據變更configDataChanged(groupKey, dataId, group, tenant, isBeta, betaIps, tag);}
    /*** adaptor to config module ,when server side config change ,invoke this method.** @param groupKey groupKey*/public void configDataChanged(String groupKey, String dataId, String group, String tenant, boolean isBeta,List<String> betaIps, String tag) {//獲取注冊的Client列表Set<String> listeners = configChangeListenContext.getListeners(groupKey);if (CollectionUtils.isEmpty(listeners)) {return;}int notifyClientCount = 0;//遍歷client列表for (final String client : listeners) {//拿到grpc連接Connection connection = connectionManager.getConnection(client);if (connection == null) {continue;}ConnectionMeta metaInfo = connection.getMetaInfo();//beta ips check.String clientIp = metaInfo.getClientIp();String clientTag = metaInfo.getTag();if (isBeta && betaIps != null && !betaIps.contains(clientIp)) {continue;}//tag checkif (StringUtils.isNotBlank(tag) && !tag.equals(clientTag)) {continue;}//構建請求參數ConfigChangeNotifyRequest notifyRequest = ConfigChangeNotifyRequest.build(dataId, group, tenant);//構建推送任務RpcPushTask rpcPushRetryTask = new RpcPushTask(notifyRequest, 50, client, clientIp, metaInfo.getAppName());//推送任務 向客戶端發送變更通知push(rpcPushRetryTask);notifyClientCount++;}Loggers.REMOTE_PUSH.info("push [{}] clients ,groupKey=[{}]", notifyClientCount, groupKey);}

這里實際上也是一個異步執行的過程,推送任務RpcPushTask會被提交到ClientConfigNotifierServiceExecutor來計劃執行,第一次會立即推送配置,即調用RpcPushTask的run方法,如果失敗則延遲重試次數x2的秒數再次執行,直到超過重試次數,主動注銷當前連接

    private void push(RpcPushTask retryTask) {ConfigChangeNotifyRequest notifyRequest = retryTask.notifyRequest;// 判斷是否重試次數達到限制if (retryTask.isOverTimes()) {Loggers.REMOTE_PUSH.warn("push callback retry fail over times .dataId={},group={},tenant={},clientId={},will unregister client.",notifyRequest.getDataId(), notifyRequest.getGroup(), notifyRequest.getTenant(),retryTask.connectionId);// 主動注銷連接connectionManager.unregister(retryTask.connectionId);} else if (connectionManager.getConnection(retryTask.connectionId) != null) {// first time :delay 0s; sencond time:delay 2s  ;third time :delay 4s// 嘗試執行配置推送ConfigExecutor.getClientConfigNotifierServiceExecutor().schedule(retryTask, retryTask.tryTimes * 2, TimeUnit.SECONDS);} else {// client is already offline,ingnore task.}}

RpcPushTask

        @Overridepublic void run() {tryTimes++;if (!tpsMonitorManager.applyTpsForClientIp(POINT_CONFIG_PUSH, connectionId, clientIp)) {push(this);} else {//推送任務rpcPushService.pushWithCallback(connectionId, notifyRequest, new AbstractPushCallBack(3000L) {@Overridepublic void onSuccess() {tpsMonitorManager.applyTpsForClientIp(POINT_CONFIG_PUSH_SUCCESS, connectionId, clientIp);}@Overridepublic void onFail(Throwable e) {tpsMonitorManager.applyTpsForClientIp(POINT_CONFIG_PUSH_FAIL, connectionId, clientIp);Loggers.REMOTE_PUSH.warn("Push fail", e);push(RpcPushTask.this);}}, ConfigExecutor.getClientConfigNotifierServiceExecutor());}}

客戶端處理變更通知

ClientWorker

 private void initRpcClientHandler(final RpcClient rpcClientInner) {rpcClientInner.registerServerRequestHandler((request) -> {if (request instanceof ConfigChangeNotifyRequest) {ConfigChangeNotifyRequest configChangeNotifyRequest = (ConfigChangeNotifyRequest) request;LOGGER.info("[{}] [server-push] config changed. dataId={}, group={},tenant={}",rpcClientInner.getName(), configChangeNotifyRequest.getDataId(),configChangeNotifyRequest.getGroup(), configChangeNotifyRequest.getTenant());String groupKey = GroupKey.getKeyTenant(configChangeNotifyRequest.getDataId(), configChangeNotifyRequest.getGroup(),configChangeNotifyRequest.getTenant());CacheData cacheData = cacheMap.get().get(groupKey);if (cacheData != null) {synchronized (cacheData) {cacheData.getLastModifiedTs().set(System.currentTimeMillis());cacheData.setSyncWithServer(false);//向阻塞隊列中添加元素 觸發長連接的執行notifyListenConfig();}}//返回客戶端響應return new ConfigChangeNotifyResponse();}return null;});}

后續邏輯處理看客戶端的邏輯

向集群節點推送通知

AsyncRpcTask

 configClusterRpcClientProxy.syncConfigChange(member, syncRequest, new AsyncRpcNotifyCallBack(task));
    public void syncConfigChange(Member member, ConfigChangeClusterSyncRequest request, RequestCallBack callBack)throws NacosException {clusterRpcClientProxy.asyncRequest(member, request, callBack);}
    public void asyncRequest(Member member, Request request, RequestCallBack callBack) throws NacosException {RpcClient client = RpcClientFactory.getClient(memberClientKey(member));if (client != null) {client.asyncRequest(request, callBack);} else {throw new NacosException(CLIENT_INVALID_PARAM, "No rpc client related to member: " + member);}}

ConfigChangeClusterSyncRequestHandler 處理ConfigChangeClusterSyncRequest

    @TpsControl(pointName = "ClusterConfigChangeNotify")@Overridepublic ConfigChangeClusterSyncResponse handle(ConfigChangeClusterSyncRequest configChangeSyncRequest,RequestMeta meta) throws NacosException {//是否是灰度if (configChangeSyncRequest.isBeta()) {dumpService.dump(configChangeSyncRequest.getDataId(), configChangeSyncRequest.getGroup(),configChangeSyncRequest.getTenant(), configChangeSyncRequest.getLastModified(), meta.getClientIp(),true);} else {//向連接自己節點的 client端進行數據的同步dumpService.dump(configChangeSyncRequest.getDataId(), configChangeSyncRequest.getGroup(),configChangeSyncRequest.getTenant(), configChangeSyncRequest.getLastModified(), meta.getClientIp());}return new ConfigChangeClusterSyncResponse();}

服務端配置變更總結

1.當從服務端進行配置的新增和修改會先將數據給持久化到內嵌的數據源或者外部的比如mysql中
2.然后發送配置變更的事件會將配置通過GRPC協議同步給連接自己的cleint端 和連接集群中其他節點的客戶端

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/213231.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/213231.shtml
英文地址,請注明出處:http://en.pswp.cn/news/213231.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

進行生成簡單數字圖片

1.之前只能做一些圖像預測,我有個大膽的想法,如果神經網絡正向就是預測圖片的類別,如果我只有一個類別那就可以進行生成圖片,專業術語叫做gan對抗網絡 2.訓練代碼 import torch import torch.nn as nn import torch.optim as optim import torchvision.transforms as transfo…

飛天使-rsync大文件斷點續傳與vim批量刪除

文章目錄 rsync 斷點續傳&#xff0c;親測有效vim 批量刪除消息 rsync 斷點續傳&#xff0c;親測有效 rsync -vzrtp -P --append -e "/usr/bin/ssh -p 22 -o StrictHostKeyCheckingno" m.tar.gz root10.0.0.1:/tmp后臺運行 screem 既可 或者 nohup rsync -vzrt…

【華為od】存在一個m*n的二維數組,其成員取值范圍為0,1。其中值為1的元素具備擴散性,每經過1S,將上下左右值為0的元素同化為1。

存在一個m*n的二維數組,其成員取值范圍為0,1。其中值為1的元素具備擴散性,每經過1S,將上下左右值為0的元素同化為1。將數組所有成員初始化為0,將矩陣的[i, j]和[m,n]位置上元素修改成1后,在經過多長時間所有元素變為1。 輸入描述 輸入的前兩個數字是矩陣大小。后面是數字…

盛域宏數合伙人張天:AI時代,數字化要以AI重構

大數據產業創新服務媒體 ——聚焦數據 改變商業 在這個飛速發展的科技時代&#xff0c;數字化已經深刻地改變了我們的生活和商業方式。信息技術的迅猛發展使得數據成為現代社會最寶貴的資源之一。數字化已經不再是可選項&#xff0c;而是企業持續發展的必由之路。背靠著數據的…

【React】路由的基礎使用

react-router-dom6的基礎使用 1、安裝依賴 npm i react-router-dom默認安裝最新版本的 2、在src/router/index.js import { createBrowserRouter } from "react-router-dom"/* createBrowserRouter&#xff1a;[/home]--h5路由createHashRouter&#xff1a;[/#/ho…

Linux訪問NFS存儲及自動掛載

本章主要介紹NFS客戶端的使用 創建NFS服務器并通過NFS共享一個目錄在客戶端上訪問NFS共享的目錄自動掛載的配置和使用 1.1 訪問NFS存儲 前面那篇介紹了本地存儲&#xff0c;本章就來介紹如何使用網絡上上的存儲設備。NFS即網絡文件系統&#xff0c;所實現的是Linux和Linux之…

通信:mqtt學習網址

看這個網址&#xff1a;講的很詳細&#xff0c;后面補實戰例子 第一章 - MQTT介紹 MQTT協議中文版 (gitbooks.io)https://mcxiaoke.gitbooks.io/mqtt-cn/content/mqtt/01-Introduction.html

【網絡編程】-- 04 UDP

網絡編程 6 UDP 6.1 初識Tomcat 服務端 自定義 STomcat S 客戶端 自定義 C瀏覽器 B 6.2 UDP 6.2.1 udp實現發送消息 接收端&#xff1a; package com.duo.lesson03;import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketExceptio…

【論文極速讀】LVM,視覺大模型的GPT時刻?

【論文極速讀】LVM&#xff0c;視覺大模型的GPT時刻&#xff1f; FesianXu 20231210 at Baidu Search Team 前言 這一周&#xff0c;LVM在arxiv上剛掛出不久&#xff0c;就被眾多自媒體宣傳為『視覺大模型的GPT時刻』&#xff0c;筆者抱著強烈的好奇心&#xff0c;在繁忙工作之…

m.2固態硬盤怎么選擇?

一、什么是固態硬盤 固態硬盤又稱SSD&#xff0c;是Solid State Drive的簡稱&#xff0c;由于采用了閃存技術&#xff0c;其處理速度遠遠超過傳統的機械硬盤&#xff0c;這主要是因為固態硬盤的數據以電子的方式存儲在閃存芯片中&#xff0c;不需要像機械硬盤那樣通過磁頭讀寫磁…

linux查看筆記本電池健康情況

本人的老電腦&#xff0c;筆記本x1 carbon 5th 用久了&#xff0c;電池不行了&#xff0c;實際容量只有27.657%&#xff0c;充電到40%的時候&#xff0c;一瞬間彪滿100%。到某寶淘了一個 model: 01AV430的電池,等更換了再看看使用情況 $ upower --help 用法&#xff1a;upower…

Linux 安裝 中間件 Tuxedo

安裝步聚 一、首先&#xff0c;下載中間件安裝包&#xff1a; tuxedo121300_64_Linux_01_x86 Tuxedo下載地址&#xff1a; Oracle Tuxedo Downloads 二、新建Oracle用戶組&#xff08;創建Oracle用戶時&#xff0c;需要root權限操作&#xff0c;登陸&#xff09; [rootloca…

【CiteSpace】引文可視化分析軟件CiteSpace下載與安裝

CiteSpace 譯“引文空間”&#xff0c;是一款著眼于分析科學分析中蘊含的潛在知識&#xff0c;是在科學計量學、數據可視化背景下逐漸發展起來的引文可視化分析軟件。由于是通過可視化的手段來呈現科學知識的結構、規律和分布情況&#xff0c;因此也將通過此類方法分析得到的可…

【Spring教程23】Spring框架實戰:從零開始學習SpringMVC 之 SpringMVC簡介與SpringMVC概述

目錄 1&#xff0c;SpringMVC簡介2、SpringMVC概述 歡迎大家回到《Java教程之Spring30天快速入門》&#xff0c;本教程所有示例均基于Maven實現&#xff0c;如果您對Maven還很陌生&#xff0c;請移步本人的博文《如何在windows11下安裝Maven并配置以及 IDEA配置Maven環境》&…

python使用vtk與mayavi三維可視化繪圖

VTK&#xff08;Visualization Toolkit&#xff09;是3D計算機圖形學、圖像處理和可視化的強大工具。它可以通過Python綁定使用&#xff0c;適合于科學數據的復雜可視化。Mayavi 依賴于 VTK (Visualization Toolkit)&#xff0c;一個用于 3D 計算機圖形、圖像處理和可視化的強大…

AS安裝目錄

編輯器&#xff1a; sdk: gradle: gradle使用的jdk目錄&#xff1a;Gradle使用的jdk是android studio安裝目錄下的jbr 成功項目的android studio配置&#xff1a;

H264碼流結構

視頻編碼的碼流結構是指視頻經過編碼之后得到的二進制數據是怎么組織的&#xff0c;或者說&#xff0c;就是編碼后的碼流我們怎么將一幀幀編碼后的圖像數據分離出來&#xff0c;以及在二進制碼流數據中&#xff0c;哪一塊數據是一幀圖像&#xff0c;哪一塊數據是另外一幀圖像。…

C++面試寶典第4題:合并鏈表

題目 有一個鏈表&#xff0c;其節點聲明如下&#xff1a; struct TNode {int nData;struct TNode *pNext;TNode(int x) : nData(x), pNext(NULL) {} }; 現給定兩個按升序排列的單鏈表pA和pB&#xff0c;請編寫一個函數&#xff0c;實現這兩個單鏈表的合并。合并后&#xff0c;…

scheduleatfixedrate詳解

scheduleatfixedrate詳解 大家好&#xff0c;我是免費搭建查券返利機器人賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01;在Java開發中&#xff0c;我們常常需要執行定時任務&#xff0c;并且需要保證任務按照一定…

使用Java實現基數排序算法

文章目錄 基數排序算法 基數排序算法 &#xff08;1&#xff09;基本思想&#xff1a;將整數按位數切割成不同的數字&#xff0c;然后按每個位數分別比較。 &#xff08;2&#xff09;排序過程&#xff1a;將所有待比較數值&#xff08;正整數&#xff09;統一為同樣的數位長…