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.mzph.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…

盛域宏数合伙人张天: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

【论文极速读】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;不需要像机械硬盘那样通过磁头读写磁…

【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;…

Vuex快速上手

一、Vuex 概述 目标&#xff1a;明确Vuex是什么&#xff0c;应用场景以及优势 1.是什么 Vuex 是一个 Vue 的 状态管理工具&#xff0c;状态就是数据。 大白话&#xff1a;Vuex 是一个插件&#xff0c;可以帮我们管理 Vue 通用的数据 (多组件共享的数据)。例如&#xff1a;购…

VSCode SSH登录服务器 提示XHR failed

设置->搜索“代理” 把图中的√去掉 重启 即可

tidb安装 centos7单机集群

安装 [rootlocalhost ~]# curl --proto https --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh [rootlocalhost ~]# source .bash_profile [rootlocalhost ~]# which tiup [rootlocalhost ~]# tiup playground v6.1.0 --db 2 --pd 3 --kv 3 --host 192.168.1…

SQL自学通之函数 :对数据的进一步处理

目录 一、目标 二、汇总函数 COUNT SUM AVG MAX MIN VARIANCE STDDEV 三、日期/时间函数 ADD_MONTHS LAST_DAY MONTHS_BETWEEN NEW_TIME NEXT_DAY SYSDATE 四、数学函数 ABS CEIL 和FLOOR COS、 COSH 、SIN 、SINH、 TAN、 TANH EXP LN and LOG MOD POW…

【SpringBoot教程】SpringBoot 实现前后端分离的跨域访问(Nginx)

作者简介&#xff1a;大家好&#xff0c;我是撸代码的羊驼&#xff0c;前阿里巴巴架构师&#xff0c;现某互联网公司CTO 联系v&#xff1a;sulny_ann&#xff08;17362204968&#xff09;&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗…

Mybatis之核心配置文件详解、默认类型别名、Mybatis获取参数值的两种方式

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

arm-none-eabi-gcc not find

解决办法&#xff1a;安装&#xff1a;gcc-arm-none-eabi sudo apt install gcc-arm-none-eabi; 如果上边解决问题了就不用管了&#xff0c;如果解决不了&#xff0c;加上下面这句试试运气&#xff1a; $ sudo apt-get install lsb-core看吧方正我是运气还不错&#xff0c;感…