一,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类。
然后这个实现类里面会有一个初始化方法,会通过这个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里面的数据存入到磁盘里面,在客户端来拉取这个配置信息的时候,就会直接去读这个本地磁盘里面的数据。