nacos的本地配置与启动步骤及NoDataSourceset问题解决

Posted mmm.c

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nacos的本地配置与启动步骤及NoDataSourceset问题解决相关的知识,希望对你有一定的参考价值。

文章前提是本地机器已经安装好了mysql,配置好了mysql与Java环境变量。

首先在网络上找到一个nacos-server包。官网下载传送门

https://github.com/alibaba/nacos/releases

这里说下如果手里有的是个.gz而不是zip文件该怎么办,下好zip文件的可以忽略这一步:

解压开后是个.gz的文件,可以用windows自带的WindowsPowerShell工具使用Linux命令解压,我是win10系统直接搜索就可以看到。

打开上述工具后使用cd命令,进入到nacos.gz文件所属的文件夹下,使用tar -zxvf命令解压该文件,解压后便可在相同位置下看到一个名为nacos的文件夹。

接下来是通用的步骤了:

 

 文件夹目录如上图,其中bin目录中使我们可以启动与停止nacos服务的开关,config文件夹中是我们要配置的有关数据库信息的文件夹。

首先进行数据库相关配置

数据库信息的配置分为2步:

第一步,我们要将nacos-mysql.sql中的数据脚本在数据库中执行一下,也就是在本地的mysql数据库中新建一个专门为nacos服务的数据库。这里要强调一下:

1)我用的nacos版本是2.0.4版本,对应的mysql版本最好是5.7版本,最多到mysql8,版本差距过大再执行启动时候也会报错。

第二步,配置config目录中的application.properties文件,将刚才执行好的数据库信息填入

用户名密码根据自己的情况来填写即可,不过这里要强调的是: 

2)mysql数据库的名称要和application.properties文件中配置的数据库信息一致,也就是上面的nacos-config这个名称。否则会报 No DataSource set 这个让人十分头疼与常见的错误,遇到这个问题可以自行检查下端口后面这个数据库名称与自己库中的名称是否一致

然后进行启动项的配置

这里就是配置下bin目录中startup.cmd文件的启动方式,由于我这里说的是本机本地启动,也就是单机启动,所以在mode这里要改为standalone,默认cluster是集群启动,我们不改也会启动不起来。

更改后启动时候可以注意下这里是否更改成功:

 一切保存完毕后,最后双击startup.cmd便可启动我们的nacos了:

 如上图所示,便是启动成功了。

启动过程中最常见的就是NoDataSourceset报错,意思是没有设置数据库,解决办法上面也讲述了,只要自己定义的nacos数据库名称与配置文件中的名称一致,便没问题,若果还不行这里有两个思路:

1)本地连接一下数据库看看账号密码是否正确,或者数据库当前是否可用,有时候数据库的连接过多也会导致nacos连接失败。

2)增加连接时间,将这里增大,可能是数据库性能问题影响到了,可以给一点耐心。

 最后,可以通过http://localhost:8848/nacos/index.html#/login来登录我们的后台管理,账号密码也都是nacos。

到此nacos的本地从无到有就实现了,不过补充一点,我一开始用的nacos版本是2.0.4的,mysql是5.7。不知道是版本问题还是nacos.server包找的不对,使用自带的数据源启动可以正常启动,但是在引入本地mysql当做数据源后,就死活起不来报错no database set,将nacos换了2.1.1版本才可以正常启动,所以2.0.4这个版本的nacos可能有点问题。

先记录到这,如有问题欢迎指出。

4Nacos 配置中心源码解析之 服务端启动

上一篇文章中我们使用 -Dnacos.standalone=true本地启动了 Nacos 服务器,并且可以在 http://localhost:8848/nacos 通过 nacos/nacos 用户名密码就可以访问 nacos 控制页面。下面我们就来大体看一下 Nacos 在启动的时候干了哪些核心的事。

1、Nacos 认证服务

Nacos 中的 nacos-console 项目依赖了与配置中心相关的以下几个模块:

  • nacos-config:配置中心项目模块
  • nacos-plugin-default-impl:Nacos 插件模块:主要是用户认证授权相关操作,这里使用的 Spring Security 来做安全认证。相关的配置类为:NacosAuthConfig

1.1 内存数据库加载数据

JVM 启动参数添加 -Dnacos.standalone=true,这个时候在 DynamicDataSource#getDataSource 就会初始化 LocalDataSourceServiceImpl

DynamicDataSource#getDataSource()

    public synchronized DataSourceService getDataSource() 
        try 
            
            // Embedded storage is used by default in stand-alone mode
            // In cluster mode, external databases are used by default
            
            if (PropertyUtil.isEmbeddedStorage()) 
                if (localDataSourceService == null) 
                    localDataSourceService = new LocalDataSourceServiceImpl();
                    localDataSourceService.init();
                
                return localDataSourceService;
             else 
                if (basicDataSourceService == null) 
                    basicDataSourceService = new ExternalDataSourceServiceImpl();
                    basicDataSourceService.init();
                
                return basicDataSourceService;
            
         catch (Exception e) 
            throw new RuntimeException(e);
        
    

LocalDataSourceServiceImpl#initialize 会使用 derby 这个内存数据库来保存数据。保存数据地址为:$user.home/nacos/data/derby-data

然后调用 LocalDataSourceServiceImpl#reload 加载 console/resources/META-INF/schema.sql 里面的数据到内存数据库当中。其中就包括我们登陆使用的 nacos/nacos 用户名密码

1.2 nacos plugin 暴露用户认证

plugin-default-impl 模块中的com.alibaba.nacos.plugin.auth.impl.controller 包里面包含了以下几个 Http 服务:

  • PermissionController:权限操作服务:获取所有权限、添加角色权限、删除角色权限等接口
  • RoleController:角色操作服务:获取所有角色、查询角色、添加角色、删除角色等接口
  • UserController:用户操作服务:包括创建用户、删除用户、修改用户、获取所有用户、用户登陆等接口

Nacos 使用的 Spring Seurity 做的权限管理,默认支持 Nacos 类型权限也就是数据库管理权限以及 Ldap 权限管理。默认使用数据库 RBAC 权限管理。 Spring Security Web 配置类为:NacosAuthConfig。

1.3 用户登陆时序图


以上就是 Nacos 登陆认证时序图。用户的具体的信息会缓存到 ConcurrentHashMap 当中。通过 Spring 定时器 NacosUserDetailsServiceImpl#reload 定时从数据库刷新缓存中的数据。因为我们在初始化数据库的时候会调用以下 SQL 语句保存数据到内存数据库 derby 当中。

CREATE TABLE users (
	username varchar(50) NOT NULL PRIMARY KEY,
	password varchar(500) NOT NULL,
	enabled boolean NOT NULL DEFAULT true
);

INSERT INTO users (username, password, enabled) VALUES 
('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);

这条数据就是加载到NacosUserDetailsServiceImpl 的缓存 Map 中,这样我们就可以使用 nacos/nacos 用户名密码进行授权登陆了。

2、暴露 Http 服务给控制台

下面我们来讨论一下 console 服务启动之后暴露了哪些 http 服务给控制台,在这里我们排除 naming 服务只讨论配置服务相关的 rest 服务.

2.1 console 服务

我们先来看一下 console 服务中的 ConsoleConfig 这个配置类。

ConsoleConfig.java

@Component
@EnableScheduling
@PropertySource("/application.properties")
public class ConsoleConfig 
    
    @Autowired
    private ControllerMethodsCache methodsCache;
    
    /**
     * Init.
     */
    @PostConstruct
    public void init() 
        methodsCache.initClassMethod("com.alibaba.nacos.core.controller");
        methodsCache.initClassMethod("com.alibaba.nacos.naming.controllers");
        methodsCache.initClassMethod("com.alibaba.nacos.config.server.controller");
        methodsCache.initClassMethod("com.alibaba.nacos.console.controller");
    
    
    @Bean
    public CorsFilter corsFilter() 
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.setMaxAge(18000L);
        config.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    
    
    @Bean
    public XssFilter xssFilter() 
        return new XssFilter();
    
    
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() 
        return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(ZoneId.systemDefault().toString());
    

首先配置两个 Filter 过滤器:CorsFilter 是为了解决 console 前后端分享接口调用跨域问题;XssFilter 为了解决跨域攻击问题。

ConsoleConfig#init 方法是解析传入包里面的 Controller@RequestMapping 方法缓存 http 请求与 @ReqeustMapping 的方法映射。这样在 Filter 里面就可以过滤掉非法请求。

下面我们来分析一下 console 模块下的 Controller:

  • HealthController:Nacos 健康的操作,比如:服务健康检测,查看 Config 与 Naming 是否对外服务。
  • NamespaceController:Nacos 命名空间相关的操作,比如:获取所有命名空间信息、获取命名空间详情、创建命名空间、修改命名空间、删除命名空间等。
  • ServerStateController:Nacos 服务状态的操作,比如:查看服务状态

2.2 core 服务

下面我们来分析一下 core 模块下的 Controller:

  • NacosClusterController:Nacos 集群相关的操作,比如:获取自身节点信息,获取所有节点信息、获取所有节点地址、查看当前节点是否健康等。
  • ServerLoaderController:Nacos 连接相关的操作,比如:获取当前服务的 Client 连接信息、获取当前服务的服务状态、当前服务重新连接客户端、获取当前服务指标等。
  • CoreOpsController:Nacos 基础操作,比如:执行 raft 命令、获取 ID 生成规则相关信息、设置日志级别
  • CoreOpsV2Controller:Nacos 基础操作v2,比如:执行 raft 命令、获取 ID 生成规则相关信息、设置日志级别
  • NacosClusterV2Controller:Nacos 集群相关的操作v2,比如:获取自身节点信息,获取所有节点信息、获取所有节点地址等。

2.3 config 服务

下面我们来分析一下 config 模块下的 Controller:

  • CapacityController:获取租户的配置能力、修改租户的配置能力
  • ClientMetricsController:获取配置中心集群的指标信息、获取当前节点所有客户端的指标
  • CommunicationController:把指定的配置保存到本地文件当中、获取 grpc 连接信息、获取 http 长轮训状态
  • ConfigController:发布配置信息、获取配置信息、删除配置信息、查询配置信息等
  • ConfigOpsController:把当前所有配置保存到本地文件当中、设置日志级别、使用 derby 内存数据库动态传入 SQL 操作、通过文件动态上传配置
  • HealthController:判断当前节点配置中心的健康情况
  • HistoryController:分页查询配置的历史记录、ID查询配置的历史记录详情、ID查询配置的上一个历史记录详情、根据命名空间查询配置的历史记录
  • ListenerController:根据 IP 获取所有的订单信息

3、暴露 Grpc 服务给客户端

console 模块启动的时候会启动两个 GRPC 服务端:一个是 SDK 使用;一个 Cluster 使用。它们两个暴露的服务都是一样的只不过是暴露的端口不一样:

  • SDK 服务:暴露端口 9848,Nacos 的启动端口 8848 + 1000(GrpcSdkServer#rpcPortOffset),
  • Cluster 服务:暴露端口 9849,Nacos 的启动端口 8848 + 1001(GrpcClusterServer#rpcPortOffset),

GRPC 类结构如下:

  • GrpcClusterServer:Cluster 偏移端口、获取 Cluster RPC 线程池
  • GrpcSdkServer:Sdk 偏移端口、获取 Sdk RPC 线程池
  • BaseGrpcServer:服务启动的具体实现
  • BaseRpcServer:RPC 基类实现,注册 Payload(GRPC请求响应类)、RPC服务启动

3.1 注册 Payload

Nacos 暴露的 Grpc 服务的请求参数与响应参数都基类都是 Payload,在基类 BaseRpcServer 中通过 static 代码块注册 Payload。

BaseRpcServer.java

    static 
        PayloadRegistry.init();
    

通过 Java 的 SPI 机制分别在 client 模块加载到 ClientPayloadPackageProvider 以及 config 加载到 ConfigPayloadPackageProvider

client/META-INF/services/com.alibaba.nacos.common.remote.PayloadPackageProvider

public class ClientPayloadPackageProvider implements PayloadPackageProvider 
    
    private final Set<String> scanPackage = new HashSet<>();
    
    
        scanPackage.add("com.alibaba.nacos.api.naming.remote.request");
        scanPackage.add("com.alibaba.nacos.api.remote.request");
        scanPackage.add("com.alibaba.nacos.api.config.remote.request");
        scanPackage.add("com.alibaba.nacos.api.naming.remote.response");
        scanPackage.add("com.alibaba.nacos.api.config.remote.response");
        scanPackage.add("com.alibaba.nacos.api.remote.response");
    
    
    @Override
    public Set<String> getScanPackage() 
        return scanPackage;
    

下面是 config 的配置:

config/META-INF/services/com.alibaba.nacos.common.remote.PayloadPackageProvider

public class ConfigPayloadPackageProvider implements PayloadPackageProvider 
    
    private final Set<String> scanPackage = new HashSet<>();
    
    
        scanPackage.add("com.alibaba.nacos.api.remote.request");
        scanPackage.add("com.alibaba.nacos.api.remote.response");
        scanPackage.add("com.alibaba.nacos.api.config.remote.request");
        scanPackage.add("com.alibaba.nacos.api.config.remote.response");
    
    
    @Override
    public Set<String> getScanPackage() 
        return scanPackage;
    


然后把这两个类所定义扫描的包以类名以及类 Class 注册到 PayloadRegistry.REGISTRY_REQUEST 当中。当进行 GRPC 调用的时候提供给 GrpcUtils 进行请求响应对象转换。

3 .2 启动 grpc 服务

BaseRpcServer.java

    @PostConstruct
    public void start() throws Exception 
        String serverName = getClass().getSimpleName();
        Loggers.REMOTE.info("Nacos  Rpc server starting at port ", serverName, getServicePort());
        
        startServer();
    
        Loggers.REMOTE.info("Nacos  Rpc server started at port ", serverName, getServicePort());
        Runtime.getRuntime().addShutdownHook(new Thread(() -> 
            Loggers.REMOTE.info("Nacos  Rpc server stopping", serverName);
            try 
                BaseRpcServer.this.stopServer();
                Loggers.REMOTE.info("Nacos  Rpc server stopped successfully...", serverName);
             catch (Exception e) 
                Loggers.REMOTE.error("Nacos  Rpc server stopped fail...", serverName, e);
            
        ));

    

这个类里面的逻辑比较简单:

  • 定义了服务启动接口 startServer(),由子类实现
  • 注册一个服务停止后调用的钩子函数 stopServer()

BaseGrpcServer.java

    @Override
    public void startServer() throws Exception 
        final MutableHandlerRegistry handlerRegistry = new MutableHandlerRegistry();
        
        // #1
        ServerInterceptor serverInterceptor = new ServerInterceptor() 
            @Override
            public <T, S> ServerCall.Listener<T> interceptCall(ServerCall<T, S> call, Metadata headers,
                    ServerCallHandler<T, S> next) 
                Context ctx = Context.current()
                        .withValue(CONTEXT_KEY_CONN_ID, call.getAttributes().get(TRANS_KEY_CONN_ID))
                        .withValue(CONTEXT_KEY_CONN_REMOTE_IP, call.getAttributes().get(TRANS_KEY_REMOTE_IP))
                        .withValue(CONTEXT_KEY_CONN_REMOTE_PORT, call.getAttributes().get(TRANS_KEY_REMOTE_PORT))
                        .withValue(CONTEXT_KEY_CONN_LOCAL_PORT, call.getAttributes().get(TRANS_KEY_LOCAL_PORT));
                if (REQUEST_BI_STREAM_SERVICE_NAME.equals(call.getMethodDescriptor().getServiceName())) 
                    Channel internalChannel = getInternalChannel(call);
                    ctx = ctx.withValue(CONTEXT_KEY_CHANNEL, internalChannel);
                
                return Contexts.interceptCall(ctx, call, headers, next);
            
        ;
        
        // #2
        addServices(handlerRegistry, serverInterceptor);
        
        // #3
        server = ServerBuilder.forPort(getServicePort()).executor(getRpcExecutor())
                .maxInboundMessageSize(getInboundMessageSize()).fallbackHandlerRegistry(handlerRegistry)
                .compressorRegistry(CompressorRegistry.getDefaultInstance())
                .decompressorRegistry(DecompressorRegistry.getDefaultInstance())
                .addTransportFilter(new ServerTransportFilter() 
                    @Override
                    public Attributes transportReady(Attributes transportAttrs) 
                        InetSocketAddress remoteAddress = (InetSocketAddress) transportAttrs
                                .get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
                        InetSocketAddress localAddress = (InetSocketAddress) transportAttrs
                                .get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR);
                        int remotePort = remoteAddress.getPort();
                        int localPort = localAddress.getPort();
                        String remoteIp = remoteAddress.getAddress().getHostAddress();
                        Attributes attrWrapper = transportAttrs.toBuilder()
                                .set(TRANS_KEY_CONN_ID, System.currentTimeMillis() + "_" + remoteIp + "_" + remotePort)
                                .set(TRANS_KEY_REMOTE_IP, remoteIp).set(TRANS_KEY_REMOTE_PORT, remotePort)
                                .set(TRANS_KEY_LOCAL_PORT, localPort).build();
                        String connectionId = attrWrapper.get(TRANS_KEY_CONN_ID);
                        Loggers.REMOTE_DIGEST.info("Connection transportReady,connectionId =  ", connectionId);
                        return attrWrapper;
                        
                    
                    
                    @Override
                    public void transportTerminated(Attributes transportAttrs) 
                        String connectionId = null;
                        try 
                            connectionId = transportAttrs.get(TRANS_KEY_CONN_ID);
                         catch (Exception e) 
                            // Ignore
                        
                        if (StringUtils.isNotBlank(connectionId)) 
                            Loggers.REMOTE_DIGEST
                                    .info("Connection transportTerminated,connectionId =  ", connectionId);
                            connectionManager.unregister(connectionId);
                        
                    
                ).build();
        
        // #4
        server.start();
    
  • #1 其实是定义一个服务器拦截器,用于设置 connection id 等信息
  • #2 往 MutableHandlerRegistry 动态添加 GRPC 方法定义
  • #3 通过上面的参数构建 GRPC 的 Server。
  • #4 启动 GRPC 服务

下面我们来看一下 GRPC 是动态添加 GRPC 方法到 Server 里面的。

BaseGrpcServer.java

    private void addServices(MutableHandlerRegistry handlerRegistry, ServerInterceptor... serverInterceptor) 
        
        // unary common call register.
        final MethodDescriptor<Payload, Payload> unaryPayloadMethod = MethodDescriptor.<Payload, Payload>newBuilder()
                .setType(MethodDescriptor.MethodType.UNARY)
                .setFullMethodName(MethodDescriptor.generateFullMethodName(REQUEST_SERVICE_NAME, REQUEST_METHOD_NAME))
                .setRequestMarshaller(ProtoUtils.marshaller(Payload.getDefaultInstance()))
                .setResponseMarshaller(ProtoUtils.marshaller(Payload.getDefaultInstance())).build();
        
        final ServerCallHandler<Payload, Payload> payloadHandler = ServerCalls
                .asyncUnaryCall((request, responseObserver) -> grpcCommonRequestAcceptor.request(request, responseObserver));
        
        final ServerServiceDefinition serviceDefOfUnaryPayload = ServerServiceDefinition.builder(REQUEST_SERVICE_NAME)
                .addMethod(unaryPayloadMethod, payloadHandler).build();
        handlerRegistry.addService(ServerInterceptors.intercept(serviceDefOfUnaryPayload, serverInterceptor));
        
        // bi stream register.
        final ServerCallHandler<Payload, Payload> biStreamHandler = ServerCalls.asyncBidiStreamingCall(
                (responseObserver) -> grpcBiStreamRequestAcceptor.requestBiStream(responseObserver));
        
        final MethodDescriptor<Payload, Payload> biStreamMethod = MethodDescriptor.<Payload, Payload>newBuilder()
                .setType(MethodDescriptor.MethodType.BIDI_STREAMING).setFullMethodName(MethodDescriptor
                        .generateFullMethodName(REQUEST_BI_STREAM_SERVICE_NAME, REQUEST_BI_STREAM_METHOD_NAME))
                .setRequestMarshaller(ProtoUtils.marshaller(Payload.newBuilder().build()))
                .setResponseMarshaller(ProtoUtils.marshaller(Payload

以上是关于nacos的本地配置与启动步骤及NoDataSourceset问题解决的主要内容,如果未能解决你的问题,请参考以下文章

Nacos下载及启动

nacos服务注册与发现及服务配置实现

启动项目载入配置中心配置失败,未配置参数

微服务nacos配置管理

微服务架构 *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)

SpringCloud + Nacos 简单注册消费例子 | Feign调用