Skip to content

一、概述

Nacos是阿里的一个开源产品,它是针对微服务架构中的服务发现、配置管理、服务治理的综合型解决方案。

官网地址

特性

Nacos主要提供以下四大功能:

  1. 服务发现与服务健康检查

    Nacos使服务更容易注册,并通过DNS或HTTP接口发现其他服务,Nacos还提供服务的实时健康检查,以防止向不健康的主机或服务实例发送请求。

  2. 动态配置管理

    动态配置服务允许您在所有环境中以集中和动态的方式管理所有服务的配置。Nacos消除了在更新配置时重新 部署应用程序,这使配置的更改更加高效和灵活。

  3. 动态DNS服务

    Nacos提供基于DNS 协议的服务发现能力,旨在支持异构语言的服务发现,支持将注册在Nacos上的服务以域名的方式暴露端点,让三方应用方便的查阅及发现。

  4. 服务和元数据管理

    Nacos 能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周 期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略。

1.1 服务发现数据模型

命名空间(Namespace)

用于进行租户粒度的配置隔离,命名空间不仅适用于nacos的配置管理,同样适用于服务发现。Namespace 的常 用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。

服务

提供给客户端的软件功能,通过预定义接口网络访问。

服务名

服务提供的标识,通过该标识可以唯一确定其指代的服务。

实例

提供一个或多个服务的具有可访问网络地址(IP:Port)的进程,启动一个服务,就产生了一个服务实例。

元信息

Nacos数据(如配置和服务)描述信息,如服务版本、权重、容灾策略、负载均衡策略、鉴权配置、各种自定义标 签 (label),从作用范围来看,分为服务级别的元信息、集群的元信息及实例的元信息。

集群

服务实例的集合,服务实例组成一个默认集群, 集群可以被进一步按需求划分,划分的单位可以是虚拟集群,相同 集群下的实例才能相互感知

yml
spring: 
  application: 
    name: transaction‐service 
  cloud: 
    nacos: 
      discovery: 
        server‐addr: 127.0.0.1:7283 # 注册中心地址 
        namespace: a1f8e863‐3117‐48c4‐9dd3‐e9ddc2af90a8 # 开发环境 
        cluster‐name: DEFAULT # 默认集群,可不填写
        
        ## Note: 集群作为实例的隔离,相同集群的实例才能相互感知。
        ## Note: namespace、cluster-name若不填写都将采取默认值,namespace的默认是public命名空间, cluster-name的默认值为DEFAULT集群。

1.2 服务协作流程

(1)在微服务启动时,会向服务发现中心上报自身实例信息,这里ServiceB 包含多个实例。 每个实例包括:IP地址、端口号信息。

(2)微服务会定期从Nacos Server(服务发现中心)获取服务实例列表。

(3)当ServiceA调用ServiceB时,ribbon组件从本地服务实例列表中查找ServiceB的实例,如获取了多个实例如 Instance1、Instance2。这时ribbon会通过用户所配置的负载均衡策略从中选择一个实例。

(4)最终,Feign组件会通过ribbon选取的实例发送http请求。

采用Feign+Ribbon的整合方式,是由Feign完成远程调用的整个流程。而Feign集成了Ribbon,Feign使用Ribbon 完成调用实例的负载均衡。

1.3 环境隔离

Nacos提供了namespace来实现环境隔离功能。

  • nacos中可以有多个namespace
  • namespace下可以有group、service等
  • 不同namespace之间相互隔离,例如不同namespace的服务互相不可见

1.4 权重配置

默认情况下NacosRule是同集群内随机挑选,权重配置来控制访问频率,权重越大则访问频率越高。权重为0则该实例永远不会被访问.可在nacos页面中进行配置。

1.5 Nacos配置管理模型

对于Nacos配置管理,通过Namespace、group、Data ID能够定位到一个配置集。

配置集(Data ID)

在系统中,一个配置文件通常就是一个配置集,一个配置集可以包含了系统的各种配置信息,例如,一个配置集可 能包含了数据源、线程池、日志级别等配置项。每个配置集都可以定义一个有意义的名称,就是配置集的ID即Data ID。

配置项

配置集中包含的一个个配置内容就是配置项。它代表一个具体的可配置的参数与其值域,通常以 key=value 的形 式存在。例如我们常配置系统的日志输出级别(logLevel=INFO|WARN|ERROR) 就是一个配置项。

配置分组(Group)

配置分组是对配置集进行分组,通过一个有意义的字符串(如 Buy 或 Trade )来表示,不同的配置分组下可以有 相同的配置集(Data ID)。当您在 Nacos 上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默 认采用 DEFAULT_GROUP 。配置分组的常见场景:可用于区分不同的项目或应用,例如:学生管理系统的配置集 可以定义一个group为:STUDENT_GROUP。

命名空间(Namespace)

命名空间(namespace)可用于进行不同环境的配置隔离。例如可以隔离开发环境、测试环境和生产环境,因为 它们的配置可能各不相同,或者是隔离不同的用户,不同的开发人员使用同一个nacos管理各自的配置,可通过 namespace隔离。不同的命名空间下,可以存在相同名称的配置分组(Group) 或 配置集。

Namespace:代表不同环境,如开发、测试、生产环境。

Group:代表某项目,如XX医疗项目、XX电商项目

DataId:每个项目下往往有若干个工程,每个配置集(DataId)是一个工程的主配置文件

二、基本使用

2.1 集成nacos配置中心

依赖

groovy
implementation 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config:2.1.2.RELEASE'

配置

yml
spring:
  #引入其他的application-{active}的配置文件,多个使用,号隔开 nacos中也相同
#  profiles:


<NolebasePageProperties />




#    active: db
  application:
    name: ruoyi
  cloud:
    nacos:
      # 配置
      config:
        # 开发空间名称 TODO 后修改为动态的namespace
        namespace: dev #填写上面Nacos中的namespace(就是开发空间边上那段)
        # Nacos地址
        server-addr: localhost:8848
        group: ${spring.application.name}
        # Nacos用户名 开启鉴权时需要补充
#        username: test
#        # Nacos用户密码
#        password: test
        # Nacos文件后缀
        file-extension: properties
        #配置dataid名称 默认为${spring.cloud.nacos.config.prefix}.${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
        prefix: ruoyi-config
        # 拓展配置 优先级高于shared-configs nacos中的配置项名也必须为ruoyi-application.properties
#        extension-configs:
#          - dataId: ruoyi-application.properties
#            group: ${spring.application.name}
#            refresh: true
        shared-configs:
          - dataId: application-db.yml
            group: common

使用

java
@Configuration
@RefreshScope //不加不会自动刷新
public class RuoyiConfig  {

    @Value("${person.basicwork.exchangeid:}")//:后为默认值
    private Integer exchangeid;
}

2.2 集成nacos注册中心

依赖

groovy
implementation 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2.1.2.RELEASE'

配置

yml
spring:
  cloud:
    nacos:
      discovery:
        # 服务注册地址
        server-addr: 127.0.0.1:8848
        group: ${spring.application.name}
        #TODO 修改为动态的namespace
        namespace: dev

使用

通过openfeign 并添加注解 @EnableDiscoveryClient

2.3 项目配置自动推送至nacos

在2.1的操作上拓展

自定义配置注解

java
package com.lly.core.annotation;

import java.lang.annotation.*;
/**
 *  统一配置注解
 *
 * @author cxf
 * date 2019/7/16
 */
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValueDesc {
    /**
     *  描述内容
     */
    String value() default "";

}

配置类注解

所有配置类均实现该接口,扫描配置只扫描该接口子类,不实现不会注册

java
package com.lly.core.config;

/**
 * @Classname IConfig
 * @Description 配置类基类
 * @Date 2021/08/15 20:30
 * @Created by wxp
 */
public interface IConfig {
}

配置类demo

java
package com.lly.ruoyi;

import com.lly.core.annotation.ValueDesc;
import com.lly.core.config.IConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;

/**
 * @Classname RuoyiConfig
 * @Description ruoyi配置类
 * Configuration 注册到spring
 * get/set方法是为了能够使用spring的el注入配置
 * @Date 2021/08/15 21:25
 * @Created by wxp
 */
@Configuration
@RefreshScope //不加不会自动刷新
public class RuoyiConfig implements IConfig {

    @ValueDesc("工资科社工套改表id")//描述信息
    @Value("${person.basicwork.exchangeid:}")//:后为默认值
    private Integer exchangeid;

    public Integer getExchangeid() {
        return exchangeid;
    }

    public void setExchangeid(Integer exchangeid) {
        this.exchangeid = exchangeid;
    }
}

注入配置类中配置

java
@RefreshScope //必须添加,否则不刷新
public class DemoController {

    @Value("#{ruoyiConfig.test}")
    private Integer testValue;
}

向nacos推送实现

java
package com.lly.core.config;

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.cloud.nacos.parser.NacosDataParserHandler;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.common.utils.Md5Utils;
import com.lly.core.annotation.ValueDesc;
import com.lly.core.exception.CommonError;
import com.lly.core.utils.ClassScaner;
import com.lly.core.utils.SpringContextUtils;
import com.lly.datacover.exception.MicroRuntimeException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;

/**
 * @Classname MyConfigPush
 * @Description 扫描本项目配置,将配置信息同步至nacos中。
 *              基于spirng spi配置,需在resources/META-INF/下添加spring.factories文件,将类加入
 * @Date 2021/08/15 19:36
 * @Created by wxp
 */
@Order(Ordered.HIGHEST_PRECEDENCE) //排序 需比nacos自带获取配置先执行
public class ConfigPushLocator implements PropertySourceLocator {

    private static final Logger log = LoggerFactory.getLogger(ConfigPushLocator.class);

    private NacosConfigProperties nacosConfigProperties;

    private NacosConfigManager nacosConfigManager;

    private ConfigService configService;

    /**
     * 构造方法,用于注入所需对象和初始化SpringContextUtils工具类
     */
    public ConfigPushLocator(NacosConfigManager nacosConfigManager, ApplicationContext context) {
        this.nacosConfigManager = nacosConfigManager;
        this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties();
        this.configService = nacosConfigManager.getConfigService();
        SpringContextUtils.init(context);
    }

    @Override
    public PropertySource<?> locate(Environment environment) {
        String group = nacosConfigProperties.getGroup();
        if (StringUtils.isBlank(group)){
            log.error("从配置文件中获取config的group失败");
            return null;
        }
        String dataId = group + "-config";
        String configs = "";
        String beforeConfig = "";
        try {
            // 获取nacos下相关的配置
            beforeConfig = configService.getConfig(dataId, group, nacosConfigProperties.getTimeout());
        } catch (NacosException e) {
//            e.printStackTrace();
            log.error("从nacos中获取配置参数失败,使用默认的配置",e);
            return null;
        }

        try {
            // 获取当前配置
            configs = getConfigs(beforeConfig);
        } catch (Exception e) {
            log.error("获取项目中的配置信息错误",e);
        }
        if (StringUtils.isNotBlank(configs)){

            try {
                // md5 加密验证数据是否相同
                if (Md5Utils.getMD5(beforeConfig.getBytes("UTF-8"))
                        .equals(Md5Utils.getMD5(configs.getBytes("UTF-8"))) ){
                    log.info("配置同步成功");
                    return null;
                }
            }catch (Exception e){
                log.error("转换编码失败",e);
            }


            try {
                //推送
                boolean b = configService.publishConfig(dataId, group, configs);
            } catch (NacosException e) {
//                e.printStackTrace();
                log.error("发布配置到nacos中错误",e);
            }
        }

        log.info("配置同步成功");
        return null;
    }

    private String getConfigs(String beforeConfig) throws Exception {
        List<Class<? extends IConfig>> classes = ClassScaner.scan("com.lly", IConfig.class);
        if (log.isDebugEnabled()) {
            log.debug("查找配置类{}", classes.size());
        }
        if(null==classes||classes.size()==0){
            return "";
        }
        Map<String, Object> dataMap = NacosDataParserHandler.getInstance()
                .parseNacosData(beforeConfig, nacosConfigProperties.getFileExtension());
        StringBuilder builder = new StringBuilder();
        for (Class<? extends IConfig> clazz : classes) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                ValueDesc descAnnotation = field.getAnnotation(ValueDesc.class);
                if (null == descAnnotation) {
                    continue;
                }
                Value valueAnnotation = field.getAnnotation(Value.class);
                if (null == valueAnnotation) {
                    log.warn("配置参数{},未使用Value注解(统一配置参数应同时使用@ValueConfig和@Value注解).", field.getName());
                    continue;
                }
                //切割字符串得到key,value值
                String[] keyAndValue = StringUtils.split(valueAnnotation.value(), ":", 2);
                if(keyAndValue.length!=2){
                    log.error("配置参数未设置默认值,格式[{key}:{defVal}]请检查.key:{}",valueAnnotation.value());
                    throw new MicroRuntimeException(CommonError.unknown, "配置参数未设置默认值,格式[{key}:{defVal}]请检查.key:" + valueAnnotation.value());
                }
                String key = StringUtils.removeStart(keyAndValue[0], "${");
                String value = StringUtils.removeEnd(keyAndValue[1], "}");
                if (dataMap.containsKey(key)){
                    value = (String) dataMap.get(key);
                }
//                获取desc值
                String desc = descAnnotation.value();

                appendConfig(builder,key,value,desc);
            }
        }
        return builder.toString();
    }

    private void appendConfig(StringBuilder builder,String key,String value,String desc){
        builder.append("#").append(desc).append("\n")
                .append(key).append("=").append(value).append("\n");
    }
}

推送配置类注入spi

  • 该启动类是spring的spi启动注册

    在在resources/META-INF/下添加spring.factories文件

    factories
    org.springframework.cloud.bootstrap.BootstrapConfiguration=com.lly.core.config.ConfigPushLocator

    注入类

    java
    package com.lly.core.config;
    
    import com.alibaba.cloud.nacos.NacosConfigManager;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @Classname ConfigConfiguration
     * @Description 注入NacosConfigManager 和 ApplicationContext对象
     * @Date 2021/08/15 20:32
     * @Created by wxp
     */
    @Configuration
    public class ConfigConfiguration {
    
        @Bean
        public ConfigPushLocator buildPushLocator(NacosConfigManager nacosConfigManager,ApplicationContext context){
            return new ConfigPushLocator(nacosConfigManager,context);
        }
    }

相关工具类

springbean工具类

java
package com.lly.core.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.util.ClassUtils;

import java.util.Map;

/**
 * Spring 上下文工具类<br/>
 * 注意:如果在ApplicationContext未准备好的情况下会获取失败的情况,需要考虑先后顺序
 *
 * @author Brack.zhu
 * @date 2019/4/4
 */

public class SpringContextUtils {

    /**
     * Spring应用上下文环境
     */
    private static ApplicationContext applicationContext;

    private SpringContextUtils(){

    }

    /**
     * 外部设置由初始化时
     * @param context
     */
    public static void init(ApplicationContext context){
        applicationContext=context;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static Environment getEnvironment() {
        return applicationContext.getEnvironment();
    }

    /**
     * 获取对象
     * 例如: getBean("userService")//注意: 类名首字母一定要小写!
     */
    public static <T> T getBean(String beanId) throws BeansException {
        return (T) applicationContext.getBean(beanId);
    }

    /**
     * 获取对象
     */
    public static <T> T getBean(Class<T> clazz) throws BeansException {
        return applicationContext.getBean(clazz);
    }

    /**
     * 获取对象
     */
    public static <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException {
        return applicationContext.getBeansOfType(type);
    }

    /**
     * 获取Spring容器对象名称集合
     * @return
     */
    public static String[] getBeanNames(){
        return applicationContext.getBeanDefinitionNames();
    }

    /**
     * 判断指定bean名称Bean容器是否包含
     *
     * @param beanName
     * @return
     */
    public static boolean containsBean(String beanName) {
        if (null == applicationContext) {
            //未准备好
            return false;
        }
        return applicationContext.containsBean(beanName);
    }

    /**
     * 判断指定Class默认名称Bean容器是否包含
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> boolean containsBean(Class<T> clazz) {
        String beanDefName = ClassUtils.getShortNameAsProperty(clazz);
        return containsBean(beanDefName);
    }

    /**
     * 获取Environment参数
     * @param key
     * @return
     */
    public static String getEnvProperty(String key) {
        return getEnvironment().getProperty(key);
    }


}

类路径扫描工具类

java
package com.lly.core.utils;

import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.SystemPropertyUtils;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;

/**
 * 类扫描器
 * @author sunwen
 * @since 2019/5/17
 */
public class ClassScaner {

    public static <T> List<Class<? extends T>> scan(String basePackage, Class<T> clazz) throws Exception{
        ApplicationContext context = SpringContextUtils.getApplicationContext();
        if(context == null){
            throw new Exception("spring context has not inited");
        }
        return scan(context, basePackage, clazz);
    }

    /**
     * 扫描指定包下的clazz所有子类(排除抽象类和接口)
     * @author sunwen
     * @since 2020/8/19
     */
    public static <T> List<Class<? extends T>> scan(ResourceLoader resourceLoader, String basePackage, Class<T> clazz)
            throws IOException, ClassNotFoundException {
        List<Class<? extends T>> list = new ArrayList<>();
        ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        MetadataReaderFactory metaReader = new CachingMetadataReaderFactory(resourceLoader);
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                + org.springframework.util.ClassUtils.convertClassNameToResourcePath(
                        SystemPropertyUtils.resolvePlaceholders(basePackage))
                + "/**/*.class";
        Resource[] resources = resolver.getResources(packageSearchPath);
        for (Resource r : resources) {
            MetadataReader reader = metaReader.getMetadataReader(r);
            ClassMetadata classMetadata = reader.getClassMetadata();
            if(classMetadata.isInterface() || classMetadata.isAbstract()){
                continue;
            }
            Class c = Class.forName(classMetadata.getClassName());
            if (clazz.isAssignableFrom(c) && !clazz.equals(c)){
                list.add(c);
            }
        }
        return list;
    }

    /**
     * 扫描指定包下的clazz注解的类(排除抽象类和接口)
     * @author sunwen
     * @since 2020/8/19
     */
    public static List<ClassMetadata> scanAnnotation(ResourceLoader resourceLoader, String basePackage, Class<? extends Annotation> clazz)
            throws IOException {
        List<ClassMetadata> list = new ArrayList<>();
        ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        MetadataReaderFactory metaReader = new CachingMetadataReaderFactory(resourceLoader);
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                + org.springframework.util.ClassUtils.convertClassNameToResourcePath(
                SystemPropertyUtils.resolvePlaceholders(basePackage))
                + "/**/*.class";
        Resource[] resources = resolver.getResources(packageSearchPath);
        String annotationName = clazz.getName();
        for (Resource r : resources) {
            MetadataReader reader = metaReader.getMetadataReader(r);
            ClassMetadata classMetadata = reader.getClassMetadata();
            if(classMetadata.isInterface() || classMetadata.isAbstract()){
                continue;
            }
            AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
            if(annotationMetadata.hasAnnotation(annotationName) || annotationMetadata.hasMetaAnnotation(annotationName)){
                list.add(classMetadata);
            }
        }
        return list;
    }
}

三、核心知识

3.1 负载均衡

负载均衡就是将用户请求(流量)通过一定的策略,分摊在多个服务实例上执行,它是系统处理高并发、缓解网络 压力和进行服务端扩容的重要手段之一。它分为服务端负载均衡和客户端负载均衡。

Ribbon是一个客户端负载均衡器,它的责任是从一组实例列表中挑选合适的实例,如何挑选?取决于负载均衡策略

RoundRobinRule(默认):轮询,即按一定的顺序轮换获取实例的地址。

RandomRule:随机,即以随机的方式获取实例的地址。

AvailabilityFilteringRule: 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,以及并发的连接数量 超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问;

WeightedResponseTimeRule: 根据平均响应时间计算所有服务的权重,响应时间越快,服务权重越大,被选中的 机率越高; 刚启动时,如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够时,会切换到 WeightedResponseTimeRule

RetryRule: 先按照RoundRobinRule的策略获取服务,如果获取服务失败,则在指定时间内会进行重试,获取可用 的服务;

BestAvailableRule: 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的 服务;

ZoneAvoidanceRule: 默认规则,复合判断server所在区域的性能和server的可用性选择服务器;

默认的ZoneAvoidanceRule并不能实现根据同集群优先来实现负载均衡。

因此Nacos中提供了一个NacosRule的实现,可以优先从同集群中挑选实例。

1)给order-service配置集群信息

在主类上添加**@EnableDiscoveryClient**注解

xml
<!--nacos客户端-->
<dependency> 
    <groupId>com.alibaba.cloud</groupId> 
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency

修改order-service的application.yml文件,添加集群配置:

sh
spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ # 集群名称

2)修改负载均衡规则

修改order-service的application.yml文件,修改负载均衡规则:

yaml
userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则

3.2 openApi管理配置

通过 curl工具来测试nacos的open api:

本教程下载curl的windows版本:curl-7.66.0_2-win64-mingw,下载地址

sh
# 发布配置
curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs? dataId=nacos.cfg.dataId&group=test&content=HelloWorld"
# 获取配置
curl -X GET "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test"

3.3 Nacos的服务注册表结构

Nacos采用了数据的分级存储模型,最外层是Namespace,用来隔离环境。然后是Group,用来对服务分组。接下来就是服务(Service)了,一个服务包含多个实例,但是可能处于不同机房,因此Service下有多个集群(Cluster),Cluster下是不同的实例(Instance)。

对应到Java代码中,Nacos采用了一个多层的Map来表示。结构为Map<String, Map<String, Service>>,其中最外层Map的key就是namespaceId,值是一个Map。内层Map的key是group拼接serviceName,值是Service对象。Service对象内部又是一个Map,key是集群名称,值是Cluster对象。而Cluster对象内部维护了Instance的集合。

3.4 Nacos支撑十万服务注册压力

Nacos内部接收到注册的请求时,不会立即写数据,而是将服务注册的任务放入一个阻塞队列就立即响应给客户端。然后利用线程池读取阻塞队列中的任务,异步来完成实例更新,从而提高并发写能力。

3.5 避免并发读写冲突问题

Nacos在更新实例列表时,会采用CopyOnWrite技术,首先将旧的实例列表拷贝一份,然后更新拷贝的实例列表,再用更新后的实例列表来覆盖旧的实例列表。

这样在更新的过程中,就不会对读实例列表的请求产生影响,也不会出现脏读问题了。

3.6 Nacos与Eureka的区别

  • 接口方式:Nacos与Eureka都对外暴露了Rest风格的API接口,用来实现服务注册、发现等功能
  • 实例类型:Nacos的实例有永久和临时实例之分;而Eureka只支持临时实例.临时实例心跳不正常会被剔除,非临时实例则不会被剔除
  • 健康检测:Nacos对临时实例采用心跳模式检测,对永久实例采用主动请求来检测;Eureka只支持心跳模式
  • 服务发现:Nacos支持定时拉取和订阅推送两种模式;Eureka只支持定时拉取模式
  • 集群高可用:Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

四、安装

4.1 单机运行

需要存在java环境

properties
# 修改application.properties文件,添加mysql数据库配置
spring.datasource.platform=mysql

db.num=1

db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=123

# 修改端口号
#server.port=8848
shell
# 解压后可直接启动   可修改配置文件选择持久化数据的方式。选择mysql需执行对应脚本
sh startup.sh -m standalone

//  http://127.0.0.1:8848/nacos   用户名:nacos,默认密码:nacos

4.2 集群配置

conf目录,复制配置文件cluster.conf.example,重命名为cluster.conf并添加集群节点信息 ip+端口

启动:startup.sh