目录
1、什么是Naocs配置中心
官方文档: Nacos config · alibaba/spring-cloud-alibaba Wiki · GitHub
Nacos 提供用于存储配置和其他元数据的 key/value 存储,为分布式系统中的外部化配置提供服务器端和客户端支持。使用 Spring Cloud Alibaba Nacos Config,可以在 Nacos Server 集中管理 Spring Cloud 应用的外部属性配置。
2、Nacos的使用
2-1、给Nacos2.1.0配置数据库
导入数据
修改内容
2-2、版本推荐
2-3、父工程指定版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring-cloud-alibaba.version>2.2.9.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2-4、子工程引入依赖
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 单元测试类 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2-5、利用接口对配置进行操作
public class ConfigListenerTest {
private static String serverAddr = "localhost";
private static String dataId = "nacos-demo.yaml";
private static String group = "DEFAULT_GROUP";
private static ConfigService configService;
@Test
public void testListener() throws NacosException, InterruptedException {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
//获取配置服务
configService = NacosFactory.createConfigService(properties);;
//获取配置
String content = configService.getConfig(dataId, group, 5000);
System.out.println(content);
//注册监听器
CountDownLatch countDownLatch = new CountDownLatch(5);
configService.addListener(dataId, group, new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("配置发生变化:" + configInfo);
countDownLatch.countDown();
}
});
countDownLatch.await();
}
@Test
public void publishConfig() throws NacosException {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
//获取配置服务
configService = NacosFactory.createConfigService(properties);
configService.publishConfig(dataId,group,"age: 30", ConfigType.PROPERTIES.getType());
}
@Test
public void removeConfig() throws NacosException, InterruptedException {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
//获取配置服务
configService = NacosFactory.createConfigService(properties);
boolean isRemoveOk = configService.removeConfig(dataId, group);
System.out.println(isRemoveOk);
}
}
2-6、和Springboot整合
@RefreshScope
@RestController
public class NacosConfigController {
@Value("${name}")
private String name;
@RequestMapping("/getName")
public String getName(HttpServletRequest httpRequest){
return name;
}
}
2-7、里面放置定时任务
启动
@EnableScheduling @SpringBootApplication public class NacosConfigApplication { public static void main(String[] args) { SpringApplication.run(NacosConfigApplication.class); } }
增加定时任务
@RestController public class NacosConfigController { @Value("${name}") private String name; @RequestMapping("/getName") public String getName(HttpServletRequest httpRequest){ return name; } // 定时任务每5秒执行一次 @Scheduled(cron = "*/2 * * * * ?") public void execute(){ System.out.println("获取姓名:" + name); } }
发现问题:如果更改配置,则对应的定时任务就失败
2-8、分析失败原因
2-8-1、Schedule执行原理
当我们更改配置的时候,我们看日志会进行容器的刷新
此时容器中并没有对应的NacosConfigController对应的实例对象。所以定时任务不会执行,可以调用一下controller对应的方法,然后容器中就有NacosConfigController对应的实例,有了这个实例,定时任务就会执行,因为这个定时任务是基于后置处理器进行执行的。
2-8-2、@RefreshScope对象被清理的原因
那为什么刷新容器后NacosConfigController这Bean都没有了呢?
因为在更新配置后,容器会将@RefreshScope标注的对象清掉
Scope对应的有一个接口
真正实例创建 除了单例、多例、其他
分析这里从缓存中获取
单例获取是从单例对象池中,原型是重新构建Bean ,而Refresh是从BeanLifecycleWraperCache里面
也就是从缓存中获取对象,同时这里有个destroy
总结一下:
- @RefreshScope中有个@Scope里面值是Refresh,它创建对象是放到对应的缓存中,通过GenericScope#get方法从缓存中获取对应Bean对象;
- 在更新数据的时候,会发送一个RefreshEvent事件,容器会监听这个事件,然后将缓存中数据进行删除;
- 而定时任务是在创建bean的后置处理器中执行的,此时bean都被清理了,所以定时任务也没有了;
- 再次访问对应的NacosConfigController的时候,就会创建对应的对象放到缓存,此时定时任务也就执行了;
解决问题方案:
缓存删除是监听RefreshEvent事件而处理的,现在也可以监听事件进行处理,监听事件,如果事件发生,它回调用对应监听器,然后就会实例化,这样定时任务也可以执行。
@Slf4j @RefreshScope @RestController public class NacosConfigController implements ApplicationListener<RefreshScopeRefreshedEvent> { @Value("${name}") private String name; @RequestMapping("/getName") public String getName(HttpServletRequest httpRequest){ return name; } // 定时任务每5秒执行一次 @Scheduled(cron = "*/2 * * * * ?") public void execute(){ System.out.println("获取姓名:" + name); } @Override public void onApplicationEvent(RefreshScopeRefreshedEvent event) { log.info("监听刷新容器事件"); } }
3、源码分析
3-1、服务启动加载bootstrap.propertis
准备环境加载bootstrap.propertis,这里它会发送事件进行监听,直接进入 #load,在load打上断点
总结:根据堆栈信息来发现,它是在准备环境的时候发送一个事件,ConfigFileApplicationListener监听事件,最后调用PropertiesPropertySourceLoader 对资源进行加载
3-2、客户端拉取远程配置进行合并
这里最终会调用用到NacosPropertySourceLocator# ,在locate上打上断点可以看一下堆栈信息
看一下locate方法
记载配置文件后最终会用composite进行合并,那它们无论加载共享配置、扩展配置和当前应用配置最终会调到NacosPropertySourceLocator#loadNacosDatalfPresent
看一下它的配置加载
key1:从本地获取配置
key2:远程获取配置
总结:这里要注意Spring的扩展点之一:在Bean构建之前加载一些数据,比如配置属性,就可以用这个扩展点,这里加载配置中心内容,这个内容用于后面bean对象创建。
例如:
进行配置
3-3、服务端处理配置拉取
客户端请求为ConfigQueryRequest,则从服务端进行搜索找到对应处理类
下面对应的方法比较长,可以看一下关键的点
总结:分析发送请求是从缓存文件中获取到的,这里带出两个问题:
- 因为从缓存中获取的,那直接修改数据库应该是不起作用的;
- 缓存一定是从数据库中获取的,那什么时候设置进去的呢?
3-4、服务启动进行数据库数据加载
- 服务端启动时就会依赖DumpService的init方法,从数据库中load 配置存储在本地磁盘上,并将一些重要的元信息例如MD5值缓存在内存中。服务端会根据心跳文件中保存的最后一次心跳时间,来判断到底是从数据库dump全量配置数据还是部分增量配置数据(如果机器上次心跳间隔是6h以内的话)。
- 全量dump当然先清空磁盘缓存,然后根据主键ID每次捞取一千条配置刷进磁盘和内存。增量dump就是捞取最近六小时的新增配置(包括更新的和删除的),先按照这批数据刷新一遍内存和文件,再根据内存里所有的数据全量去比对一遍数据库,如果有改变的再同步一次,相比于全量dump的话会减少一定的数据库IO和磁盘I0次数。
构建bean时候一定初始化@PostConstruct 对应的方法
判断全量和增量获取数据
全量拉取
总结:这里的md5值的我们学习,md5算法获取对应文件的值,如果这个值变化说明文件发生了变化,利用这中方式可以通过文件md5值来判断其他是否有变化
3-5、加载数据发生变更,发送事件
如果队列写满则如下操作:但是上面是服务启动暂时没有新的服务进来,所以这里subscriber是空的也就无法调用。
如果正常情况下应该是放到队列里面,那就应该有取的地方,全文搜索对应的queue
哪里调用到这里呢?
从这里可以知道DefaultPublisher是一个线程,所有会调用run方法
总结:应该学会发布监听事件
3-6、监听配置变更
全文搜索ApplicationReadyEvent,查看NacosContextRefresher
发送RefreshEvent事件后,就是对@RefreshScope标志的实例进行删除,这里可以参考2.7分析失败原因
key1:销毁对应实例
key2:发送事件RefreshScopeRefreshedEvent 这也是通过监听这个事件,来实例化对应的实例的。
3-7、服务端端更改配置
配置中心发布配置,一定是调用SpringMVC中的Controller方法 ,进行全文搜索,通过名称进行分析应该是ConfigController。
key1: 更新数据库
key2:发布事件,进行客户端通知配置变更,以及集群同步。
全文搜索ConfigDataChangeEvent
AsyncRpcTask是一个任务,并向里面传递了rpcQuene的一个任务队列,看一下它是怎样处理的。
key1:看一下数据持久化, key2中集群数据同步就是发送一个rpc请求
我们重点key1:
上面解释过这里是异步处理一定有个地方处理它
发布LocalDataChangeEvent事件
全文搜索对应的事件
查看任务RpcPushTask对应的方法:
向客户端发送请求,进行配置变更的通知
3-8、客户端处理事件
发送请求的是ConfigChangeNotifyRequest,到客户端全文搜索一下,找对应的处理
全文搜索listenExecutebell找对应的处理位置
3-9、客户端定时拉取配置
3.10 于Nacos1.x长轮询做对比