nacos解析-解密服务端保存配置数据原理

本文基于nacos-2.0.3版本

本文分析nacos服务端保存配置数据的原理。
通过上一篇文章《nacos解析-客户端是如何拉取配置信息的》我们知道了客户端通过http协议将请求发送到服务端,链接的URL为:

http://127.0.0.1:8848/nacos/v1/cs/configs

通过URL的path我们很容易在服务端找到处理该请求的类:ConfigController。该类位于config模块下,通过名字就可以知道该模块用于配置管理。
ConfigController提供了对配置的增删改查方法,比如,保存配置的方法是publishConfig(),查询配置的方法是getConfig(),本文主要分析publishConfig()方法。
下面是publishConfig()方法的源码。

    @PostMapping//采用restful风格,post请求会调用该方法
    @Secured(action = ActionTypes.WRITE, parser = ConfigResourceParser.class)
    public Boolean publishConfig(HttpServletRequest request, HttpServletResponse response,
            @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 {
        //srcIp:请求方地址
        final String srcIp = RequestUtil.getRemoteIp(request);
        //requestIpApp:请求方应用名
        final String requestIpApp = RequestUtil.getAppName(request);
        srcUser = RequestUtil.getSrcUserName(request);
        //配置的类型,可以是json、xml,默认是text
        if (!ConfigType.isValidType(type)) {
            type = ConfigType.getDefaultType().getType();
        }
        //下面是对参数的合法性检查,包括必输性和长度
        ParamUtils.checkTenant(tenant);
        ParamUtils.checkParam(dataId, group, "datumId", content);
        ParamUtils.checkParam(tag);
        Map<String, Object> configAdvanceInfo = new HashMap<String, Object>(10);
        MapUtil.putIfValNoNull(configAdvanceInfo, "config_tags", configTags);
        MapUtil.putIfValNoNull(configAdvanceInfo, "desc", desc);
        MapUtil.putIfValNoNull(configAdvanceInfo, "use", use);
        MapUtil.putIfValNoNull(configAdvanceInfo, "effect", effect);
        MapUtil.putIfValNoNull(configAdvanceInfo, "type", type);
        MapUtil.putIfValNoNull(configAdvanceInfo, "schema", schema);
        ParamUtils.checkParam(configAdvanceInfo);
        //检查dataId是否是非法格式,默认nacos对dataId没有特殊要求
        if (AggrWhitelist.isAggrDataId(dataId)) {
            LOGGER.warn("[aggr-conflict] {} attempt to publish single data, {}, {}", RequestUtil.getRemoteIp(request),
                    dataId, group);
            throw new NacosException(NacosException.NO_RIGHT, "dataId:" + dataId + " is aggr");
        }
        
        final Timestamp time = TimeUtils.getCurrentTime();
        //betaIps没有找到相关说明,我理解应该是发布灰度服务的机器IP
        //带有betaIps的配置,表示是灰度服务使用的
        String betaIps = request.getHeader("betaIps");
        ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content);
        configInfo.setType(type);
        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 {
            // beta publish
            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;
    }

总的来说,publishConfig()一共做了三件事:

  1. 检查参数是否合法;
  2. 将数据保存到数据库,并通知监听器;
  3. 记录日志。

下面介绍配置如何保存到数据库。
nacos提供了两种保存数据库的方法,一种是将数据保存到内置数据库,内置数据库使用Derby实现,实现类是EmbeddedStoragePersistServiceImpl,另一种是保存到mysql数据库,实现类是ExternalStoragePersistServiceImpl。下面分析一下前者,后者与前者的逻辑基本一样。
保存到数据库使用的方法是persistService.insertOrUpdate()。

    public void insertOrUpdate(String srcIp, String srcUser, ConfigInfo configInfo, Timestamp time,
            Map<String, Object> configAdvanceInfo, boolean notify) {
        //先调用findConfigInfo查询配置是否已经存在了,如果不存在,则插入,否则更新
        //查询逻辑是根据该方法入参查询config_info表
        if (Objects.isNull(findConfigInfo(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()))) {
            addConfigInfo(srcIp, srcUser, configInfo, time, configAdvanceInfo, notify);
        } else {
            updateConfigInfo(configInfo, srcIp, srcUser, time, configAdvanceInfo, notify);
        }
    }

addConfigInfo()最终调用下面的方法:

    private void addConfigInfo(final String srcIp, final String srcUser, final ConfigInfo configInfo,
            final Timestamp time, final Map<String, Object> configAdvanceInfo, final boolean notify,
            BiConsumer<Boolean, Throwable> consumer) {
        
        try {
        	//命名空间,默认是空
            final String tenantTmp =
                    StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant();
            configInfo.setTenant(tenantTmp);
            //生成config_info表的主键
            //主键生成规则采用雪花算法
            long configId = idGeneratorManager.nextId(RESOURCE_CONFIG_INFO_ID);
            //生产CONFIG_HISTORY_ID表的主键
            long hisId = idGeneratorManager.nextId(RESOURCE_CONFIG_HISTORY_ID);
            //生成一条insert语句,用于将dataId、groupId、tenantId和content插入config_info表
            addConfigInfoAtomic(configId, srcIp, srcUser, configInfo, time, configAdvanceInfo);
            String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");
            //生成insert语句,用于将配置与tag之间的关联关系插入config_tags_relation表
            addConfigTagsRelation(configId, configTags, configInfo.getDataId(), configInfo.getGroup(),
                    configInfo.getTenant());
            //生成insert语句,将配置更改历史插入his_config_info表
            insertConfigHistoryAtomic(hisId, configInfo, srcIp, srcUser, time, "I");
            //集群模式下生成ConfigDumpEvent事件,
            //根据注释说明,如果nacos是集群模式,事件将通过raft算法发送到其他机器
            EmbeddedStorageContextUtils.onModifyConfigInfo(configInfo, srcIp, time);
            //执行上面的insert语句,这些insert语句都在一个事务里面,使用spring的JdbcTemplate执行
            databaseOperate.blockUpdate(consumer);
        } finally {
        	//清空上下文数据
            EmbeddedStorageContextUtils.cleanAllContext();
        }
    }

addConfigInfo()还是挺简单的。
最后再来看一下ConfigChangePublisher.notifyConfigChange()方法。ConfigChangePublisher.notifyConfigChange()的作用主要是通知集群里面的其他机器告知配置发生了变化,不过在集群模式下使用内置数据库不会发出该通知,因为EmbeddedStorageContextUtils.onModifyConfigInfo()方法已经发过了。该方法原理在后面文章做介绍。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值