写在前面

在前面我们学习了Netflix开源的Eureka组件来实现服务发现与服务注册中心,接下来学习Spring Cloud Alibaba开源的的Nacos组件,使用它来代替eureka和consul等传统方式来实现服务发现与服务,注册中心以及Spring Cloud Config分布式配置中心的功能。由于Nacos是阿里巴巴开源的,因此中文文档非常齐全,由于本文主要介绍Spring Cloud集成Nacos,因此对于Nacos的详细介绍不会有很多篇幅,而更多的内容则会放在另一个系列文章中。

Nacos是什么

Nacos简介

Nacos致力于帮助开发者发现、配置和管理微服务。Nacos提供了一组简单易用的特性集,帮助开发者快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos帮助开发者更敏捷和容易地构建、交付和管理微服务平台。 Nacos是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。

Nacos特性

在Nacos中,服务(Service)是一等公民,Nacos支持几乎所有主流类型的“服务”的发现、配置和管理,如Kubernetes ServicegRPCDubbo RPC Service等,Nacos的关键特性包括:
(1)服务发现与服务健康监测。
Nacos支持基于DNS和基于RPC的服务发现。服务提供者使用 原生SDKOpenAPI、或一个独立的Agent TODO注册 Service 后,服务消费者可以使用DNS TODO 或HTTP&API查找和发现服务。

Nacos提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos支持传输层 (PING 或 TCP)和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。对于复杂的云环境和网络拓扑环境中(如VPC、边缘网络等)服务的健康检查,Nacos提供了agent上报模式和服务端主动检测2种健康检查模式。Nacos还提供了统一的健康检查仪表盘,帮助开发者根据健康状态管理服务的可用性及流量。

(2)动态配置服务。
动态配置服务可以让开发者以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。

动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。

配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。

Nacos提供了一个简洁易用的UI (控制台样例 Demo) 帮助开发者管理所有的服务和应用的配置。Nacos还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助开发者更安全地在生产环境中管理配置变更和降低配置变更带来的风险。

(3)动态DNS服务。
动态DNS服务支持权重路由,让您更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单DNS解析服务。动态DNS服务还能让开发者更容易地实现以DN 协议为基础的服务发现,以帮助开发者消除耦合到厂商私有服务发现API上的风险。

Nacos提供了一些简单的 DNS APIs TODO 帮助开发者管理服务的关联域名和可用的 IP:PORT列表。

(4)服务及元数据管理。
Nacos能让开发者从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的SLA以及最首要的metrics统计数据。

Nacos基本架构及概念

接下来系统学习Nacos的基本架构和一些基本概念,掌握它们可以帮助开发者更好的理解和正确的使用Nacos产品。

安装Nacos

(1)安装Maven和Java环境,推荐使用64位的操作系统,JDK版本不低于1.9,Maven版本不低于3.2。
(2)开发者可以点击 这里获取Nacos对应版本,当前推荐使用稳定版本为1.3.1。
(3)启动服务器。请注意如果是单机模式运行需要添加standalone参数,也就是非集群模式。开发者是Linux系统则使用sh startup.sh -m standalone命令;Ubuntu系统则使用bash startup.sh -m standalone命令;Windows系统则使用cmd startup.cmd -m standalone命令,当然最简单的方式就是双击startup.cmd运行文件即可。
(4)打开浏览器访问http://127.0.0.1:8848/nacos/,然后在登录页面填写账户信息,默认账号和密码都是nacos,由于本篇文章只是学习Spring Cloud与Nacos的集成使用,而关于Nacos的配置信息大家可以去conf文件夹下的application.properties配置文件中进行阅读,这都是老套路了。下面是Nacos启动的页面,是不是一股浓浓的阿里云风格:

构建应用接入Nacos服务注册中心

在完成了Nacos服务的安装和启动之后,接下来就可以顺着官方文档的思路开始编写两个应用,一个是服务提供者,另一个是服务消费者来学习Nacos的服务发现与服务注册功能。点击 这里先了解Spring Cloud如何与Nacos集成。

我们知道SpringBoot意在简化,是一种开发、配置风格;而SpringCloud意在简化分布式,是功能的集合,风格的统一。SpringCloud是把当下流行的分布式组件拿过来,使用SpringBoot风格进行封装简化,使开发者可以快速上手,同时使开发者有一致的用户体验。

创建服务提供者

第一步,创建Spring Boot项目。使用Spring Initializr工具创建一个Spring Boot应用,名称为service-provider

第二步,编辑依赖文件。编辑项目的pom.xml依赖文件,添加如下配置信息:

1
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
56
57
58
59
60
61
62
63
64
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

特别注意其中Spring Cloud、Spring Cloud Alibaba和Spring Boot这三者的版本信息,开发者可以点击 这里进行查阅,这个页面详细介绍了组件版本关系、毕业版本依赖关系等信息,尤其是这个毕业版本依赖关系,笔者使用的Spring Boot版本为2.3.2.RELEASE,Spring Clou版本为Hoxton.SR8,Spring Cloud Alibaba版本为2.2.3.RELEASE,也就是如下所示的版本信息:

注意三者使用的<dependency><groupId><artifactId><version>信息。

其实Nacos就像之前使用的zipkin一样,官方提供了server服务端,也就是之前我们下载的nacos-server-1.3.1文件。

第三步,开启服务注册发现功能。开发者可以通过在项目启动类上添加@EnableDiscoveryClient注解进而达到开发服务注册发现的目的,如下所示:

1
2
3
4
5
6
7
@EnableDiscoveryClient
@SpringBootApplication
public class ServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceProviderApplication.class, args);
}
}

第四步,配置服务名称和服务注册中心地址。application.properties配置文件中通过spring.application.name属性来为服务命名,如命名为service-provider。接着再通过spring.cloud.nacos.discovery.server-addr属性来指定服务注册中心的地址,此处指定为之前从GitHub上下载的Nacos服务,如下所示:

1
2
3
4
spring.application.name=service-provider
server.port=8070

spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

第五步,提供一个访问接口。由于这是一个web工程,因此可以添加一个Controller,并提供一个访问入口。在项目目录下新建controller包,并在里面新建HelloController文件,其中的代码为:

1
2
3
4
5
6
7
8
9
10
@RestController
@Slf4j
public class HelloController {
@GetMapping(value = "/hello")
public String hello(@RequestParam String name){
log.info("the name is: "+name);
log.info("******正在调用service-provider服务******");
return "hello, "+name;
}
}

第六步,启动项目。首先确保之前下载的nacos服务已经正常运行,然后启动这里名为service-provider的服务,接着在浏览器访问http://127.0.0.1:8848/nacos即可看到Nacos控制面板服务管理的服务列表一栏中看到服务的注册信息:

点击右侧的详情按钮,即可查看对应服务的详细信息,如下所示:

服务提供者在创建完成后,接下来开始创建服务消费者。

创建服务消费者

第一步,创建Spring Boot项目。使用Spring Initializr工具创建一个Spring Boot应用,名称为service-consumer

第二步,编辑依赖文件。由于服务消费者的依赖和服务提供者的依赖一模一样,因此这里就不再粘贴依赖信息。

第三步,开启服务注册发现功能。开发者可以通过在项目启动类上添加@EnableDiscoveryClient注解进而达到开发服务注册发现的目的。同时考虑到服务消费者的负载均衡和高可用问题,一般都会提供一个RestTemplate方法,该方法用于返回一个RestTemplate对象,并在其上方添加@LoadBalanced注解,开启@LoadBalanced与Ribbon的集成,开启客户端负载均衡功能,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
@EnableDiscoveryClient
@SpringBootApplication
public class ServiceConsumerApplication {
@LoadBalanced
@Bean
RestTemplate restTemplate(){
return new RestTemplate();
}

public static void main(String[] args) {
SpringApplication.run(ServiceConsumerApplication.class, args);
}
}

第四步,配置服务名称和服务注册中心地址。application.properties配置文件中通过spring.application.name属性来为服务命名,如命名为service-consumer。接着再通过spring.cloud.nacos.discovery.server-addr属性来指定服务注册中心的地址,此处指定为之前从GitHub上下载的Nacos服务,如下所示:

1
2
3
4
spring.application.name=service-consumer
server.port=8080

spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

第五步,提供一个访问接口。由于这是一个web工程,因此可以添加一个Controller,并提供一个访问入口。在项目目录下新建controller包,并在里面新建TestController文件。请注意这个服务消费者需要调用之前服务提供者提供的/hello接口,相应的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Slf4j
@RestController
public class TestController {

@Autowired
private RestTemplate restTemplate;

@GetMapping(value = "/test")
public String test(){
log.info("******正在调用service-consumer服务******");
return restTemplate.getForEntity("http://service-provider/hello?name={1}",String.class,"envythink").getBody();
}
}

第六步,启动项目。首先确保之前下载的nacos服务和service-provider服务已经正常运行,然后启动这里名为service-consumer的服务,接着在浏览器访问http://127.0.0.1:8848/nacos即可看到Nacos控制面板服务管理的服务列表一栏中看到服务的注册信息:

此时在浏览器访问http://localhost:8080/test即可看到服务消费者调用了服务提供者的接口:

此时再去查看服务提供者和服务消费者的控制台,分别观察到如下所示的输出信息:

是不是感觉比Eureka使用起来更加简单,连eureka-server都不需要自己定义,直接使用官方提供的现成服务端即可。

按照之前学习Eureka的顺序,接下来应当学习如何搭建Nacos集群,但是考虑到Nacos数据持久化还未学习,因此这里先跳过Nacos集群的搭建工作。

服务消费方式

使用RestTemplate

通过查阅pom.xml配置文件中的依赖配置可以发现,nacos中其实是已经集成了netflix-ribbonconfig,如下所示:

也就是说Nacos不仅支持Eureka的服务发现与注册外,还具有Ribbon的客户端负载均衡、Config的分布式配置等功能。之前在Ribbon中使用的RestTemplate依然可以使用,关于RestTemplate的详细用法,可以参看之前写的《客户端负载均衡:SpringCloud Ribbon基础使用》一文的内容。这里再次介绍Ribbon中基于服务名来调用这一用法,可以看到在定义RestTemplate的时候,我们在这个用于提供RestTemplate对象的方法上添加了@LoadBalanced注解,这样在真正调用服务接口的时候,可以直接采用服务名来作为请求路径,而不是指定某个具体的实例,这种方式使得在后续调用的时候,Spring Cloud会将请求拦截下来,然后通过负载均衡器来选出可用的服务节点,并替换服务名部分为具体的ip和端口,这样就实现了基于服务名的负载均衡调用。

使用WebClient

WebClient是Spring5最新引入的,可以将其理解为reactive版的RestTemplate,这个笔者用的不多,但是其用法和上面介绍的RestTemplate差不多,因此这里就举个例子,这个例子同样可以实现前面RestTemplate实现的目的。

第一步,在service-consumer项目入口类ServiceConsumerApplication内添加一个用于生成 WebClient.Builder对象的loadBalancedWebClientBuilder方法:

1
2
3
4
5
@LoadBalanced
@Bean
WebClient.Builder loadBalancedWebClientBuilder(){
return WebClient.builder();
}

第二步,在service-consumer项目的controller包内新建WebController文件。请注意这个服务消费者需要调用之前服务提供者提供的/hello接口,相应的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
@Slf4j
public class WebController {
@Autowired
private WebClient.Builder webClientBuilder;

@GetMapping(value = "/webTest")
public Mono<String> webTest(){
return webClientBuilder.build()
.get()
.uri("http://service-provider/hello?name=envythink")
.retrieve()
.bodyToMono(String.class);
}
}

第三步,启动service-consumer项目。首先确保之前下载的nacos服务和service-provider服务已经正常运行,然后启动这里名为service-consumer的服务,接着在浏览器访问http://localhost:8080/webTest即可看到服务消费者调用了服务提供者的接口:

使用Feign

前面介绍的RestTemplate和WebClient都是Spring自己封装的工具,接下来学习Netflix OSS套件中的声明式服务调用Feign组件,通过它可以更加方便的来定义和使用服务消费者。同样举一个例子,这个例子也是实现前面RestTemplate实现的目的。

第一步,给service-consumer项目添加openfeign依赖。既然是Netflix OSS套件,那么就需要在service-consumer项目引入其依赖后才能使用:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

第二步,在service-consumer项目的入口类ServiceConsumerApplication上添加@EnableDiscoveryClient注解,用于开启客户端发现功能;同时还需要添加@EnableFeignClients注解,用于开启Spring Cloud Feign的支持功能,如下所示:

1
2
3
4
5
6
7
8
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ServiceConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerApplication.class, args);
}
}

第三步,service-consumer项目提供RestTemplate方法。考虑到服务消费者的负载均衡和高可用问题,一般都会提供一个RestTemplate方法,该方法用于返回一个RestTemplate对象,并在其上方添加@LoadBalanced注解,开启@LoadBalanced与Ribbon的集成,开启客户端负载均衡功能,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ServiceConsumerApplication {
@LoadBalanced
@Bean
RestTemplate restTemplate(){
return new RestTemplate();
}

public static void main(String[] args) {
SpringApplication.run(ServiceConsumerApplication.class, args);
}
}

第四步,确认service-provider服务提供者提供接口信息为/hello,如下所示:

1
2
3
4
5
6
7
8
9
10
@RestController
@Slf4j
public class HelloController {
@GetMapping(value = "/hello")
public String hello(@RequestParam String name){
log.info("the name is: "+name);
log.info("******正在调用service-provider服务******");
return "hello, "+name;
}
}

第五步,回到service-consumer项目中,在其项目目录下新建service包,并在里面新建FeignService接口,里面的代码为:

1
2
3
4
5
6
@Component
@FeignClient("service-provider")
public interface FeignService {
@GetMapping(value = "/hello")
String hello(@RequestParam("name") String name);
}

可以看到在上面的接口上,我们使用了@FeignClient("service-provider")注解并在其中通过指定服务名来绑定服务,然后再使用Spring MVC的@GetMapping注解来绑定具体该服务提供的REST接口。

第六步,调用服务。在service-consumer项目的controller包内新建FeignController类,用于实现对Feign客户端的调用。同时需要注入之前定义的FeignService实例,并在feignTest方法中调用这个绑定了service-provider服务接口的客户端来向该服务发起/hello接口的调用。相应的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
@RestController
@Slf4j
public class FeignController {
@Autowired
private FeignService feignService;

@GetMapping(value = "/feignTest")
public String feignTest(){
return feignService.hello("envythink");
}
}

第七步,启动service-consumer项目。首先确保之前下载的nacos服务和service-provider服务已经正常运行,然后启动这里名为service-consumer的服务,接着在浏览器访问http://localhost:8080/feignTest即可看到服务消费者调用了服务提供者的接口:

从上面三种消费方式可以看出,不论开发者使用的是RestTemplate、WebClient还是Feign,这似乎和开发者使用的是Nacos,还是Eureka与Consul无关。是的的确就是这样,因此对于一些Spring Cloud的老司机来说,就算更换了Nacos作为新的服务注册中心,其对于应用的底层代码是没有任何关系的。那么问题来了,为什么会有这种奇妙的感觉呢?原因在于无论是Nacos还是Eureka,其底层都是Spring Cloud Common。Spring Cloud Common在服务注册与发现、客户端负载均衡等方面都做了很好的抽象,而上层应用其实依赖的都是这些抽象接口,而非针对某个具体中间件的实现。因此在Spring Cloud中,开发者可以很方便的切换服务治理方面的中间件,这样就可以出现不同的实现方案。

这样关于Nacos实现服务注册与发现、客户端负载均衡的学习就到此为止,后面开始学习如何使用Nacos作为配置中心。