【微服务架构】SpringCloud Alibaba(八):Nacos 2.1.0 作为配置中心(Nacos的使用)

在这里插入图片描述
个人主页:道友老李
欢迎加入社区:道友老李的学习社区

一、SpringCloud Alibaba

Spring Cloud Alibaba是阿里巴巴提供的一站式微服务解决方案,是Spring Cloud体系中的一个重要分支,它将阿里巴巴在微服务领域的实践经验和开源技术进行了整合,为开发者提供了一系列便捷的工具和组件,用于构建分布式微服务应用。以下是其详细介绍:

1、核心组件

  • Nacos:用于服务注册与发现以及配置管理。它可以帮助微服务实例自动注册到注册中心,并能够动态获取配置信息,使应用程序能够灵活地应对配置的变化,无需重启服务。
  • Sentinel:主要用于流量控制、熔断降级等功能。它可以保护微服务免受高并发、流量异常等情况的影响,确保系统在压力下能够稳定运行,避免因个别服务出现问题而导致整个系统崩溃。
  • RocketMQ:是一款高性能、高可靠的分布式消息队列。它在微服务架构中常用于实现异步消息传递、解耦系统组件之间的依赖关系,从而提高系统的整体性能和可扩展性。
  • Seata:致力于提供分布式事务解决方案,确保在分布式系统中数据的一致性。它通过对事务的协调和管理,使得多个微服务之间在进行数据交互时能够遵循ACID原则。

2、优势

  • 一站式解决方案:涵盖了微服务架构中的多个关键领域,包括服务治理、配置管理、流量控制、分布式事务等,开发者无需再从多个不同的开源项目中进行整合,大大降低了微服务架构的搭建和维护成本。
  • 与Spring Cloud生态的深度集成:基于Spring Cloud的编程模型和规范进行开发,使得熟悉Spring Cloud的开发者能够快速上手并轻松集成到现有的Spring Cloud项目中,充分利用Spring Cloud的各种特性和优势。
  • 阿里巴巴的技术实力和实践经验支持:得益于阿里巴巴在大规模分布式系统开发和运营方面的丰富经验,Spring Cloud Alibaba的组件经过了实际生产环境的考验,具有较高的稳定性、性能和可扩展性,能够应对各种复杂的业务场景和高并发流量。

3、应用场景

  • 电商系统:在电商业务中,存在多个微服务,如商品服务、订单服务、库存服务等。Spring Cloud Alibaba可以通过Nacos进行服务注册与发现,使用Sentinel对各个服务的流量进行控制,利用RocketMQ实现异步消息通知,比如下单成功后异步通知库存服务扣减库存,通过Seata保证分布式事务的一致性,确保订单和库存等数据的准确性。
  • 金融系统:金融领域对数据一致性和系统稳定性要求极高。Spring Cloud Alibaba的Seata可以确保在多个金融业务操作之间的分布式事务一致性,如转账操作涉及到两个不同账户服务之间的资金变动。Nacos可以提供配置管理,方便对金融业务的各种配置参数进行动态调整,Sentinel则可以防止因突发的高并发交易对系统造成冲击。
  • 物联网(IoT)平台:物联网场景中,大量的设备会产生实时数据并上传到云端。Spring Cloud Alibaba可以通过Nacos管理各个物联网服务的注册与发现,使用RocketMQ接收和处理大量的设备数据消息,进行异步处理和分发。Sentinel可以对物联网服务的流量进行控制,防止因设备数据突发增长导致系统过载。

二、Nacos作为配置中心源码分析

1、什么是Naocs配置中心

官方文档: https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config

Nacos 提供用于存储配置和其他元数据的 key/value 存储,为分布式系统中的外部化配置提供服务器端和客户端支持。使用 Spring Cloud Alibaba Nacos Config,您可以在 Nacos Server 集中管理你 Spring Cloud 应用的外部属性配置。

2、Nacos的使用

2.1 给Nacos2.1.0配置数据库

导入数据

image.png

修改内容

image.png

image.png

2.2 版本推荐

https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明

image.png

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执行原理

当我们更改配置的时候,我们看日志会进行容器的刷新

image.png

此时容器中并没有对应的NacosConfigController对应的实例对象。所以我们的定时任务不会执行,我们可以调用一下我们controller对应的方法,然后容器中就有了NacosConfigController对应的实例,有了这个实例,我们的定时任务就会执行,因为这个定时任务是基于后置处理器进行执行的。

image.png

2.8.2 @RefreshScope对象被清理的原因

那为什么刷新容器后NacosConfigController这Bean都没有了呢?

因为在我们更新配置后,我们的容器会将@RefreshScope标注的对象清掉,好我们来分析一下

image.png

image.png

Scope对应的有一个接口

image.png

image.png

image.png

image.png

真正实例创建 除了单例、多例、其他

image.png

我们分析这里从缓存中获取,

image.png

我们知道 单例获取是从单例对象池中,原型是重新构建Bean ,而我们Refresh是从BeanLifecycleWraperCache里面

image.png

image.png

image.png

image.png

也就是从缓存中获取对象,同时这里有个destroy

image.png

总结一下:

1、@RefreshScope中有个@Scope里面值是Refresh,他创建对象是放到对应的缓存中,我们通过GenericScope#get方法从缓存中获取对应Bean对象

2、我们更新数据的时候,会发送一个RefreshEvent事件,容器会监听这个事件,然后将缓存中数据进行删除

3、而我们定时任务是在创建bean的后置处理器中执行的,此时bean都被清理了,所以定时任务也没有了

4、我们再次访问对应的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("监听刷新容器事件");
    }
}