使用Nacos实现服务注册与发现
# 使用Nacos实现服务注册与发现
https://nacos.io/ 官网
# 什么是 Nacos
Nacos: 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
它提供了服务注册与发现、配置管理等功能,帮助开发者更容易地实现微服务架构中的服务治理和配置管理。它也是一个微服务,不过它的功能是管理其他服务。
Nacos官网 (opens new window)中对它有详细的介绍,这里不过多展示,下面直接进行实操:
官网-Nacos快速上手指南 (opens new window)
据说 Nacos 在阿里巴巴内部有超过 10 万的实例运行,已经过了类似双十一等各种大型流量的考验。
Nacos默认是AP模式,但也可以调整切换为CP,我们一般用默认AP即可。
所谓的AP,和CP,其实是分布式理论中的一种特性。
CAP是一个分布式中的理论,CAP理论指出,在设计分布式系统时,无法同时满足以下三个基本需求中的全部,最多只能同时满足其中的两个:
Consistency(一致性):
- 指的是所有节点在同一时间看到的数据是相同的。换句话说,一旦某个客户端完成了对数据的一项写操作后,所有的后续读请求都应该能够读到这次写入的数据或之后更新的数据版本。这要求系统具有强一致性。
Availability(可用性):
- 表示系统始终处于可运行状态,即对于每一个来自客户端的应用程序请求都能得到响应——无论返回成功与否,但必须保证服务可用。即使部分节点故障,整个系统仍然需要保持对外提供服务的能力。
Partition Tolerance(分区容错性):
- 分区指的是分布式系统中不同节点分布在不同的子网络中(由于网络故障等原因导致),这些子网络可能彼此间通信中断。分区容错意味着即便发生这种情况,系统依然能够继续运作。为了实现这一点,系统必须能够在网络分割的情况下继续工作,并且至少能部分执行操作。
其实单机,就是所谓的AC。当然这是一种不严谨的说法。
# 主要功能
- 服务发现与管理:支持基于DNS和HTTP接口的服务注册和发现,使得服务能够自动找到彼此并进行通信。
- 配置管理:提供了一个中心化的配置管理系统,支持实时更新配置,且可以针对不同的环境(如开发、测试、生产)设置不同的配置。
- 动态DNS服务:通过支持权重路由,可以让用户更容易地实现中间层负载均衡、更灵活的路由策略以及流量控制等需求。
可能有人会问,它既然是管理服务的,那它能具有负载均衡的能力吗?
它自身也具有负载均衡的能力。然而,通过与其他组件的集成,Nacos 可以实现更强大的服务调用之间的负载均衡。
- 集成负载均衡器:它可以很容易地与一些常见的负载均衡策略或工具集成,如 Ribbon(Spring Cloud 中的一个负载均衡器)、LoadBalancer Client(Spring Cloud LoadBalancer)等。这些工具可以根据从 Nacos 获取的服务实例信息来决定将请求分发到哪个实例上,从而实现客户端侧的负载均衡。
# 解决的问题
- 服务发现难:在微服务架构中,随着服务数量的增长,手动维护服务之间的调用关系变得复杂。Nacos简化了这一过程,让服务之间可以自动发现对方。
- 配置管理不便:传统的配置文件方式难以应对频繁变化的配置项,尤其是在多环境部署时。Nacos允许集中管理和动态更新配置,减少了配置错误的风险。
- 服务治理挑战:包括但不限于服务健康检查、流量调度等问题。Nacos提供的服务管理能力有助于提高系统的可用性和稳定性。
# 带来的问题
任何一个新的东西,在解决问题时,也会带来一些新的问题(只不过有些问题,可能在目前看来,不用在意,于是便认为没有带来问题了)。
- 依赖增加:引入Nacos意味着你的项目需要依赖额外的服务组件,这增加了系统架构的复杂度和运维成本。
- 高可用性要求:为了确保服务发现和配置管理的可靠性,Nacos本身也需要部署为高可用模式,这对资源消耗提出了更高的要求。
- 学习曲线:对于初次接触Nacos的团队来说,理解和掌握其工作原理及最佳实践可能需要一定的时间成本。
- 潜在性能瓶颈:虽然Nacos设计上考虑了大规模场景下的性能问题,但在极端情况下(如超大规模集群),仍可能存在性能瓶颈或扩展性挑战。
# 安装与运行nacos
https://github.com/alibaba/nacos/releases
我这里使用的版本为 1.4.0
Nacos 1.X 是老版本,2.X版本是新版(目前有这2大版本)
2.X在启动时,需要修改部分配置,具体请查看官网
下载 nacos-server-$version.zip 包。完成之后,解压。根据不同平台,执行不同命令,启动单机版Nacos服务:
unzip nacos-server-$version.zip 或者 tar -xvf nacos-server-$version.tar.gz
cd nacos/bin
2
# 启动服务
注意
注:Nacos的运行需要以至少2C4g60g*3的机器配置下运行。
# Linux/Unix/Mac
启动命令(standalone代表着单机模式运行,非集群模式):
sh startup.sh -m standalone
如果您使用的是ubuntu系统,或者运行脚本报错提示[[符号找不到,可尝试如下运行:
bash startup.sh -m standalone
# Windows
启动命令(standalone代表着单机模式运行,非集群模式):
startup.cmd -m standalone
# 启动成功
在日志输出中看到巨大的nacos标志以及
INFO Nacos started successfully in stand alone mode. use embedded storage
则表示服务启动成功
# 页面访问
启动完成之后,访问:http://127.0.0.1:8848/nacos/,可以进入Nacos的服务管理页面;
默认用户名密码为:nacos
# 配置文件
以下是它解压后的文件夹及其文件,最外层一共有5个,这里只展示部分文件
.
├── LICENSE
├── NOTICE
├── bin
│ ├── shutdown.cmd
│ ├── shutdown.sh
│ ├── startup.cmd
│ └── startup.sh
├── conf
│ ├── 1.4.0-ipv6_support-update.sql
│ ├── application.properties
│ ├── application.properties.example
│ ├── cluster.conf.example
│ ├── nacos-logback.xml
│ ├── nacos-mysql.sql
│ └── schema.sql
└── target
└── nacos-server.jar
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 关于启动,在bin文件夹下
启动nacos默认是集群版,现在我们要对其进行修改,则需要修改startup.cmd(或startup.sh)文件
export SERVER="nacos-server"
export MODE="standalone"
export FUNCTION_MODE="all"
export MEMBER_LIST=""
export EMBEDDED_STORAGE=""
2
3
4
5
我这里用的是sh文件,将 MODE改为 standalone即可。
如果你不想修改脚本本身,可以在执行 startup.sh 脚本时通过命令行参数指定单机模式
sh startup.sh -m standalone
- 全局配置文件
conf文件夹下可以进行对应配置的修改。Application.properties
#*************** Spring Boot Related Configurations ***************#
### Default web context path:
server.servlet.contextPath=/nacos
### Include message field
server.error.include-message=ALWAYS
### Default web server port:
server.port=8848
# .....
#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
# spring.datasource.platform=mysql
### Count of DB:
# db.num=1
### Connect URL of DB:
# 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=nacos
# db.password.0=nacos
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这行配置设定了Nacos服务的Web访问路径前缀为 /nacos。也就是说,如果你想访问Nacos的服务端点,你需要在请求URL中包含这个上下文路径。例如,默认情况下,Nacos控制台可以通过 http://localhost:8848/nacos 访问(假设Nacos运行在本地并且使用默认端口)。
# 可能出现的问题
# 服务启动失败
启动后,页面提示:
nohup: /Library/Internet: No such file or directory
大概率论是因为jdk没配置对。nacos会使用系统默认的Java_Home,而mac自带的jdk并不满足nacos的要求(路径Internet Plug-Ins中存在一个空格,这个空格会导致shell脚本失效)。
解决方案--2种:
- 修改nacos的配置文件,将可用的jdk环境配置给nacos
- 修改系统默认的jdk环境
这里我使用修改nacos配置文件的方式
- 打开nacos/bin/starup.cmd (windows环境下) 其他环境则是 starup.sh
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home
export JAVA="$JAVA_HOME/bin/java"
export BASE_DIR=`cd $(dirname $0)/..; pwd`
export DEFAULT_SEARCH_LOCATIONS="classpath:/,classpath:/config/,file:./,file:./config/"
export CUSTOM_SEARCH_LOCATIONS=file:${BASE_DIR}/conf/,${DEFAULT_SEARCH_LOCATIONS}
#===========================================================================================
# JVM Configuration
#===========================================================================================
if [[ "${MODE}" == "standalone" ]]; then
2
3
4
5
6
7
8
9
10
11
12
以上是文件中的部分内容,将JAVA_HOME后的路径设置成可以用的jdk路径即可,以上路径为我电脑的路径,大家根据自己的实际情况来修改。
# 编写对应微服务
nacos这个组件,有2个部分,分别是配置管理和服务发现。需要引入的pom坐标也不同,分别是:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>${latest.version}</version>
</dependency>
<!--配置管理模块-->
2
3
4
5
6
下面是服务发现模块:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${latest.version}</version>
</dependency>
<!--配置服务发现模块-->
2
3
4
5
6
引入该模块后,就可以将微服务注入到nacos-server上,从而实现被其他服务调用了。
在成功启动nacos后,下面让我们尝试编写一些简单的应用,来实验nacos的服务注册与发现功能(也就是--nacos-discovery模块)。
# 实现服务提供者
- 引入对应pom坐标
- 创建一个SpringBoot应用,并引入
Nacos的服务注册与发现模块
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<groupId>com.cogar.nacos</groupId>
<artifactId>nacosDiscovery</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nacosDiscovery</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring.cloud.version>2021.0.1</spring.cloud.version>
<spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos发现与注册模块-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
由于在dependencyManagement中已经引入了版本,所以这里就不用指定具体版本了
- 在
application.properties(或application.yml) 中配置 Nacos server 的地址:
server.port=8809
spring.application.name=service-provider
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
2
3
4
其实在这里还可以进行其他的配置,
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
namespace: public
2
3
4
5
6
7
8
其中username、password、namespace,不过即使不配置也会有默认值,以上就是使用的默认值。
username和password就是用来登录nacos-server的。而namespace有什么用呢?它则是用于实现服务隔离的,它可以用来隔离我们不同的一个服务实例OK。比如说我们可以给它加一个开发环境,加一个生产环境,对吧?开发环境我们给它配置命名空间,是开发环境,这个生产环境我们给它配置成生产环境。
当然关于nacos-discovery的配置不止上述这些,这里就不展开了。
- 开启服务注册发现功能
通过 Spring Cloud 原生注解 @EnableDiscoveryClient 开启服务注册发现功能:
@EnableDiscoveryClient
@SpringBootApplication
public class NacosDiscoveryApplication {
public static void main(String[] args) {
SpringApplication.run(NacosDiscoveryApplication.class, args);
}
}
2
3
4
5
6
7
8
9
在一些新版的nacos中,即使不使用 @EnableDiscoveryClient也能够开启服务发现与注册功能。
- 编写我们自己需要的功能与服务,这里就简单的实现一下:
@RestController
public class TestController {
@GetMapping("/hello/{echo}")
public String echoHello(@PathVariable("echo")String echo){
return "this is service-provider :"+echo;
}
}
2
3
4
5
6
7
8
- 完成后启动项目,之后就能看到这样一行日志,且在nacos的管理页,也能看到多了一个服务
INFO 10852 --- [ main] c.a.c.n.registry.NacosServiceRegistry : nacos registry, DEFAULT_GROUP service-provider x.x.x.x:8809 register finished
且在nacos-server的web页面,服务管理页面能看到新增了一个微服务。
到此,一个简单的服务提供者编写完成。
# 实现服务消费者
所谓的消费者就是指可以通过 Nacos从 Nacos server 上获取到它要调用的服务(服务提供者提供的)。
- 导入对应的pom坐标
<!--2020版本就已经不再使用netflix,自然也就没有了ribbon。怎么办?使用Spring Cloud Load Balancer代替-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
2
3
4
5
这里跟之前服务提供者的pom几乎一样,只是多了一个loadbalancer。如果不加在消费者进行调用时,会出现UnknownHostException。
为什么会出现这个错误,是因为——nacos在进行服务调用时,会进行负载均衡,换个角度说就是要依赖,负载均衡器。
所以在这里你必须去添加一个负载均衡器的这么一个注解。好吧?添加在哪呢?在我们的这个RestTemplate的这个Bean当中去给它加上load baLance这个注解。OK好,加完之后,相当于我们这个RestTemplate就有了负载均衡的一个调用机制,之后就能成功调用了。
当然,这里的调用只是基于RestTemplate来进行,如何使用其他的比如feign,可能情况就会改变,因为其他组件或许自身实现了——负载均衡。
- 在配置文件
application.properties(或application.yml)进行服务的配置
server.port=8088
spring.application.name=service-consumer
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
2
3
4
- 通过 Spring Cloud 原生注解
@EnableDiscoveryClient开启服务注册发现功能。
- 启动类加注解,开启服务注册与发现
@EnableDiscoveryClient
//在启动类出加
2
- AppConfig.java
@Configuration
public class AppConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
// @LoadBalanced :实现了服务名的调用,同时开启基于服务名的负载均衡调用
// 不加在后续调用其他服务时 则只能通过ip的方式
}
2
3
4
5
6
7
8
9
10
11
定义RestTemplate的时候,增加了@LoadBalanced注解(该注解实现了@LoadBalanced 与 Ribbon 的集成),在真正调用服务接口的时候,原来host部分是通过手工拼接ip和端口的,现在直接采用服务名即可。
在真正调用的时候,Spring Cloud会将请求拦截下来,然后通过负载均衡器选出节点,并替换服务名部分为具体的ip和端口,从而实现基于服务名的负载均衡调用。
@RestController
public class DemoController {
private final RestTemplate restTemplate;
@Autowired
public DemoController(RestTemplate restTemplate) {this.restTemplate = restTemplate;}
@GetMapping(value = "/print/{str}")
public String test(@PathVariable String str) {
return restTemplate.getForObject("http://service-provider/hello/" + str, String.class);
// 调用服务提供者 提供的接口
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 启动项目并对消费者提供的接口进行调用(提供者跟消费者都要同时启动),
- 调用http://127.0.0.1:8088/print/master接口
- 看到返回内容为:this is service-provider :master
# 一些注意事项
该项目使用的SpringBoot及cloud版本信息为:
<spring.cloud.alibaba.version>2021.0.1.0</spring.cloud.alibaba.version>
<spring.boot.version>2.6.3</spring.boot.version>
<spring.cloud.version>2021.0.1</spring.cloud.version>
2
3
某些版本可以不用额外引入loadbalancer也能进行服务调用
下面是SpringBoot、SpringCloud以及SpringCloud Alibaba的版本对应关系
# 一些问题
# nacos如何分辨服务没有挂掉?
使用心跳机制。
它会动态的去调用这个服务获取的接口,来实时的拉取到我们最新的一个服务列表。并且还有一个心跳机制,当我们的这个服务没有响应了的时候,会将我们的这个状态给它改变。超过了一段时间,又会将这个服务给它剔除掉。所以说他会能够动态的感知和刷新某个服务实例的一个服务列表。
心跳包,是微服务方向nacos发送。
# 跟其他注册中心的区别
| nacos | Eureka | Consul | CoreDNS | Zookeeper | |
|---|---|---|---|---|---|
| 致性协议 | CP+AP | AP | CP | -- | CP |
| 健康检查 | TCP/HTTP/MYSQL/Client Beat | Client Beat | TCP/HTTP/gRPC/Cmd | -- | Keep Alive |
| 负载均衡策略 | 权重/metadata/Selector | Ribbon | Fabio | RoundRobin | -- |
| 雪崩保护 | 有 | 有 | 无 | 无 | 无 |
| 自动注销实例 | 支持 | 支持 | 支持 | 不支持 | 支持 |
| 访问协议 | HTTP/DNS | HTTP | HTTP/DNS | DNS | TCP |
| 监听支持 | 支持 | 支持 | 支持 | 不支持 | 支持 |
| 多数据中心 | 支持 | 支持 | 支持 | 不支持 | 不支持 |
| 跨注册中心同步 | 支持 | 不支持 | 支持 | 不支持 | 不支持 |
| SpringCloud集成 | 支持 | 支持 | 支持 | 不支持 | 支持 |
| Dubbo集成 | 支持 | 不支持 | 支持 | 不支持 | 支持 |
| K8S集成 | 支持 | 不支持 | 支持 | 支持 | 不支持 |
# 关于namespace
如果nacos-server中你想通过namespace来区分服务,那么在SpringBoot(也就是nacos客户端中),你对应的namespace的值应该取的是命名空间ID,而不是名称。
spring:
cloud:
nacos:
config:
namespace: c9f18cb4-b445-4da4-b526-f52bf3298730
file-extension: properties
discovery:
server-addr: 127.0.0.1:8848
namespace: c9f18cb4-b445-4da4-b526-f52bf3298730
application:
name: orderD
2
3
4
5
6
7
8
9
10
11
12
命名空间的id是创建之后,自动生产的。