Nacos服务发现和分布式配置

了解SpringCloud框架的开发者都知道Eureka、Consul等注册中心,在SpringCloudAlibaba中,服务注册中心是Nacos,它是Dynamic Naming and Configuration Service的缩写。和Consul类似,Nacos提供了服务注册中心和分布式配置中心两个功能,这篇笔记我们介绍在SpringCloudAlibaba中Nacos的搭建和使用。

nacos简介

nacos是阿里开源的一款分布式微服务注册中心和配置中心,在SpringCloud中使用nacos非常简单,只需要引入起步依赖并添加几行配置即可。nacos采用了CP+AP设计,即可以通过配置选择客户端节点使用CP模式或AP模式,默认为AP模式,这能够很好的解决不同场景的业务需求。

官方文档:https://nacos.io/

单机nacos服务搭建

在开发阶段,我们可以启动一个单节点的nacos服务,但在生产环境中,我们一般需要搭建高可用的nacos集群,此外nacos需要数据库来持久化数据,在开发阶段我们可以使用nacos自带的嵌入式数据库,在生产环境则推荐将数据存储到MySQL等数据库中。启动单节点的nacos服务非常简单,我们首先从Github下载nacos软件包。

Github Release页:https://github.com/alibaba/nacos/releases

这里我们下载最新版即可,解压后我们可以看到其中的目录结构如下:

|_ bin # nacos服务的启动和停止脚本
|_ conf # nacos的配置文件
|_ target # nacos程序jar包

对于开发环境,我们不需要修改任何配置,直接运行启动脚本即可,这会以单节点、无认证鉴权、使用嵌入式数据库的方式启动nacos服务。在Linux下,我们直接执行以下命令启动单机版的nacos。

./startup.sh -m standalone

在Windows下,我们将启动脚本换成对应的.cmd即可。

startup.cmd -m standalone

此时我们就启动了可以用于开发环境的单机版nacos服务,它默认开启了8848端口用于访问管理控制台,我们可以直接用浏览器访问http://localhost:8848/nacos/index.html查看nacos管理控制台,进行服务和分布式配置的管理。

开启认证功能

前面我们启动的nacos没有认证功能,任何人都是可以直接访问的,这显然不安全。如果需要开启认证功能,我们可以在nacos的conf/application.properties配置文件中添加相关配置。

nacos.core.auth.enabled=true
nacos.core.auth.server.identity.key=root
nacos.core.auth.server.identity.value=abc123
nacos.core.auth.plugin.nacos.token.secret.key=VTVkMjFRN2ZhUzdLS2FDU2FzYTg5YTBDQzBheFE3ZmFTN0tLYUNT

配置中,我们开启了认证功能然后设置了用户名和密码,nacos.core.auth.plugin.nacos.token.secret.key配置了一个密钥,它会通过HMAC生成一个对称密钥用于认证信息的加密存储,我们生成一个随机字符串后再将其编码为Base64作为这个密钥即可。

此时我们尝试访问nacos控制台,可以看到必须要登陆才能继续操作。

nacos集群搭建

前面我们搭建了单节点的nacos服务,不过这种方式仅适用于开发环境,对于生产环境我们需要搭建高可用的nacos集群,数据也需要持久化到数据库中,这里我们继续介绍如何搭建生产可用的nacos集群。nacos的集群搭建方式可以参考下图,我们需要至少3台Linux主机部署nacos,此外还需要一台Nginx主机,以及一个MySQL数据库服务器用于持久化数据。

出于方便起见,这里我们启动3个虚拟机部署nacos,1个虚拟机部署MySQL和Nginx,实际生产环境中数据库通常需要单独部署,但这里我们主要是演示nacos的使用,因此就不弄那么复杂了。

192.168.1.100 # Nginx和MySQL
192.168.1.119 # nacos1
192.168.1.120 # nacos2
192.168.1.121 # nacos3

注意:根据官方文档nacos集群模式的一个节点至少需要2GB的堆内存,因此我这里使用的虚拟机都配置为了2C4G,内存配置的过小可能出现启动报错的情况。

首先我们需要将nacos软件包复制到部署nacos的3台主机中,然后分别解压,我们需要修改配置文件来创建nacos集群。我们先修改conf/application.properties配置文件和数据持久化相关的设置,在这里我们指定MySQL数据库的地址、用户名、密码等信息。

spring.sql.init.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://192.168.1.100:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root

配置后,我们还需要在数据库中创建对应的库和表,我们这里创建的库名为nacos,表结构可以在nacos软件包的conf/mysql-schema.sql文件中找到,我们在nacos库中执行该SQL脚本即可。其它配置如认证功能等,我们可以根据自己的需要进行修改。

然后我们创建集群,这里我们需要将cluster.conf.example配置文件复制一份命名为cluster.conf

cp cluster.conf.example cluster.conf

修改其中的内容,配置集群的各个节点。

192.168.1.119
192.168.1.120
192.168.1.121

配置完成后就可以尝试启动nacos了,我们运行bin/start.sh脚本即可。

集群模式启动速度会慢一些,我们查看日志,当出现Nacos started successfully in cluster mode. use external storage时表示启动成功,此时我们可以查看任意节点的nacos控制台。

最后我们还需要配置Nginx使得我们的微服务程序能够以负载均衡的方式访问nacos,而非固定配置一个nacos节点的IP,这样起不到高可用的效果。下面是一个可供参考的Nginx配置。

worker_processes  1;

events {
    worker_connections 1024;
}

http {
    include mime.types;
    default_type application/octet-stream;
    sendfile on;
    keepalive_timeout 65;

    upstream nacos-cluster {
        server 192.168.1.119:8848;
        server 192.168.1.120:8848;
        server 192.168.1.121:8848;
    }

    server {
        listen 8848;
        server_name 192.168.1.100;

        location / {
            proxy_pass http://nacos-cluster;
        }
    }
}

stream {
    upstream nacos-grpc {
        server 192.168.1.119:9848;
        server 192.168.1.120:9848;
        server 192.168.1.121:9848;
    }

    server {
        listen 9848;
        proxy_pass nacos-grpc;
    }
}

配置文件中,我们反向代理了3个nacos节点的8848端口,它们使用HTTP协议供客户端连接,除此之外我们还代理了TCP协议9848端口,该端口用于客户端GRPC连接。注意nacos实际上还会使用7848和9849端口,这两个端口用于nacos节点间的通信,切勿暴露到集群外部,如果nacos启动时出现和这两个端口相关的异常需要检查主机的防火墙配置。

如上述配置完成后,我们就可以访问http://192.168.1.100:8848/nacos/index.html查看nacos集群的控制台了,此时微服务连接nacos指定Nginx主机的8848端口即可。

命名空间和分组

nacos具有命名空间和分组的概念,它们可以用于区分部署环境和划分逻辑单元。

命名空间:命名空间用来区分不同环境或不同业务场景下的配置和服务的逻辑隔离单元,每个命名空间都有自己独立的配置集、服务注册表等资源,通过使用命名空间,可以在同一个Nacos集群中实现多环境(如开发、测试、生产)或多业务场景之间的隔离,使得不同环境或业务场景的配置和服务能够独立管理、部署和调整,命名空间的默认值为public

分组:分组是指在某个命名空间中对配置或服务进行划分,通过给配置或服务分配不同的组可以更好地组织和管理它们,分组默认值为DEFAULT_GROUP,如果我们不进行自定义的分组那么默认情况下服务和配置都会处于DEFAULT_GROUP分组下。

我们可以在nacos的控制台对命名空间和分组进行配置。

注意:nacos目前的最新2.3.1版本似乎有个bug,我们初次添加配置、查看服务列表前需要先点选上面的命名空间,它没有一个默认的选中项,如果不点选命名空间创建出来的配置命名空间是“undefined”,我们的微服务就无法读取了。

实现服务注册

SpringCloud工程中,基于nacos实现服务注册发现需要引入spring-cloud-starter-alibaba-nacos-discovery依赖。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

我们这里微服务间调用采用SpringCloud OpenFeign,因此除了spring-cloud-starter-alibaba-nacos-discovery我们还需要SpringCloud OpenFeign相关的依赖,有关SpringCloud OpenFeign的使用方法请参考SpringCloud相关章节,这里就不重复赘述了。这里我们创建两个微服务productorder,其中order服务会调用product服务的接口,工程目录结构如下。

|_ order-api
|_ order-svc
|_ product-api
|_ product-svc

在微服务工程的application.properties文件中,我们需要配置nacos服务的地址、用户名和密码,以及用于服务发现的命名空间和分组,下面是一个例子。

spring.cloud.nacos.server-addr=127.0.0.1:8848
spring.cloud.nacos.username=root
spring.cloud.nacos.password=abc123
spring.cloud.nacos.discovery.namespace=public
spring.cloud.nacos.discovery.group=DEFAULT_GROUP

当然,命名空间和分组如果都使用默认值也可以省略不写。

在微服务的启动类上我们需要使用@EnableDiscoveryClient标注启用服务发现,@EnableFeignClients注解需要标注在服务的调用方,以生成代理类和注册SpringBean。

package com.gacfox.demo.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.gacfox.demo.product.client"})
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

此时我们可以访问nacos控制台,查看服务的注册情况。

使用SpringCloud OpenFeign进行服务间调用例子如下。

package com.gacfox.demo.order.controller;

import com.gacfox.demo.product.client.ProductClient;
import com.gacfox.demo.product.model.Product;
import com.gacfox.demo.product.order.model.Order;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/api/v1/order")
public class OrderController {
    @Resource
    private ProductClient productClient;

    @GetMapping("/getOrderById")
    public Order getOrderById(String orderId) {
        // ... 具体的业务逻辑
    }
}

接入分布式配置中心

接入nacos分布式配置中心需要引入spring-cloud-starter-alibaba-nacos-config依赖。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

application.properties中,我们需要配置nacos服务地址,引入nacos中创建的配置文件,以及配置的命名空间和分组。

spring.cloud.nacos.server-addr=127.0.0.1:8848
spring.cloud.nacos.username=root
spring.cloud.nacos.password=abc123
spring.config.import=nacos:app.properties,nacos:${spring.application.name}-${spring.profiles.active}.yml
spring.cloud.nacos.config.namespace=public
spring.cloud.nacos.config.group=DEFAULT_GROUP

这里我们配置了spring.config.import属性,它是一个数组,后面跟着引入的配置文件名,由于这里我们使用了nacos分布式配置中心,因此这些文件名都是以前缀nacos:开头的,当然这个前缀是可配置的,但我们一般遵循这个约定即可。从配置中我们看到,我们引入了2个配置文件,一个是固定文件名的app.properties,而另一个配置文件的文件名是动态拼接的,我们使用了服务的名字spring.application.name和环境名spring.profiles.active拼接了这个文件名,在开发环境中,它的名字可能是order-dev.yml。无论如何,这个配置文件名需要能和配置中心中创建的配置dataId对应。

在nacos控制台中,我们可以创建对应的配置项,比如我这里创建了配置项order-dev.yml,我们可以将其理解为一个配置文件,注意配置的DataId需要和微服务中application.properties中引入的配置文件名匹配,否则配置是无法正确加载的,SpringCloud工程通常使用Properties和YAML两种配置格式,我们对应创建所需的格式,并编写配置数据即可。

读取配置非常简单,我们直接使用@Value注解等Spring标准方式读取即可,nacos配置也支持基于SpringCloud的@RefreshScope注解热更新,下面是一个例子。

package com.gacfox.demo.order.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RefreshScope
@RestController
public class DemoController {
    @Value("${app.config}")
    private String config;

    @GetMapping("/demo")
    public String getConfig() {
        // ... 读取配置进行一些操作
    }
}

此时我们可以尝试访问/demo接口,动态更新nacos中的配置并查看微服务中读取的配置是否发生了热更新。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。