组件整合之配置和注册中心Nacos

Nacos简介

Nacos是阿里巴巴开源的项目,核心定位是“一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台”。

项目地址:https://nacos.io/zh-cn/

Nacos核心提供两个功能:服务注册与发现,动态配置管理。

服务注册与管理

Nacos提供基于DNS和基于RPC的服务发现,既能被用来支持https/http的服务注册与发现,也支持RPC如dubbo的服务注册与发现。

与Dubbo使用的zookeeper相比而言,两者差异还是比较大的,zookeeper是一种分布式的协调服务,它天生是作为分布式数据一致性场景下的解决方案,所以zookeeper是CP的,它牺牲了可用性来保证一致性,在极端情况下(master选举期间)服务会对外停止,对于服务可用性要求比较高的系统是难以接受的。Nacos是一种去中心化的架构,属于CAP理论里的AP架构,支持最终一致性,在分布式服务发现与注册场景下具有很不错的性能。目前dubbo官方也支持使用Nacos代替zookeeper。

动态配置服务

动态修改配置并实时生效对于服务端的同学而已并不陌生,这种服务能够让我们的服务拥有更多的灵活性,不需要重启服务即可做到配置实时生效,非常适合于“配置优先”的服务开发。

快速入门

前提:安装Nacos server

pom.xml依赖

<dependency>
  <groupId>com.alibaba.nacos</groupId>
  <artifactId>nacos-spring-context</artifactId>
  <version>1.1.1</version>
</dependency>

通过注解引入

启动配置管理

1、在nacos管理后台上创建example.properties(这个就是我们的配置文件)

2、在resources下创建nacos.properties

nacos.serverAddr=127.0.0.1:8848
nacos.dataId=example.properties

3、创建NacosClientConfig

@Configuration
@PropertySource("classpath:nacos.properties")
@EnableNacosConfig(globalProperties = @NacosProperties(serverAddr = "${nacos.serverAddr}"))
@NacosPropertySource(dataId = "${nacos.dataId}", autoRefreshed = true)
public class NacosClientConfig{

    @Value("${name}")
    private String name;

    /**
     * @PostConstruct修饰的方法会在构造函数之后,init()之前执行,所以这里的name是有值的了
     */
    @PostConstruct
    public void print(){
        System.out.println("name:" + name);
    }
}

输出结果:lisi

查看@EnableNacosConfig注解,导入了NacosConfigBeanDefinitionRegistrar。

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({NacosConfigBeanDefinitionRegistrar.class})
public @interface EnableNacosConfig {

}

4、监听配置文件变化

@NacosConfigListener(dataId = "${nacos.dataId}", type = ConfigType.PROPERTIES)
public void changeListener(String value) {
	System.out.println("value:" + value);
}

注意:这里value是整个配置文件的内容。

一般是结合Spring使用,我们可以集合@NacosValue注解实现配置自动刷新功能。

启动服务发现

1、通过添加 @EnableNacosDiscovery 注解开启 Nacos Spring 的服务发现功能

@Configuration
@EnableNacosDiscovery(globalProperties = @NacosProperties(serverAddr = "127.0.0.1:8848"))
public class NacosConfiguration {

}

2、通过nacos提供的open api注册服务

POST http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=example&ip=127.0.0.1&port=8080

这里在服务管理-服务列表就可以找到该服务。

原生API引入

启动配置管理

nacos.properties配置

nacos.serverAddr=127.0.0.1:8848
nacos.dataId=example.properties
nacos.group=DEFAULT_GROUP

手动读取nacos的配置信息

import com.alibaba.nacos.api.config.ConfigFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.util.Properties;
import java.util.concurrent.Executor;

@Configuration
public class NacosConfig {

    @Autowired
    private ConfigurableApplicationContext context;

    @Autowired
    private Environment environment;

    @PostConstruct
    public void readConfigContent() throws Exception {
        Properties clientConfig = this.getClientConfig();
        String groupId = clientConfig.getProperty("nacos.group", "DEFAULT_GROUP");
        String dataId = clientConfig.getProperty("nacos.dataId");
        //创建客户端的连接配置
        Properties serviceConfig = new Properties();
        serviceConfig.put("serverAddr", clientConfig.getProperty("nacos.serverAddr"));
        serviceConfig.put("dataId", dataId);
        serviceConfig.put("groupId",groupId);
        ConfigService configService = ConfigFactory.createConfigService(serviceConfig);
        configService.addListener(dataId, groupId, new Listener() {
            @Override
            public Executor getExecutor() {
                return null;
            }
            @Override
            public void receiveConfigInfo(String s) {
                System.out.println("change value :" + s);
                try {
                    replacePropertySource(s);
                }catch (Exception e){
                    throw new RuntimeException(e);
                }
            }
        });
        //获取远程nacos的配置
        String content = configService.getConfig(dataId, groupId, 3000);
        this.replacePropertySource(content);
    }

    private void replacePropertySource(String content) throws Exception {
        //将远程nacos的配置解析成Properties
        Properties pro = this.getConfigProperties(content);
        MutablePropertySources propertySources = context.getEnvironment().getPropertySources();
        //将配置加入到项目环境中
        PropertiesPropertySource propertySource = new PropertiesPropertySource("nacos", pro);
        if(null == propertySources.get("nacos")){
            propertySources.addLast(propertySource);
        }else{
            propertySources.remove("nacos");
            propertySources.addLast(propertySource);
        }
        //测试属性读取
        System.out.println(environment.getProperty("name"));
    }

    private Properties getConfigProperties(String content) throws Exception {
        String path = this.getClass().getClassLoader().getResource("").getPath();
        File file = new File(path + "/nacos-temp.properties");
        if (file.exists()) {
            file.delete();
        } else {
            file.createNewFile();
        }
        FileWriter writer = new FileWriter(file, false);
        writer.write(content);
        writer.close();
        Properties pro = new Properties();
        pro.load(new FileInputStream(file));
        file.delete();
        return pro;
    }

    private Properties getClientConfig() throws Exception {
        Properties configProperty = new Properties();
        String filePath = this.getClass().getClassLoader().getResource("nacos.properties").getPath();
        configProperty.load(new FileInputStream(new File(filePath)));
        return configProperty;
    }
}

启动服务发现

import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service;

/**
 * 启动监听器
 *
 */
@Service
public class StartupListener implements ApplicationListener<ContextRefreshedEvent> {


    @Override
    public void onApplicationEvent(ContextRefreshedEvent evt) {
        //在web 项目中(spring mvc),系统会存在两个容器,一个是root application context ,
        //另一个就是我们自己的 projectName-servlet context(作为root application context的子容器)
        //只在root application context初始化完成后调用逻辑代码,其他的容器的初始化完成,则不做任何处理
        //root application context 没有parent,他就是老大.
        if(evt.getApplicationContext().getParent() == null){
            //需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
            String serverIp = "127.0.0.1";
            int serverPort = 8848;
            String serviceName = "example";
            String serverAddr = serverIp + ":" + serverPort;

            Instance instance = new Instance();
            instance.setIp(serverIp);//IP
            instance.setPort(serverPort);//端口
            instance.setServiceName(serviceName);//服务名
            instance.setEnabled(true);//true: 上线 false: 下线
            instance.setWeight(1.0);//权重
            instance.addMetadata(serviceName, "true");//元数据
            try {
                NamingService namingService = NamingFactory.createNamingService(serverAddr);
                namingService.registerInstance(serviceName, instance);
            } catch (Exception e) {
                System.out.println("服务注册失败");
                e.printStackTrace();
            }
        }
    }
}

Nacos注解或原生API

nacos注解

(1)@EnableNacosConfig 注解启用 Nacos Spring 的配置管理服务

(2)@NacosValue 注解设置属性值

(3)@EnableNacosDiscovery 注解开启 Nacos Spring 的服务发现功能

(4)@NacosInjected 注入 Nacos 的 NamingService

(5)@NacosPropertySource 加载 dataId 为 example 的配置源,并开启自动更新:

@NacosPropertySource(dataId = "example", autoRefreshed = true)

(6)@EnableNacos是一个模块驱动的注解,它支持 Nacos Spring 的所有功能,包括服务发现和配置管理。它等于 @EnableNacosDiscovery 加上 @EnableNacosConfig,可以单独配置并在不同场景中使用。(@EnableNacos=@EnableNacosDiscovery+@EnableNacosConfig)

(7)@NacosConfigListener 的类型转换包括内置和自定义实现。 默认情况下,内置类型转换基于 Spring DefaultFormattingConversionService。

(8)@NacosProperties 是全局和自定义 Nacos 属性的统一注解。 它充当Java Properties 和 NacosFactory 类之间的中介。NacosFactory 负责创建 ConfigService 或 NamingService 实例。@NacosProperties 的属性完全支持占位符,它的源是Spring Environment 抽象中的各种 PropertySource,通常是Java System Properties 和操作系统环境变量。

原生API

配置管理

创建ConfigService,可以通过 NacosFactory.createConfigService() 或 ConfigFactory.createConfigService() 来创建,后者是前者的底层实现方式,这两种方式都包含如下两个方法:

  • createConfigService(serverAddr)
  • createConfigService(properties)

创建示例:

// 方式一
String serverAddr = "127.0.0.1:8848";
ConfigService configService = ConfigFactory.createConfigService(serverAddr);

// 方式二
ConfigService configService = ConfigFactory.createConfigService(properties)
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);

1、发布配置

boolean publishConfig(String dataId, String group, String content) throws NacosException

支持程序自动发布Nacos配置,创建和修改配置使用同一个方法,配置不存在则创建;配置已存在则更新。

2、读取配置

String getConfig(String dataId, String group, long timeoutMs) throws NacosException

timeoutMs指读取配置超时时间,官网推荐设置为3000ms。

3、移除配置

boolean removeConfig(String dataId, String group) throws NacosException

支持程序自动发布Nacos配置,配置不存在时会直接返回成功,移除配置后,本地的缓存文件也会被删除。

4、添加监听

void addListener(String dataId, String group, Listener listener) throws NacosException

支持动态监听配置的变化。

5、移除监听

void removeListener(String dataId, String group, Listener listener)

移除监听后,配置的变化不会再监听。

服务管理

创建NamingService,可以通过 NacosFactory.createNamingService() 或 NamingFactory.createNamingService() 来创建,后者是前者的底层实现方式,这两种方式都包含如下两个方法:

  • createNamingService(serverAddr)
  • createNamingService(properties)

创建示例:

// 方式一
String serverAddr = "127.0.0.1:8848";
NamingService namingService = NamingFactory.createNamingService(serverAddr);

// 方式二
NamingService namingService = NamingFactory.createNamingService(properties)
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);

1、注册服务实例

void registerInstance(多个参数)

方式一:
String serverIp = "127.0.0.1";
int serverPort = 8848;
String serverAddr = serverIp + ":" + serverPort;
String serviceName = "nacos-sdk-java-discovery";
NamingService namingService = NamingFactory.createNamingService(serverAddr);
namingService.registerInstance(serviceName, serverIp, serverPort);

方式二:
Instance instance = new Instance();
instance.setIp(serverIp);//IP
instance.setPort(serverPort);//端口
instance.setServiceName(serviceName);//服务名
instance.setEnabled(true);//true: 上线 false: 下线
instance.setHealthy(healthy);//健康状态
instance.setWeight(1.0);//权重
instance.addMetadata("nacos-sdk-java-discovery", "true");//元数据
NamingService namingService = NamingFactory.createNamingService(serverAddr);
namingService.registerInstance(serviceName, instance);

2、删除服务实例

void deregisterInstance(多个参数)

3、获取所有服务实例

List<Instance> getAllInstances(多个参数)

4、获取所有健康或不健康的服务实例

List<Instance> selectInstances(多个参数)

5、随机获取一个健康实例(根据负载均衡算法)

Instance selectOneHealthyInstance(多个参数)

6、添加服务实例监听

void subscribe(多个参数)

7、移除服务实例监听

void unsubscribe(多个参数)

8、分页获取所有服务实例

ListView<String> getServicesOfServer(多个参数)

9、获取所有监听的服务实例

List<ServiceInfo> getSubscribeServices()

 

posted @ 2022-01-04 08:56  残城碎梦  阅读(620)  评论(0编辑  收藏  举报