nacos配置中心的底层原理以及实现

Nacos源码系列整体栏目


【一】nacos服务注册底层源码详解

【二】nacos服务发现底层源码详解

【三】nacos的心跳机制底层源码详解

【四】nacos配置中心的底层源码详解


nacos配置中心源码分析

  • 一,nacos的配置中心
    • 1,nacos客户端源码分析
      • 1.1,nacos客户端获取服务配置
      • 1.2,nacos的服务配置监听
      • 1.3,客户端总结
    • 2,nacos配置中心服务端详解
      • 2.1,服务端获取全部配置
      • 2.2,服务端将配置存储磁盘

一,nacos的配置中心

主要就是通过这个nacos来作为一个配置中心,来统一管理这个配置

1,nacos客户端源码分析

nacos客户端所有的这个文件配置实现主要是在这个NacosNamingService的类下面,那么这个配置中心主要是在这个NacosConfigService的这个类下面。该接口下面主要有一些获取配置,发布配置,增加监听器,删除配置,删除监听器等操作。

public interface ConfigService {
    //获取配置
    String getConfig();
    //删除配置
    boolean removeConfig(String dataId, String group);
    //发布
    boolean publishConfig();
    //监听
    void addListener();
    //删除监听器
    void removeListener();
}

1.1,nacos客户端获取服务配置

在加载完所有的context上下文之后,客户端就回去拉取这个注册中心里面的这个全部配置文件

@Override
public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
    return getConfigInner(namespace, dataId, group, timeoutMs);
}

然后在这个getConfigInner方法里面,就是具体的拉取配置这个实现

private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException{
    // 优先使用本地配置
    String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
    //如果本地配置不为空,则直接返回
    if (content != null) {
        return content;
    }
    //如果本地配置为空,就会去服务端那边拉取这个全部的配置文件
    //需要通过这个http请求发起这个远程调用
    try{
        //拉取这个需要的配置
        String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs);
        //保存这个结果到本地
        cr.setContent(ct[0]);
    }
    
}

这个读取的本地配置的具体实现如下,主要是通过这个getFailover方法实现

public static String getFailover(String serverName, String dataId, String group, String tenant) {
    //获取这个本地文件,
    File localPath = getFailoverFile(serverName, dataId, group, tenant);
    //如果本地文件为空,则直接return返回
    if (!localPath.exists() || !localPath.isFile()) {
        return null;
    }
    //本地文件不为空,则读取
    return readFile(localPath);
}

如果本地为空,则需要去向这个服务端的配置中心发起http请求,并且最后会通过这个接口回调来判断这个响应的状态码。主要是在这个getServerConfig的方法里面具体实现

public String[] getServerConfig(String dataId, String group, String tenant, long readTimeout) throws NacosException {
    HttpRestResult<String> result = null;
    //建立http请求
    result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
    switch (result.getCode()) {
            case HttpURLConnection.HTTP_OK: ...省略
            case HttpURLConnection.HTTP_NOT_FOUND:
            case HttpURLConnection.HTTP_CONFLICT:
            case HttpURLConnection.HTTP_FORBIDDEN:
            default:
}

1.2,nacos的服务配置监听

在整个容器启动完成之后,就会去调用这个监听器。nacos主要是在这个NacosContextRefresher类下面来实现这个监听,其实现了这个ApplicationListener这个接口,就是一个nacos的一个上下文的一个刷新流。构造方法如下

public NacosContextRefresher(NacosRefreshProperties refreshProperties,NacosRefreshHistory refreshHistory, ConfigService configService) {
    /刷新配置文件
	this.refreshProperties = refreshProperties;
    //刷新历史文件
	this.refreshHistory = refreshHistory;
	this.configService = configService;
}

在这个类里面,会调用一个onApplicationEvent的事件方法,里面就会去进行一个nacos的监听的一个注册。

@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
	// many Spring context
	if (this.ready.compareAndSet(false, true)) {
        //nacos的监听注册
		this.registerNacosListenersForApplications();
	}
}

其注册的nacos的监听器的具体方法如下,就是会去获取nacos的全部的配置文件,然后在获取id之后,通过这个id对这个服务进行一个监听。

private void registerNacosListenersForApplications() {
	if (refreshProperties.isEnabled()) {
		for (NacosPropertySource nacosPropertySource : NacosPropertySourceRepository
				.getAll()) {
            //获取id
			String dataId = nacosPropertySource.getDataId();
			registerNacosListener(nacosPropertySource.getGroup(), dataId);
		}
	}
}

其监听这个nacos的主要方法registerNacosListener的具体实现如下,当配置发生变化的时候,这个监听方法就会发起一个调用,就会对立面的这个配置进行一个更新和替换。每一次更新都会有一个历史版本,

private void registerNacosListener(final String group, final String dataId){
    Listener listener = listenerMap.computeIfAbsent(dataId, i -> new Listener() {
        //当配置发生变化的时候,这个监听方法就会发起一个调用
		@Override
		public void receiveConfigInfo(String configInfo) {
            //记录这个历史版本
            refreshHistory.add(dataId, md5);
            //发布这个监听事件
            applicationContext.publishEvent(
						new RefreshEvent(this, null, "Refresh Nacos config"));
        }
}

最后会去调用一个refresh方法,会进行一个环境的刷新,会将新的参数和原来的参数进行一个比较,通过发布这个环境变更事件,对做出改变的值进行一个更新操作。

public synchronized Set<String> refresh() {
	Set<String> keys = refreshEnvironment();
	this.scope.refreshAll();
	return keys;
}

如果感知到这个对应的配置有改变的操作之后,就会清除当前的配置实例,会将新的实例重新通过这个bean工厂进行一个重新getBean的一个操作。

1.3,客户端总结

就是在这个客户端进行启动的时候,就会优先拉取本地的配置,如果本地配置不存在,那么就会和这个服务端建立这个http请求,然后去拉取这个服务端的全部配置,就是配置中心的全部配置。在拉取到全部配置之后,会去获取每一个配置文件的dataId,然后通过这个id对服务端的每一个配置文件进行一个监听的操作。每当服务端这边的配置文件出现修改的时候,就可以通过这个监听器进行到一个感知,然后这个客户端也会对对应的配置文件进行修改,每一份修改的配置都会存储在这个nacos配置文件里面,会作为一个历史文件保留。

2,nacos配置中心服务端详解

2.1,服务端获取全部配置

这个主要是在这个ConfigController这个类下面,在服务端的nacos-config的模块下面。
里面有一个重要的方法,就是getConfig的这个方法,

@GetMapping
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void getConfig(HttpServletRequest request, HttpServletResponse response,
        @RequestParam("dataId") String dataId, @RequestParam("group") String group,
        @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant,
        @RequestParam(value = "tag", required = false) String tag)
        throws IOException, ServletException, NacosException {
    // check tenant
    ParamUtils.checkTenant(tenant);
    tenant = NamespaceUtil.processNamespaceParameter(tenant);
    // check params
    ParamUtils.checkParam(dataId, group, "datumId", "content");
    ParamUtils.checkParam(tag);
    final String clientIp = RequestUtil.getRemoteIp(request);
    //这里的方法就是记性具体的获取配置信息
    inner.doGetConfig(request, response, dataId, group, tenant, tag, clientIp);
}

接下来就想看看这个doGetConfig方法。主要是从这个本地文件读取这个配置,而不是读取这个数据库的配置。这个文件主要存储在这个nacos的data的文件目录下。

public String doGetConfig(HttpServletRequest request, HttpServletResponse response, String dataId, String group, String tenant, String tag, String clientIp) throws IOException, ServletException{
    File file = null;
    //md5对这些文件里面的数据进行一个加密的操作
    md5 = cacheItem.getMd54Beta();
    //从磁盘里面获取这个文件的信息
    file = DiskUtil.targetBetaFile(dataId, group, tenant);
}

2.2,服务端将配置存储磁盘

主要在这个DumpService抽象类下面,有从内存中将全部配置文件存入到这个磁盘里面。
通过快捷键ctrl + alt + b可以查看这个抽象类下面的全部实现,主要有如下两个类,分别是EmbeddedDumpService和这个ExternalDumpService类。
nacos配置中心的底层原理以及实现_第1张图片
然后这个实现类里面会有一个初始化方法,会通过这个bean的前置处理器去初始化这个实例。然后通过这个dumpOperate方法来实现这个具体的配置文件的存储。

@PostConstruct
@Override
protected void init() throws Throwable {
    //存储这个配置文件
    dumpOperate(processor, dumpAllProcessor, dumpAllBetaProcessor, dumpAllTagProcessor);
}

在这个dumpOperate方法里面,来实现这个存储的具体实现。在这里面有大量的代码,其主要是一些全量加载和一些增量加载。

protected void dumpOperate(){
    TimerContext.start(dumpFileContext);
    try{
        Runnable dumpAll = () -> dumpAllTaskMgr.addTask(DumpAllTask.TASK_ID, new DumpAllTask());
        Runnable dumpAllBeta = () -> dumpAllTaskMgr.addTask(DumpAllBetaTask.TASK_ID, new DumpAllBetaTask());
        Runnable dumpAllTag = () -> dumpAllTaskMgr.addTask(DumpAllTagTask.TASK_ID, new DumpAllTagTask());
    }
    Runnable clearConfigHistory = () -> {
         LOGGER.warn("clearConfigHistory start");
         if (canExecute()) {
               try {
                   Timestamp startTime = getBeforeStamp(TimeUtils.getCurrentTime(), 24 * getRetentionDays());
                   //用于分页,每次获取磁盘里面的1000行数据
                   int totalCount = persistService.findConfigHistoryCountByTime(startTime);
                   if (totalCount > 0) {
                       int pageSize = 1000;
                       int removeTime = (totalCount + pageSize - 1) / pageSize;
                       while (removeTime > 0) {
                           persistService.removeConfigHistory(startTime, pageSize);
                           removeTime--;
                      }
                  }
              } catch (Throwable e) {     
           }
       }
    };
	//加载配置信息
    try {
        //判断是增量获取还是全量获取,主要是通过这个时间是否大于6小时
        dumpConfigInfo(dumpAllProcessor);
    }           
}

服务端总结

就是每个配置文件在注册之后,都会现存在这个mysql里面,最后会将这个mysql里面的数据存入到磁盘里面,在客户端来拉取这个配置信息的时候,就会直接去读这个本地磁盘里面的数据。

你可能感兴趣的:(nacos,java,nacos)